三十天自制操作系统(5)

第13天

在及时之前的顺序中老是新装置一个定时器都如开创一个队与之对应,这样效率不如而且造成操作系统主程序中逻辑复杂。我们事先想办法将定时器的信息队列合并。怎么统一为?首先想转我们为什么事先如果将定时器的音讯队列分开设置,为每个定时器分配一个阵呢。主要是因每个定时器超时所对应之操作不一样,因此为区别不同定时器超时操作,所以才拿班分开。那如若我们为每个定时器往队列中放入数据的值都不同,再每个定时器超时的时段先判断队列中的数码属于哪个计时器,我们不怕得独家开展不同之操作了,也达到了事先的脚下。而且代码肯定又简短了,起码不用作那么基本上判断了。

对接下去还要举行过多计时器的优化办事,但是我们现在还从未考虑怎么测试优化的结果。我们得这么,在计时器3秒中断的下把count设置为0,一直鼎力计数,然后于10秒中断的时节已计数,并把结果显示在窗口中。如果数字更是怪说明系统执行进度越来越快,性能为即越好。

咱俩前面有别定时器中断时,是故为信息队列中发送数据的不等来分的。那会不能够我们把鼠标和键盘使用的消息队列也与定时器合在一起,也用为信息队列中传递的数来区别为?下面我们定义一下间断类型。

  • 0~1 光标闪烁定时器
  • 3 3秒定时器
  • 10 10秒定时器
  • 256~511 键盘输入(从键盘控制器读入的价值更增长256)
  • 512~767 鼠标输入(从键盘控制器读入的价值更添加 512)

咱们前的序列定的采用的数据类型为char只发8各类,现在咱们用其反吧int类型。其他基本无呀变动。只是在列中读入键盘与鼠标灵数据的时候如果分别减去256暨512。

以拍卖定时器中断的当儿要的岁月支出是寻找下一个过期定时器之后的走操作,我们便是要想艺术取消活动操作。

咱于TIMER结构体中初定义一个TIMER指针,指向下一个将过的定时器。

struct TIMER {
  struct TIMER *next;
  unsigned int timeout, flags;
  struct FIFO32 *fifo;
  int data;
};

实际上就是将线性表改造成为链表操作,中断处理程序这么转写

void inthandler20(int *esp)
{
  int i;
  struct TIMER *timer;
  io_out8(PIC0_OCW2, 0x60);
  timerctl.count++;
  if (timerctl.next > timerctl.count) {
    return;
  }
  timer = timerctl.t0; //首先把最前面的地址赋给timer
  for (i = 0; i < timerctl.using; i++) {
    //因为timers的定时器都处于运行状态,所以不确认flags
     if (timer->timeout > timerctl.count) {
      break;
    }
    //超时
    timer->flags = TIMER_FLAGS_ALLOC;
    fifo32_put(timer->fifo, timer->data);
    timer = timer->next; //下一个定时器的地址赋给timer
  }
  timerctl.using -= i;
/*新移位 */
  timerctl.t0 = timer;
  /* timerctl.next的设定*/
  if (timerctl.using > 0) {
    timerctl.next = timerctl.t0->timeout;
  } else {
    timerctl.next = 0xffffffff;
  }
  return;
}

定时器设置函数

 void timer_settime(struct TIMER *timer, unsigned int timeout)
{
  int e;
  struct TIMER *t, *s;
  timer->timeout = timeout + timerctl.count;
  timer->flags = TIMER_FLAGS_USING;
  e = io_load_eflags();
  io_cli();
  timerctl.using++;
  if (timerctl.using == 1) {
    timerctl.t0 = timer;
    timer->next = 0; 
    timerctl.next = timer->timeout;
    io_store_eflags(e);
    return;
  }
  t = timerctl.t0;
  if (timer->timeout <= t->timeout) {
    timerctl.t0 = timer;
    timer->next = t;
    timerctl.next = timer->timeout;
    io_store_eflags(e);
    return;
  }
  for (;;) {
    s = t;
    t = t->next;
    if (t == 0) {
      break; 
    }
    if (timer->timeout <= t->timeout) {
      s->next = timer; 
      timer->next = t; 
      io_store_eflags(e);
      return;
    }
  }
  s->next = timer;
  timer->next = 0;
  io_store_eflags(e);
  return;
}

