1 任务管理自动化(harib13a)

大家好!昨天我们已经实践了很多关于多任务的内容,不过今天我们还得继续讲多任务。可能有人会说,“老是讲多任务都听腻了啊!”,但多任务真的非常重要(当然,如果你不想做一个多任务的操作系统,那就不重要啦)。从笔者的角度来说,希望大家能够在充分做好多任务机制的基础上,再利用多任务逐步完善操作系统本身。因此,大家再稍微忍耐一下吧。

在15.7节中,我们已经实现了真正的多任务,不过这样还不够完善,或者说不太好用。如果我们想要运行三个任务的话,就必须改写mt_taskswitch的代码。笔者认为,这样的设计实在太逊了,如果能像当初定时器和窗口背景的做法一样(具体如下),是不是觉得更好呢?

task = task\_alloc();

task->tss.eip = ○×;

task->tss.esp = △◇;

像上面这样设定各种寄存器的初始值

task\_run(task);

我们就先以此为目标,对代码进行改造吧。

■■■■■

于是我们写了下面这样一段程序,struct TASKCTL是仿照struct SHTCTL写出来的,首先我们来看结构定义。

本次的bootpack.h节选

  1. #define MAX_TASKS 1000 /*最大任务数量*/
  2. #define TASK_GDT0 3 /*定义从GDT的几号开始分配给TSS */
  3. struct TSS32 {
  4. int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
  5. int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
  6. int es, cs, ss, ds, fs, gs;
  7. int ldtr, iomap;
  8. };
  9. struct TASK {
  10. int sel, flags; /* sel用来存放GDT的编号*/
  11. struct TSS32 tss;
  12. };
  13. struct TASKCTL {
  14. int running; /*正在运行的任务数量*/
  15. int now; /*这个变量用来记录当前正在运行的是哪个任务*/
  16. struct TASK *tasks[MAX_TASKS];
  17. struct TASK tasks0[MAX_TASKS];
  18. };

■■■■■

下面我们来创建用来对struct TASKCTL及其当中包含的struct TASK进行初始化的函数task_init。由于struct TASKCTL是一个很庞大的结构,因此我们让它从memman_alloc来申请内存空间。这个函数是用来替代mt_init使用的。

我们使用sel这个变量来存放GDT编号,sel是“selector”的缩写,意为选择符。因为英特尔的大叔管段地址叫做selector,所以笔者只是照猫画虎而已,也就是代表“应该从GDT里面选择哪个编号”的意思。

本次的mtask.c节选

  1. struct TASKCTL *taskctl;
  2. struct TIMER *task_timer;
  3. struct TASK *task_init(struct MEMMAN *memman)
  4. {
  5. int i;
  6. struct TASK *task;
  7. struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
  8. taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));
  9. for (i = 0; i < MAX_TASKS; i++) {
  10. taskctl->tasks0[i].flags = 0;
  11. taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
  12. set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
  13. }
  14. task = task_alloc();
  15. task->flags = 2; /*活动中标志*/
  16. taskctl->running = 1;
  17. taskctl->now = 0;
  18. taskctl->tasks[0] = task;
  19. load_tr(task->sel);
  20. task_timer = timer_alloc();
  21. timer_settime(task_timer, 2);
  22. return task;
  23. }

调用task_init,会返回一个内存地址,意思是“现在正在运行的这个程序,已经变成一个任务了”。可能大家不是很能理解这个说法,在调用init之后,所有程序的运行都会被当成任务来进行管理,而调用init的这个程序,我们也要让它所属于某个任务,这样一来,通过调用任务的设置函数,就可以对任务进行各种控制,比如说修改优先级等。

■■■■■

下面我们来创建用来初始化一个任务结构的函数。

本次的mtask.c节选

  1. struct TASK *task_alloc(void)
  2. {
  3. int i;
  4. struct TASK *task;
  5. for (i = 0; i < MAX_TASKS; i++) {
  6. if (taskctl->tasks0[i].flags == 0) {
  7. task = &taskctl->tasks0[i];
  8. task->flags = 1; /*正在使用的标志*/
  9. task->tss.eflags = 0x00000202; /* IF = 1; */
  10. task->tss.eax = 0; /*这里先置为0*/
  11. task->tss.ecx = 0;
  12. task->tss.edx = 0;
  13. task->tss.ebx = 0;
  14. task->tss.ebp = 0;
  15. task->tss.esi = 0;
  16. task->tss.edi = 0;
  17. task->tss.es = 0;
  18. task->tss.ds = 0;
  19. task->tss.fs = 0;
  20. task->tss.gs = 0;
  21. task->tss.ldtr = 0;
  22. task->tss.iomap = 0x40000000;
  23. return task;
  24. }
  25. }
  26. return 0; /*全部正在使用*/
  27. }

关于寄存器的初始值,这里先随便设置了一下。如果不喜欢这个值,可以在bootpack.c里面设置一下。

■■■■■

接下来是task_run,这个函数非常短,看看这样写如何。

本次的mtask.c节选

  1. void task_run(struct TASK *task)
  2. {
  3. task->flags = 2; /*活动中标志*/
  4. taskctl->tasks[taskctl->running] = task;
  5. taskctl->running++;
  6. return;
  7. }

这个函数的作用是,将task添加到tasks的末尾,然后使running加1,仅此而已。

■■■■■

最后是task_switch,这个函数用来代替mt_taskswitch。

在timer.c中对mt_taskswitch的调用,也相应地修改为调用task_switch。

本次的mtask.c节选

  1. void task_switch(void)
  2. {
  3. timer_settime(task_timer, 2);
  4. if (taskctl->running >= 2) {
  5. taskctl->now++;
  6. if (taskctl->now == taskctl->running) {
  7. taskctl->now = 0;
  8. }
  9. farjmp(0, taskctl->tasks[taskctl->now]->sel);
  10. }
  11. return;
  12. }

当running为1时,不需要进行任务切换,函数直接结束。当running大于等于2时,先把now加1,然后把now所代表的任务切换成当前任务,最后再将末尾的任务移动到开头。

■■■■■

现在我们用以上这些结构和函数,将bootpack.c改写一下。

本次的bootpack.c节选

  1. void HariMain(void)
  2. {
  3. (中略)
  4. struct TASK *task_b;
  5. (中略)
  6. task_init(memman);
  7. task_b = task_alloc();
  8. task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
  9. task_b->tss.eip = (int) &task_b_main;
  10. task_b->tss.es = 1 * 8;
  11. task_b->tss.cs = 2 * 8;
  12. task_b->tss.ss = 1 * 8;
  13. task_b->tss.ds = 1 * 8;
  14. task_b->tss.fs = 1 * 8;
  15. task_b->tss.gs = 1 * 8;
  16. *((int *) (task_b->tss.esp + 4)) = (int) sht_back;
  17. task_run(task_b);
  18. (中略)
  19. }

行数变少了,不过相应地mtask.c却变长了,好像也不能说是非常好。不过好在,在HariMain中,就再也不用管GDT到底怎样、任务B的tss要分配到GDT的几号等。这些麻烦的事情,全部交给mtask.c来处理了。

当需要增加任务数量的时候,不用再像之前那样修改task_switch了,只要先task_alloc,然后再task_run就行了。

好了,我们来运行一下,“make run”。不错,貌似成功了。