自制操作系统日志——第十六天
今天我们再接再厉,继续进一步的完成我们的多任务机制。
文章目录
- 自制操作系统日志——第十六天
- 一、多任务自动管理化
- 二、任务休眠
- 设定任务优先级
- 总结
一、多任务自动管理化
在昨天我们的多任务都是通过自己手动添加,然后进行运行的,这样子当我们任务过多时,总不好再一个个给他们进行设置吧?
因此呢,我们效仿前面的定时器与图层的自动管理,来进行对多任务的管理:
首先添加并修改结构体部分:
/* 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 = 1task->tss.eax = 0;//先都设置为0task->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=0if (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; //现在活动中的levelchar lv_change;//任务切换时是否需要改变levelstruct 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;//寻找最上层的levefor(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.02stask->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){//改变活动区中的leveltask_remove(task);//这里执行后flag会变为1,因此下面也会执行的}if(task->flags != 2){//从休眠下唤醒的情况task->level = level;task_add(task);}taskctl->lv_change = 1 ;//下次切换任务时检查levelreturn;
}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变为1if(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);}
然后,我们运行一下,可以放下若不动鼠标键盘的话,其他三个计数更快;若动力鼠标或者键盘则计数会慢(因为我们给鼠标数据这个任务的优先级较高):
不动鼠标键盘:
发生鼠标键盘的中断后的:
可以看出计数确实有明显的差异!
总结
至此,我们多任务的基础部分已经算大致完成了!!感觉越来越有范了,嘿嘿嘿。加油加油!