尽管先后变长了,但是出于引入了链表的定义,不再需要举行活动操作,在定时器多的景下,效率绝对比线性表慢慢倒要及早多。

解析一下方程序变长的来头。主要是插入链表的时节来了4种状态:1、运行面临之定时器只发一个;2、插入到最前的情形;3、插入到中路的情状;4、插入到结尾之状。

为减小插边链表时候所考虑的状态,我们引入了哨兵的概念。也就算是安装一个永久无存都连续在最终到之定时器。有矣这个定时器之后,插入链表的时节就是单单生2种植状况了,插入到最好前头的情状以及插到中等的情事。

修改之后的定时器设置函数

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
  int e;
  struct TIMER *t, *s;
  timer->timeout = timeout + timerctl.count;
  timer->flags = TIMER_FLAGS_USING;
  e = io_load_eflags();
  io_cli();
  t = timerctl.t0;
  if (timer->timeout <= t->timeout) {
    timerctl.t0 = timer;
    timer->next = t; 
    timerctl.next = timer->timeout;
    io_store_eflags(e);
    return;
  }
  for (;;) {
    s = t;
    t = t->next;
    if (timer->timeout <= t->timeout) {
      s->next = timer; 
      timer->next = t; 
      io_store_eflags(e);
      return;
    }
  }
}

可看看简化了许多。简化后底中断处理函数

void inthandler20(int *esp)
{
  struct TIMER *timer;
  io_out8(PIC0_OCW2, 0x60); 
  timerctl.count++;
  if (timerctl.next > timerctl.count) {
    return;
  }
  timer = timerctl.t0; 
  for (;;) {
    if (timer->timeout > timerctl.count) {
      break;
    }
    timer->flags = TIMER_FLAGS_ALLOC;
    fifo32_put(timer->fifo, timer->data);
    timer = timer->next; 
  }
  timerctl.t0 = timer;
  timerctl.next = timer->timeout;
  return;
}

第14天

当前咱们的操作系统使用的分辨率为320*200,我们可以想办法将分辨率提高上去。以前设置分辨率的早晚是用ah
= 0; al =
画面模式;设置的。更充分一些的镜头模试叫作VBE。在挺早时候电脑规格是出于IBM公司制定的
,当然为规定了显卡画面模式,各家显卡公司即为IBM的正式制作显卡。但是后来显卡公司之艺能力超过了IBM,原来的显卡标准既不适用了,各家显卡公司就制定了自己的科班。为了给操作系统及应用程序能下各家店铺的显卡,各家显卡公司协办起来建立了VESA(Video
Electronics Standards
Association),也就是视频电子标准协会。这个协会制定了形通用的设定方法,也制造了专门的BIOS。这个BIOS被号称VESA
BIOS extension,简称也VBE。切换到VBE使用ax = 0x4f02; bx = 画面模式。

  • 0x101 6404808位彩色
  • 0x103 8006008位彩色
  • 0x105 10247688位彩色
  • 0x107 128010248位彩色

相思如果取高分辨率要事先查询一下机支持不支持VBE的亮模式。

MOV     AX,0x9000
MOV     ES,AX
MOV     DI,0
MOV     AX,0x4f00
INT     0x10
CMP     AX,0x004f
JNE     scrn320

先期管es赋值为0x9000,di赋值为0,ax赋值为0x4f00,然后int
0x10。如果ax变为0x004f的语句就是印证有VBE不是者价的语虽不得不使用320*200的分辨率了。

搭下检查VBE的版本是无是2.0之上,如果非是2.0上述那也未能够以高分辨率。

