7 多任务进阶(harib12g)

到现在为止,我们所做的多任务都是依靠在HariMain和task_b_main中写入负责任务切换的代码来实现的。有人会说,这种多任务方式“不是真正的多任务”(即便如此,应该也不至于被说成是“假的”多任务)。

那么真正的多任务又是什么样的呢?真正的多任务,是要做到在程序本身不知道的情况下进行任务切换。既然如此,我们就来为“纸娃娃系统”添加真正的多任务吧。

首先我们来创建这样一个函数。

本次的mtask.c节选

  1. struct TIMER *mt_timer;
  2. int mt_tr;
  3. void mt_init(void)
  4. {
  5. mt_timer = timer_alloc();
  6. /*这里没有必要使用timer_init */
  7. timer_settime(mt_timer, 2);
  8. mt_tr = 3 * 8;
  9. return;
  10. }
  11. void mt_taskswitch(void)
  12. {
  13. if (mt_tr == 3 * 8) {
  14. mt_tr = 4 * 8;
  15. } else {
  16. mt_tr = 3 * 8;
  17. }
  18. timer_settime(mt_timer, 2);
  19. farjmp(0, mt_tr);
  20. return;
  21. }

mt_init函数的功能是初始化mt_timer和mt_tr的值,并将计时器设置为0.02秒之后,仅此而已。在这里,变量mt_tr实际上代表了TR寄存器,而不需要使用timer_init是因为在发生超时的时候不需要向FIFO缓冲区写入数据。具体内容请继续往下看。

接下来,mt_taskswitch函数的功能是按照当前的mt_tr变量的值计算出下一个mt_tr的值,将计时器重新设置为0.02秒之后,并进行任务切换,很简单吧。

■■■■■

下面我们来改造一下timer.c的inthandler20。

本次的timer.c节选

  1. void inthandler20(int *esp)
  2. {
  3. char ts = 0;
  4. (中略)
  5. for (;;) {
  6. /* timers的计时器全部在工作中,因此不用确认flags */
  7. if (timer->timeout > timerctl.count) {
  8. break;
  9. }
  10. /*超时*/
  11. timer->flags = TIMER_FLAGS_ALLOC;
  12. if (timer != mt_timer) {
  13. fifo32_put(timer->fifo, timer->data);
  14. } else {
  15. ts = 1; /* mt_timer超时*/
  16. }
  17. timer = timer->next; /*将下一个计时器的地址赋给timer */
  18. }
  19. timerctl.t0 = timer;
  20. timerctl.next = timer->timeout;
  21. if (ts != 0) {
  22. mt_taskswitch();
  23. }
  24. return;
  25. }

在这里,如果产生超时的计时器是mt_timer的话,不向FIFO写入数据,而是将ts置为1。最后判断如果ts的值不为0,就调用mt_taskswitch进行任务切换。

看了上面这段代码,你可能会问,为什么要用ts这个变量呢?在/* 超时 */的地方直接调用mt_taskswitch不就好了吗?也就是下面这样:

出问题的例子

  1. void inthandler20(int *esp)
  2. {
  3. (中略)
  4. for (;;) {
  5. /* timers的计时器全部在工作中,因此不用确认flags */
  6. if (timer->timeout > timerctl.count) {
  7. break;
  8. }
  9. /*超时*/
  10. timer->flags = TIMER_FLAGS_ALLOC;
  11. if (timer != mt_timer) {
  12. fifo32_put(timer->fifo, timer->data);
  13. } else {
  14. mt_taskswitch();
  15. }
  16. timer = timer->next; /*将下一个计时器的地址赋给timer */
  17. }
  18. timerctl.t0 = timer;
  19. timerctl.next = timer->timeout;
  20. return;
  21. }

为什么不这样写呢?这样写的确可以让代码更简短,但是会出问题。

出问题的原因在于,调用mt_taskswitch进行任务切换的时候,即便中断处理还没完成,IF(中断允许标志)的值也可能会被重设回1(因为任务切换的时候会同时切换EFLAGS)。这样可不行,在中断处理还没完成的时候,可能会产生下一个中断请求,这会导致程序出错。

因此我们需要采用这样的设计——等中断处理全部完成之后,再在必要时调用mt_taskswitch。

■■■■■

接下来我们只需要将HariMain和task_b_main里面有关任务切换的代码删掉即可。删代码没什么难度,而且HariMain又很长,为了节约纸张我们就省略了,只把task_b_main写在下面吧。

本次的bootpack.c节选

  1. void task_b_main(struct SHEET *sht_back)
  2. {
  3. struct FIFO32 fifo;
  4. struct TIMER *timer_put, *timer_1s;
  5. int i, fifobuf[128], count = 0, count0 = 0;
  6. char s[12];
  7. fifo32_init(&fifo, 128, fifobuf);
  8. timer_put = timer_alloc();
  9. timer_init(timer_put, &fifo, 1);
  10. timer_settime(timer_put, 1);
  11. timer_1s = timer_alloc();
  12. timer_init(timer_1s, &fifo, 100);
  13. timer_settime(timer_1s, 100);
  14. for (;;) {
  15. count++;
  16. io_cli();
  17. if (fifo32_status(&fifo) == 0) {
  18. io_sti();
  19. } else {
  20. i = fifo32_get(&fifo);
  21. io_sti();
  22. if (i == 1) {
  23. sprintf(s, "%11d", count);
  24. putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11);
  25. timer_settime(timer_put, 1);
  26. } else if (i == 100) {
  27. sprintf(s, "%11d", count - count0);
  28. putfonts8_asc_sht(sht_back, 0, 128, COL8_FFFFFF, COL8_008484, s, 11);
  29. count0 = count;
  30. timer_settime(timer_1s, 100);
  31. }
  32. }
  33. }
  34. }

像这样,把有关任务切换的部分全部删掉就可以了。

■■■■■

好,我们来试试看能不能正常工作吧。“make run”,成功了,真开心!不过看上去和之前没什么区别。

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

7 多任务进阶(harib12g) - 图1

真正的多任务也成功了!

真正的多任务不会发生这样的问题,因此这种方式更好……话虽如此,但其实即便是harib12g,在任务B发生bug的情况下,也有可能出现键盘输入失去响应的问题。例如,明明写了io_cli();却忘记写io_sti();的话,中断就会一直处于禁止状态,即使产生了计时器中断请求,也不会被传递给中断处理程序。这样一来,mt_taskswitch当然也就不会被调用,这意味着任务切换也就不会被执行。

其实CPU已经为大家准备了解决这个问题的方法,因此我们稍后再考虑这个问题吧。

好,我们在真机环境下运行一下,看看速度会不会变慢。咦?速度非但没有变慢,反而变快了?运行结果是6493300,和之前的14281323相比,性能的差距是2.2倍。harib12f的时候还是差3倍来着,这次也太快了吧。我们再把timer_settime(timer_put,1);删掉,看看如果不显示计数只显示速度会怎样?说不定速度会变得更快呢?哇!结果出来了,6890930,居然达到了2.07倍,离理想值2.0倍又近了一步呢。

现在想想看,为什么速度反而会变快呢?我想这是因为在任务切换的时候,我们不再使用FIFO缓冲区的缘故。之前我们向FIFO中写入超时的编号,然后从中读取这个编号来判断是否执行任务切换,相比之下,现在的做法貌似对于CPU来说负担更小些,一定是这个原因吧。

哎呀,不知不觉就已经很晚了。今天就先到这里吧,我们明天继续。