发布于 

30天自制操作系统(15)

DAY15_多任务(1)

1.挑战任务切换

  • 多任务:多个应用程序同时运行的状态(也就是同时打开好几个窗口的状态)。
  • 实际上这些程序==并没有在同时运行==,只是看上去好像是在同时运行一样。
    15.1
  • 在一般的操作系统中,这个切换的动作每0.01~0.03秒就会进行一次。当然,切换的速度越快,让人觉得程序是在同时运行的效果也就越好。不过,CPU进行程序切换(我们称为“任务切换”)这个动作本身就需要消耗一定的时间,这个时间大约为0.0001秒左右,不同的CPU及操作系统所需的时间也有所不同。
  • CPU处理方式:当你向CPU发出任务切换的指令时,CPU会先把寄存器中的值全部写入内存中,这样做是为了当以后切换回这个程序的时候,可以从中断的地方继续运行。接下来,为了运行下一个程序,CPU会把所有寄存器中的值从内存中读取出来(当然,这个读取的地址和刚刚写入的地址一定是不同的,不然就相当于什么都没变嘛),这样就完成了一次切换。我们前面所说的任务切换所需要的时间,正是对内存进行写入和读取操作所消耗的时间。
  • TSS 任务状态段 task status segment
  • EIP 扩展指令指针寄存器 extended instruction pointer:CPU用来记录下一条需要执行的指令位于内存中哪个地址的寄存器,因此它才被称为“指令指针”。如果没有这个寄存器,记性不好的CPU就会忘记自己正在运行哪里的程序,于是程序就没办法正常运行了。每执行一条指令,EIP寄存器中的值就会自动累加,从而保证一直指向下一条指令所在的内存地址。

实际任务切换

  • 创建两个TSS
    struct TSS32 tss_a, tss_b;
  • 向它们的Idtr和iomap分别存入合适的值
    tss_a.ldtr = 0; 
    tss_a.iomap = 0x40000000;
    tss_b.ldtr = 0;
    tss_b.iomap = 0x40000000;
  • 将它们两个在GDT中进行定义

    struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT; 

    set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);
    set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32);
  • 我们向TR寄存器存入3 * 8这个值,这是因为我们刚才把当前运行的任务定义为GDT的3号。

  • 要进行任务切换,我们必须执行far模式的跳转指令

  • 在eip中,我们需要定义在切换到这个任务的时候,要从哪里开始运行。

2.任务切换进阶

  • 目标:从任务A切换到任务B,再切换回任务A

3.做个简单的多任务(1)

  • 目标:实现更快速的,来回交替的任务切换。这样我们就可以告别光标停住、鼠标卡死、键盘打不了字的情况,让两个任务看上去好像在同时运行一样。
  • 把taskswitch写成一个函数
    _farjmp: ; void farjmp(int eip, int cs); 
    JMP FAR [ESP+4] ; eip, cs
    RET

4.做个简单的多任务(2)

5.提高运行速度

  • 因为我们的程序每计1个数就在画面上显示一次,但1秒钟之内刷新100次以上的话,人眼根本就分辨不出来,所以我们不需要计1个数就刷新一次,只要每隔0.01秒刷新一次就足够了。

6.测试运行速度

向task_b_main添加代码测试运行速度

void task_b_main(struct SHEET *sht_back) 
{
struct FIFO32 fifo;
struct TIMER *timer_ts, *timer_put, *timer_1s;
int i, fifobuf[128], count = 0, count0 = 0;
char s[12];
(中略)
timer_1s = timer_alloc();
timer_init(timer_1s, &fifo, 100);
timer_settime(timer_1s, 100);
for (;;) {
count++;
io_cli();
if (fifo32_status(&fifo) == 0) {
io_sti();
} else {
i = fifo32_get(&fifo);
io_sti();
if (i == 1) {
(中略)
} else if (i == 2) {
(中略)
} else if (i == 100) {
sprintf(s, "%11d", count - count0);
putfonts8_asc_sht(sht_back, 0, 128, COL8_FFFFFF, COL8_008484, s, 11);
count0 = count;
timer_settime(timer_1s, 100);
}
}
}
}

7.多任务进阶

  • 真正的多任务,是要做到在程序本身不知道的情况下进行任务切换。
  • 果使用这样的设计,即便在程序中不进行任务切换的处理(比如忘记写了,或者因为bug没能正常切换之类的),也一定会正常完成切换。之前那种多任务的话,如果任务B因为发生bug而无法进行切换,那么当切换到任务B以后,其他的任务就再也无法运行了,这样会造成无论是按键盘还是动鼠标都毫无反应的悲剧。