MOV     AX,[ES:DI+4]
CMP     AX,0x0200
JB      scrn320 

接下来再次检查0x105底画面模式会不能够采取

MOV     CX,VBEMODE
MOV     AX,0x4f01
INT     0x10
CMP     AX,0x004f
JNE     scrn320

吓,如果证明0x105好以了,我们再度真正认0x105之镜头信息,重要之音发6单。

  • word[es:di+0x00] 模式属性,bit7非是1就算不好办(能加上0x4000)
  • word[es:di+0x12] X的分辨 率
  • word[es:di+0x14] Y的分辨率
  • byte[es:di+0x19] 颜色数,必须为8
  • byte[es:di+0x1b] 颜色的指定方法,必须也4,调色板模式
  • word[es:di+0x28] VRAM的地址

面的6只基本点性质我们设确定3独,都是不利的之后,就得把这些消息定入指定的内存了,然后跳了scrn320路序段直接进高档画面模式了。

连片下做键盘输入的拍卖。我们已足以于键盘中断处理程序中取到键盘控制寄存器中之价了。这个价值我具体指向许哪个按键被据下或者下有一个应和之报表。其中以键松开时的价值为按键按下时值加上0x80。可以创建一个屡组,从0开始按这表把键盘的扫描码换成字符的ASCII码。之前我们已经为此ASCII码创建了字文件,然后根据取得之ASCII码在屏幕及出示出。

苟我们将光标按照键盘输入情况左右活动也殊简短。首先定义一个cursor_x变量,用于存储光标的职。一开始鼠标靠近窗口的极度左边。然后判断键盘输入的按键是否是索要展示的按键,如果要出示,那么当cursor_x位置上马写副对应的字符,然后cursor_x+8,然后于新的cursor_x位置更描绘有光标。

立即仍开还实现了照停鼠标拖动实现鼠标跟着鼠标指针动。但是和咱们平素windows下走鼠标的不等,这里只是简短得落实。处理鼠标循环的一部分受到,先判断鼠标左键是免是一度按照下,如果按照下,那把窗口及时移动至鼠标所在的岗位。

第15天

马上同上想方落实多任务。所谓的大都任务便是CPU在高效得切换各个任务,使电脑使用者感到CPU在以处理不同的职责。切换任务之速度不可知尽抢吧未能够太慢。因为切换任务为是出本钱的,如果尽抢,切换任伤的付出就顶可怜,如果尽慢,还无若不要多作务,因为将应太慢。

当CPU处理任务切换时,会先把寄存器中之价值周形容副内存,然后拿运行另一个任务所需要的CPU寄存器的价由外存中读取出来,这样即使好了一如既往浅切换。这写副内存和朗诵博内存所需要的工夫即相同任务切换所要之出。
寄存器写副内存的数据结构叫做“任务状态段”(task status segment)。

struct TSS32 {
  int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
  int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
  int es, cs, ss, ds, fs, gs;
  int ldtr, iomap;
};

TSS32结构体中共有26独int变量,合计104字节第一行保存之不是寄存器数据,而是和任务育设置
相关的其余数据。第二执行是32号寄存器。第二实施是16各类寄存器,但是咱要用32各内存空间存储它们。第4执也是与职责有关的旁设置。我们临时用ldtr设置为0,将iomap设置也0x40000000。

倘进行任务切换要就此jmp指令。jmp指令分点儿栽,第一栽才变动写EIP也尽管是所谓的near模式;第二栽转移写cs和eip,就是所谓的far模式。如果同样漫漫jmp指令的对象地址段不是不过实施的代码,而是tss的话语,cpu就无见面实行日常的改写cs和eip,而是将立刻条指令理解为天职切换。CPU中还有一个TR寄存器,task
register,它的打算是为CPU记住当前正运行哪一个职责,我们于TR赋值的时段,必须把GDT编号乘以8。给TR赋值不可知用MOV指令,有一个特别的指令:LTR。下面我们看一下tss结构体如何赋值:

