1 任务管理自动化(harib13a)
大家好!昨天我们已经实践了很多关于多任务的内容,不过今天我们还得继续讲多任务。可能有人会说,“老是讲多任务都听腻了啊!”,但多任务真的非常重要(当然,如果你不想做一个多任务的操作系统,那就不重要啦)。从笔者的角度来说,希望大家能够在充分做好多任务机制的基础上,再利用多任务逐步完善操作系统本身。因此,大家再稍微忍耐一下吧。
在15.7节中,我们已经实现了真正的多任务,不过这样还不够完善,或者说不太好用。如果我们想要运行三个任务的话,就必须改写mt_taskswitch的代码。笔者认为,这样的设计实在太逊了,如果能像当初定时器和窗口背景的做法一样(具体如下),是不是觉得更好呢?
task = task\_alloc();
task->tss.eip = ○×;
task->tss.esp = △◇;
像上面这样设定各种寄存器的初始值
task\_run(task);
我们就先以此为目标,对代码进行改造吧。
■■■■■
于是我们写了下面这样一段程序,struct TASKCTL是仿照struct SHTCTL写出来的,首先我们来看结构定义。
本次的bootpack.h节选
#define MAX_TASKS 1000 /*最大任务数量*/
#define TASK_GDT0 3 /*定义从GDT的几号开始分配给TSS */
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;
};
struct TASK {
int sel, flags; /* sel用来存放GDT的编号*/
struct TSS32 tss;
};
struct TASKCTL {
int running; /*正在运行的任务数量*/
int now; /*这个变量用来记录当前正在运行的是哪个任务*/
struct TASK *tasks[MAX_TASKS];
struct TASK tasks0[MAX_TASKS];
};
■■■■■
下面我们来创建用来对struct TASKCTL及其当中包含的struct TASK进行初始化的函数task_init。由于struct TASKCTL是一个很庞大的结构,因此我们让它从memman_alloc来申请内存空间。这个函数是用来替代mt_init使用的。
我们使用sel这个变量来存放GDT编号,sel是“selector”的缩写,意为选择符。因为英特尔的大叔管段地址叫做selector,所以笔者只是照猫画虎而已,也就是代表“应该从GDT里面选择哪个编号”的意思。
本次的mtask.c节选
struct TASKCTL *taskctl;
struct TIMER *task_timer;
struct TASK *task_init(struct MEMMAN *memman)
{
int i;
struct TASK *task;
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));
for (i = 0; i < MAX_TASKS; i++) {
taskctl->tasks0[i].flags = 0;
taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
}
task = task_alloc();
task->flags = 2; /*活动中标志*/
taskctl->running = 1;
taskctl->now = 0;
taskctl->tasks[0] = task;
load_tr(task->sel);
task_timer = timer_alloc();
timer_settime(task_timer, 2);
return task;
}
调用task_init,会返回一个内存地址,意思是“现在正在运行的这个程序,已经变成一个任务了”。可能大家不是很能理解这个说法,在调用init之后,所有程序的运行都会被当成任务来进行管理,而调用init的这个程序,我们也要让它所属于某个任务,这样一来,通过调用任务的设置函数,就可以对任务进行各种控制,比如说修改优先级等。
■■■■■
下面我们来创建用来初始化一个任务结构的函数。
本次的mtask.c节选
struct TASK *task_alloc(void)
{
int i;
struct TASK *task;
for (i = 0; i < MAX_TASKS; i++) {
if (taskctl->tasks0[i].flags == 0) {
task = &taskctl->tasks0[i];
task->flags = 1; /*正在使用的标志*/
task->tss.eflags = 0x00000202; /* IF = 1; */
task->tss.eax = 0; /*这里先置为0*/
task->tss.ecx = 0;
task->tss.edx = 0;
task->tss.ebx = 0;
task->tss.ebp = 0;
task->tss.esi = 0;
task->tss.edi = 0;
task->tss.es = 0;
task->tss.ds = 0;
task->tss.fs = 0;
task->tss.gs = 0;
task->tss.ldtr = 0;
task->tss.iomap = 0x40000000;
return task;
}
}
return 0; /*全部正在使用*/
}
关于寄存器的初始值,这里先随便设置了一下。如果不喜欢这个值,可以在bootpack.c里面设置一下。
■■■■■
接下来是task_run,这个函数非常短,看看这样写如何。
本次的mtask.c节选
void task_run(struct TASK *task)
{
task->flags = 2; /*活动中标志*/
taskctl->tasks[taskctl->running] = task;
taskctl->running++;
return;
}
这个函数的作用是,将task添加到tasks的末尾,然后使running加1,仅此而已。
■■■■■
最后是task_switch,这个函数用来代替mt_taskswitch。
在timer.c中对mt_taskswitch的调用,也相应地修改为调用task_switch。
本次的mtask.c节选
void task_switch(void)
{
timer_settime(task_timer, 2);
if (taskctl->running >= 2) {
taskctl->now++;
if (taskctl->now == taskctl->running) {
taskctl->now = 0;
}
farjmp(0, taskctl->tasks[taskctl->now]->sel);
}
return;
}
当running为1时,不需要进行任务切换,函数直接结束。当running大于等于2时,先把now加1,然后把now所代表的任务切换成当前任务,最后再将末尾的任务移动到开头。
■■■■■
现在我们用以上这些结构和函数,将bootpack.c改写一下。
本次的bootpack.c节选
void HariMain(void)
{
(中略)
struct TASK *task_b;
(中略)
task_init(memman);
task_b = task_alloc();
task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
task_b->tss.eip = (int) &task_b_main;
task_b->tss.es = 1 * 8;
task_b->tss.cs = 2 * 8;
task_b->tss.ss = 1 * 8;
task_b->tss.ds = 1 * 8;
task_b->tss.fs = 1 * 8;
task_b->tss.gs = 1 * 8;
*((int *) (task_b->tss.esp + 4)) = (int) sht_back;
task_run(task_b);
(中略)
}
行数变少了,不过相应地mtask.c却变长了,好像也不能说是非常好。不过好在,在HariMain中,就再也不用管GDT到底怎样、任务B的tss要分配到GDT的几号等。这些麻烦的事情,全部交给mtask.c来处理了。
当需要增加任务数量的时候,不用再像之前那样修改task_switch了,只要先task_alloc,然后再task_run就行了。
好了,我们来运行一下,“make run”。不错,貌似成功了。