自制操作系统日志——第十六天
今天我们再接再厉,继续进一步的完成我们的多任务机制。
一、多任务自动管理化
在昨天我们的多任务都是通过自己手动添加,然后进行运行的,这样子当我们任务过多时,总不好再一个个给他们进行设置吧?
因此呢,我们效仿前面的定时器与图层的自动管理,来进行对多任务的管理:
首先添加并修改结构体部分:
/* mtask.c */
#define MAX_TASKS 1000 //最大的任务数量
#define TASK_GDT0 3 //指从第几号的GDT开始
struct TSS32//共26个int成员,104字节。摘自cpu技术资料里的设定
{
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;//段寄存器的值。段寄存器16位,但是预先设定32,以防止为来说不定是32了
int ldtr, iomap;//任务设置部分。先设置ldtr=0 ,iomap=0x40000000
};
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];
};
extern struct TIMER *task_timer;
struct TASK *task_init(struct MEMMAN *memman);
struct TASK *task_alloc(void);
void task_run(struct TASK *task);
void task_switch(void);
然后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();//接收到akkoc返回的第一个任务
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;
}
- 分配并初始化一个任务:
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;//全部都在使用当中
}
- 运行与切换任务:
//将活动的task添加到tasks的末尾
void task_run(struct TASK *task)
{
task->flags = 2;//活动中的标志
taskctl->tasks[taskctl->running] = task;
taskctl->running++;
return;
}
void task_switch(void)
{
timer_settime(task_timer, 2);
if(taskctl->running >= 2) {//只有两个任务以上才进行任务切换
taskctl->now++;//先把now++,然后把now所代表的任务切换成当前任务
if(taskctl->now == taskctl->running){//当前正在运行的任务是最后一个的话,则切换回第一个任务
taskctl->now = 0;
}
farjmp(0, taskctl->tasks[taskctl->now]->sel);
}
return;
}
- 最后,将原本的mt_switch mt_timer等等都进行替换即可!(在timer.c中) ; 然后还要对主程序进行修改一下:
略
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);
略
然后,make run 发现确实可以正常运行!非常好,我们已经实现自动化的管理了。。
二、任务休眠
截至前一节为止,我们的多任务系统都是给每个任务大约相同的运行时间。但在实际中,并非每个任务都需要一样的运行时间。就比如,我们的任务a,如果没有发生鼠标,键盘中断的话那么其大多时间都是空闲的,这样子就会损耗cpu的运行效率。
因此,我们需要让处于空闲状态的任务进行休眠,以此来提高cpu的运行效率。那么要如何进行休眠呢?当然这很简单,由于我们之前已经建立了任务管理表,因此我们只需将休眠的任务从tasks中剔除就可以达到休眠的效果了。
创建sleep:
void task_sleep(struct TASK *task)
{
int i;
char ts = 0;
if(task->flags == 2){//如果指定任务处于唤醒中
if(task == taskctl->tasks[taskctl->now]){
ts = 1;//当前任务让自己休眠,那么稍后需要进行任务切换;如果是任务A让任务B休眠则不用切换
}
//寻找task的位置
for(i = 0; i < taskctl->running; i++){
if(taskctl->tasks[i] == task){
//找到了该任务的位置
break;
}
}
taskctl->running--;
if(i < taskctl->now ){
taskctl->now--;//需要移动成员,进行相应的处理
}
//移动成员
for(; i < taskctl->running; i++){
taskctl->tasks[i] = taskctl->tasks[ i +1 ];
}
task->flags = 1;//不工作的状态
if(ts !=0 ){//任务切换
if(taskctl->now >= taskctl->running ){//如果now出现异常则修正
taskctl->now = 0;
}
farjmp(0, taskctl->tasks[taskctl->now]->sel);
}
}
return;
}
然后,为了防止我们休眠任务a后,若键盘中断发生像缓冲区写入数据,但无法响应的情况。我们要在fifo中进行一下修改,使得其可以唤醒任务:
首先更改fifo32的结构体:
struct FIFO32{
int *buf;//缓冲区地址
int p, q, size, free, flags;//p 下一写入地址;q 下一读出地址; size是总字节数,free是缓冲区中没有数据的字节数;flag判断溢出
struct TASK *task;
};
然后,修改fifo的初始化与写入的函数:
void fifo32_init(struct FIFO32 *fifo, int size, int *buf, struct TASK *task)
//初始化fifo地址
{
fifo->size = size;
fifo->buf = buf;
fifo->free = size; //空
fifo->flags = 0;
fifo->p = 0 ; //写入位置
fifo->q = 0 ; //读取位置
fifo->task = task; //有数据读入时需要唤醒的任务!!!如果赋予0则代表禁用自动唤醒
return;
}
int fifo32_put(struct FIFO32 *fifo, int data)
//向fifo传递数据并保存
{
if(fifo->free == 0)
{
//无空余,溢出
fifo->flags |= FLAGS_OVERRUN;
return -1;
}
fifo->buf[fifo->p] = data;
fifo->p++;
if(fifo->p == fifo->size)
{
fifo->p = 0;
}
fifo->free--;
if(fifo->task != 0){
if(fifo->task->flags != 2 ){//如果任务处于休眠状态
task_run(fifo->task);
}
}
return 0;
}
最后,进行改写主函数!
- 先初始化fifo缓冲区,将fifo的自动唤醒设置为0,表示禁止中断;
- task_init返回一个地址,赋予task_a;
- 当fifo为空时,将任务a进行休眠。不过这里,我们要先休眠,再开发中断,否则先开放中断的话,在处理休眠过程中如果发生了可能会使得自动唤醒功能出错!!
struct TASK *task_a, *task_b;
略
fifo32_init(&fifo, 128, fifobuf, 0);
略
task_a = task_init(memman);
fifo.task = task_a;
略
for(;;)
{
io_cli(); //IF=0
if (fifo32_status(&fifo) == 0)
{
task_sleep(task_a);
io_sti();
略
void task_b_main(struct SHEET *sht_back)
{
略
fifo32_init(&fifo, 128, fifobuf, 0);
然后,make run一下!
哇哦,这里我们会发现任务b的速度快了两倍不止!!
这是昨天的测速:
这是,今天添加了休眠后的测速:
解释一下原因: 假设我们处理器的能力为每秒200次:
- 那么由于昨天的任务a与任务b所占的时间相同,因此任务b的处理能力为100;
- 其中10用于显示count,90用于计算count;
- 而今天的休眠后,由于a已经休眠了,因此b的处理能力为198(这是因为休眠的任务a也不是完全不消耗处理能力的)
- 那么显示count的还是10,则就会有188用于计算count。
- 因此,大约就是2倍多吧!!
然后,我们设置多个任务同时进行吧!!!这里请查看源代码了,太长了就不放进来了,就看看效果图:
设定任务优先级
这里,我们利用任务的优先级,来确定哪一个任务应该获得的运行时间最长!以确保比较重要的任务的优先级足够,不至于运行时会有所卡顿。
我们拟设置共用n个taskctl,给每一个taskctl设置不同的级别。级别越低的taskctl里的任务越优先执行。例如,在level 0 里只要一任务,就可以忽略level 1 里的所有任务!!!
除此之外,我们还要设计一个同层内的优先级,利用priority给同一级别里的任务分配不同的运行时长,当priority值越大,则代表该任务在本层里的得到的运行时长越长。。
首先,我们设置Bootpack.h:
/* mtask.c */
#define MAX_TASKS 1000 //最大的任务数量
#define TASK_GDT0 3 //指从第几号的GDT开始
#define MAX_TASKS_LV 100 //每个level最多100个任务
#define MAX_TASKLEVELS 10 //共十个level
struct TSS32//共26个int成员,104字节。摘自cpu技术资料里的设定
{
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;//段寄存器的值。段寄存器16位,但是预先设定32,以防止为来说不定是32了
int ldtr, iomap;//任务设置部分。先设置ldtr=0 ,iomap=0x40000000
};
struct TASK
{
int sel, flags; //sel用于存放GDT编号
int level, priority; //设置优先级
struct TSS32 tss;
};
struct TASKLEVEL
{
int running; //正在运行的任务数量
int now; //记录当前正在运行的是哪一个任务
struct TASK *tasks[MAX_TASKS_LV];
};
struct TASKCTL
{
int now_lv; //现在活动中的level
char lv_change;//任务切换时是否需要改变level
struct TASKLEVEL level[MAX_TASKLEVELS];
struct TASK tasks0[MAX_TASKS];
};
在这个声明里,TASKCTL 变成了管理level以及每一个单独的task的。tasklevel就是管理本leve中的每一个任务。然后,我们需要新创建几个用于操作tasklevel的函数:
//返回现在活动中的struct task的地址
struct TASK *task_now(void)
{
struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
return tl->tasks[tl->now];
}
//向tasklevel中增加一个任务
void task_add(struct TASK *task)
{
struct TASKLEVEL *tl = &taskctl->level[task->level];
tl->tasks[tl->running] = task;
tl->running++;
task->flags = 2;//活动中
return;
}
//从tasklevel中删除一个任务
void task_remove(struct TASK *task)
{
int i;
struct TASKLEVEL *tl = &taskctl->level[task->level];
//寻找task的位置
for(i = 0; i < tl->running; i++){
if(tl->tasks[i] == task){
break;//找到了
}
}
tl->running--;
if(i < tl->now){
tl->now--;//需要移动成员,因此做对应的处理
}
if(tl->now >= tl->running){
tl->now = 0; //now出现异常,进行修正
}
task->flags = 1;//休眠中
//移动
for(; i < tl->running; i++){
tl->tasks[i] = tl->tasks[i + 1];
}
return;
}
//决定切换到哪一个level中
void task_switchsub(void)
{
int i;
//寻找最上层的leve
for(i = 0; i < MAX_TASKLEVELS; i++){
if(taskctl->level[i].running > 0)
{
break;//找到了
}
}
taskctl->now_lv = i;
taskctl->lv_change = 0;
return;
}
然后,继续修改一下前面的task任务部分:
//返回一个内存地址,将当前运行的调用该函数的程序作为一个任务
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);
}
for (i = 0; i < MAX_TASKLEVELS; i++) {
taskctl->level[i].running = 0;
taskctl->level[i].now = 0;
}
task = task_alloc();//接收到akkoc返回的第一个任务
task->flags = 2;//活动中的标志
task->priority = 2;//0.02s
task->level = 0; //先将当前调用程序的任务暂时设置为0,即最高
task_add(task);
task_switchsub();//level 设置
load_tr(task->sel);
task_timer = timer_alloc();
timer_settime(task_timer, task->priority);
return task;
}
//将活动的task添加到tasks的末尾
void task_run(struct TASK *task, int level, int priority)
{
if(level < 0){
level = task->level; //不改变level
}
if(priority > 0){
task->priority = priority;
}
if(task->flags == 2 && task->level != level){//改变活动区中的level
task_remove(task);//这里执行后flag会变为1,因此下面也会执行的
}
if(task->flags != 2){
//从休眠下唤醒的情况
task->level = level;
task_add(task);
}
taskctl->lv_change = 1 ;//下次切换任务时检查level
return;
}
void task_switch(void)
{
struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
struct TASK *new_task, *now_task = tl->tasks[tl->now];
tl->now++;
if (tl->now == tl->running) {
tl->now = 0;
}
if (taskctl->lv_change != 0) {
task_switchsub();
tl = &taskctl->level[taskctl->now_lv];
}
new_task = tl->tasks[tl->now];
timer_settime(task_timer, new_task->priority);
if (new_task != now_task) {
farjmp(0, new_task->sel);
}
return;
}
void task_sleep(struct TASK *task)
{
struct TASK *now_task;
if(task->flags == 2){
//如果处于活动中
now_task = task_now();
task_remove(task); //执行此语句flags变为1
if(task == now_task){
//如果是让自己休眠,则需要进行任务切换
task_switchsub();
now_task = task_now();//在设定完成后取得当前值
farjmp(0, now_task->sel);
}
}
return;
}
然后,我们修改一下fifo的放数据的函数:
int fifo32_put(struct FIFO32 *fifo, int data)
//向fifo传递数据并保存
{
if(fifo->task != 0){
if(fifo->task->flags != 2 ){//如果任务处于休眠状态
task_run(fifo->task, -1, 0);//唤醒任务
}
}
然后继续,进一步的修改主函数:
task_run(task_a, 1, 0);
略
/* sht_win_b */
for (i = 0; i < 3; i++) {
task_run(task_b[i], 2, i + 1);
}
然后,我们运行一下,可以放下若不动鼠标键盘的话,其他三个计数更快;若动力鼠标或者键盘则计数会慢(因为我们给鼠标数据这个任务的优先级较高):
不动鼠标键盘:
发生鼠标键盘的中断后的:
可以看出计数确实有明显的差异!
总结
至此,我们多任务的基础部分已经算大致完成了!!感觉越来越有范了,嘿嘿嘿。加油加油!