task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
tss_b.eip = (int) &task_b_main;
tss_b.eflags = 0x00000202; /* IF = 1; */
tss_b.eax = 0;
tss_b.ecx = 0;
tss_b.edx = 0;
tss_b.ebx = 0;
tss_b.esp = task_b_esp;
tss_b.ebp = 0;
tss_b.esi = 0;
tss_b.edi = 0;
tss_b.es = 1 * 8;
tss_b.cs = 2 * 8;
tss_b.ss = 1 * 8;
tss_b.ds = 1 * 8;
tss_b.fs = 1 * 8;
tss_b.gs = 1 * 8;

咱事先打晚6单段落寄存器赋值开始看,我们受cs赋值为GDT2声泪俱下,其他是GDT1如泣如诉,和bootpack.c使用了一样的地方段。然后是eip,我们管task_b_main的函数地址与给她。然后是esp,也即是堆栈地址,我们新申请了64K内存空间,给taskB。如果同原的主函数以相同的栈那些切换任务的当儿自然会生出问题。

咱们先行这么尝试多任务:在主函数定时器10秒超时的时候切换到任务b,然后以职责b的定时候5秒超时之早晚切换回任务a。

void task_b_main(void)
{
  struct FIFO32 fifo;
  struct TIMER *timer;
  int i, fifobuf[128];
  fifo32_init(&fifo, 128, fifobuf);
  timer = timer_alloc();
  timer_init(timer, &fifo, 1);
  timer_settime(timer, 500);
  for (;;) {
    io_cli();
    if (fifo32_status(&fifo) == 0) {
      io_sti();
      io_hlt();
    } else {
      i = fifo32_get(&fifo);
      io_sti();
      if (i == 1) { 
        taskswitch3(); 
      }
    }
  }
}

早已实现了点滴单任务中的跳转。接下来我们贯彻A,B两个任务每过0.02秒即转换一破。先安装一个定时器time_ts变量,超时的日吧0.02秒,如果超时向行中发送0x2。任务A可以领鼠标和键盘输入的,我们很爱确认任务A是否当运行,问题不怕来以职责B我们哪确定任务B也会正常运行也?在任务B中安装一个计数器,每0.01秒在屏幕及写来计数器的数值便可了。
可遇到一个问题,如何才会于任务B知道sht_back,只有为任务B知道者地点才能够在桌面上出示数字。我们用栈来传递数据。首先要知道C语言的函数传递参数就是因此栈,比如C语言的函数void
setA(int
a);这个函数被调用后,函数会从esp+4的内存中取出a这个数值。我们这边就是动C语言的是特点。在过到任务B的时候传递一个参数,任务B的函数声明也
void task_b_main(struct SHEET
*sht_back);这样运行任务B的当儿可以直接采用sht_back这个变量。我们先行将任务B的esp减去8,然后拿esp+4内存地址的值赋为sht_back,这样就算足以了,任务B函数在以sht_back变量值的早晚就是是esp+4。

眼下我们都是于任务中一直写切换任务的程序段,如果要是确实落实多任务最是写一段先后调整任务中的切换,而未是说明任务协调切换。

struct TIMER *mt_timer;
int mt_tr;

void mt_init(void)
{
  mt_timer = timer_alloc();
  timer_settime(mt_timer, 2);
  mt_tr = 3 * 8;
  return;
}

void mt_taskswitch(void)
{
  if (mt_tr == 3 * 8) {
    mt_tr = 4 * 8;
  } else {
    mt_tr = 3 * 8;
  }
  timer_settime(mt_timer, 2);
  farjmp(0, mt_tr);
  return;
 }

上述是兑现任务切换的函数,这里设置了一个0.02秒的计时器,然后以计时器中断里如果出现这中断时调用mt_taskwitch这个函数就好了,两独任务之程序中即不需要协调写切换任务之次了。