harib13a:
今天我们要继续折腾多任务,任务的高效管理是操作系统的一个重要的任务。在今天,我们将为系统创建更加完善的任务管理系统,其中包括优先级,任务等级等。
1、任务管理结构体
#define MAX_TASKS 1000 /* 最大任务数量 */ #define TASK_GDT0 3 /* 任务块在GDT中的初始位置:从GDT的3号段开始 */ struct TSS32 {//任务状态段,这个在前面已经提到过,这里再介绍一下 // 26个int成员,104字节 int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3; //与任务设置相关的信息(任务切换时,除backlink,都不会被写入) int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;//32位寄存器;eip任务返回时,找到返回的地址 int es, cs, ss, ds, fs, gs;//16位寄存器 int ldtr, iomap; //有关任务设置的信息。任务切换时CPU不写;ldtr = 0; iomap = 0x4000_0000 }; struct TASK {//任务结构体 //sel用来存储GDT中的编号 //flag表示任务的状态 //tss是任务状态段 int sel, flags; struct TSS32 tss; }; struct TASKCTL { //任务管理结构体 int running; /* 表示正在运行中的任务数量 */ int now; /* 记录当前正在运行的是哪一个任务 */ struct TASK *tasks[MAX_TASKS];//记录正在运行中的任务的地址 struct TASK tasks0[MAX_TASKS];//1000个任务 };
2、初始化任务管理结构体
//初始化TASKCTL;初始化一个TASK为flag=2(正在运行) //返回:返回正在运行这个任务的地址 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; //初始化任务段描述的GDT taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));//分配任务管理结构体 for (i = 0; i < MAX_TASKS; i++) { taskctl->tasks0[i].flags = 0; //所有任务的flag置0 (未使用) taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;//任务在GDT中的编号 set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);//1000个任务按照编号写入GDT中 } task = task_alloc(); //临时任务分配 task->flags = 2; /* 临时任务为正在运行中flag=2 */ taskctl->running = 1; //正在运行中任务数为1 taskctl->now = 0; //当前运行任务编号为0(数组从0开始的) taskctl->tasks[0] = task; //赋值给TASKCTK的第一个任务 load_tr(task->sel); //加载到TR寄存器中,让该任务运行 task_timer = timer_alloc();//定时器设置 timer_settime(task_timer, 2); return task; }
3、创建初始化一个任务结构的函数
/* 这个函数做的事,就是把TASKCTL中的任务数组task0[] 的下一个没有使用的任务的任务状态段赋值,初始化,并置flag=1正在使用*/ struct TASK *task_alloc(void) { int i; struct TASK *task;//临时的任务结构体 for (i = 0; i < MAX_TASKS; i++) { //按顺序查找taskctl中的所有任务 if (taskctl->tasks0[i].flags == 0) {//找到第一个没有被分配的任务 task = &taskctl->tasks0[i]; //赋值给临时变量task 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; /* 初始化成功 */ }
4、任务运行函数task_run
//这个函数做的事:将参数task的flag=2运行中,再将改任务的地址放到TASKCTL的tasks[]中 void task_run(struct TASK *task) { task->flags = 2; /* task的flag=2 正在运行 */ taskctl->tasks[taskctl->running] = task; //task的地址放到ctl的tasks[]中 taskctl->running++; //运行中任务计数+1 return; }
5、最后,任务切换task_switch()
//将now指向的运行中的任务向后移动一个 void task_switch(void) { timer_settime(task_timer, 2);//任务切换的定时器 if (taskctl->running >= 2) { //运行中的任务数>2 taskctl->now++; //now指向下一个运行中的任务 if (taskctl->now == taskctl->running) { taskctl->now = 0; //到达了尾部,又回到开始 } farjmp(0, taskctl->tasks[taskctl->now]->sel);//JMP到now指向的新的任务的GDT } return; }
harib13b:
前面我们已经完成了任务管理系统的任务初始化,创建等工作。下面我们继续完善,添加任务删除(休眠)操作task_sleep.
//mtask.c void task_sleep(struct TASK *task) { int i; char ts = 0; if (task->flags == 2) { /* 如果指定的任务处于唤醒状态 */ if (task == taskctl->tasks[taskctl->now]) {//要休眠的是正在运行的任务 ts = 1; /* 让自己休眠,稍后需要进行任务切换 */ } for (i = 0; i < taskctl->running; i++) {/*寻找task所在的位置 */ if (taskctl->tasks[i] == task) { /* 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; } //跳到GDT相应的段号。 farjmp(0, taskctl->tasks[taskctl->now]->sel); } } return; }
接下来是FIFO中写入数据时将任务唤醒的功能,添加用于记录唤醒任务的信息成员。
注 意:这里是有数据写入的时候就唤醒任务。
//bootpack.h struct FIFO32 { int *buf; int p, q, size, free, flags; struct TASK *task;//写入数据时,需要唤醒的任务。 }; //fifo.c 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; /* 写入数据是需要唤醒的任务 */ 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); /* task_run()唤醒 */ } } return 0; }
harib13c:
增加窗口的数量,增加了B0、B1、B2三个任务窗口,它们的设定相同。
void HariMain(void) { //.... unsigned char *buf_back, buf_mouse[256], *buf_win, *buf_win_b; struct SHEET *sht_back, *sht_mouse, *sht_win, *sht_win_b[3]; struct TASK *task_a, *task_b[3]; //.... /* sht_win_b */ for (i = 0; i < 3; i++) { sht_win_b[i] = sheet_alloc(shtctl); buf_win_b = (unsigned char *) memman_alloc_4k(memman, 144 * 52); sheet_setbuf(sht_win_b[i], buf_win_b, 144, 52, -1); sprintf(s, "task_b%d", i); make_window8(buf_win_b, 144, 52, s, 0); task_b[i] = task_alloc(); task_b[i]->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8; task_b[i]->tss.eip = (int) &task_b_main; task_b[i]->tss.es = 1 * 8; task_b[i]->tss.cs = 2 * 8; task_b[i]->tss.ss = 1 * 8; task_b[i]->tss.ds = 1 * 8; task_b[i]->tss.fs = 1 * 8; task_b[i]->tss.gs = 1 * 8; *((int *) (task_b[i]->tss.esp + 4)) = (int) sht_win_b[i]; task_run(task_b[i]); } //.... }
harib13d:
到这里,任务是以相同的速度运行的,任务切换时间都为0.02s,下面我们通过设定不同的任务切换间隔来设定任务的优先级。
1、修改任务结构体TASK
//bootpack.h struct TASK { int sel, flags; /* sel偼GDT偺斣崋偺偙偲 */ int priority; //这里的优先级实际上是设定了任务切换的时间。 struct TSS32 tss; };
2、任务的相应的函数mtask.c
/* mtask.c节选 */ #include "bootpack.h" struct TASKCTL *taskctl; struct TIMER *task_timer; struct TASK *task_init(struct MEMMAN *memman) { //... task->priority = 2; /* 0.02s */ //... task_timer = timer_alloc();//设定任务切换的时间的定时器 timer_settime(task_timer, task->priority); return task; } void task_run(struct TASK *task, int priority) { if (priority > 0) { //初始化任务的时候,加入了设定优先级的参数。 task->priority = priority; } if (task->flags != 2) { task->flags = 2; /* 摦嶌拞儅乕僋 */ taskctl->tasks[taskctl->running] = task; taskctl->running++; } return; } void task_switch(void) { //..... //定时器时间超时才进行任务的切换 timer_settime(task_timer, task->priority); if (taskctl->running >= 2) { farjmp(0, task->sel); } return; }
3、改写fifo.c中的任务唤醒
int fifo32_put(struct FIFO32 *fifo, int data) /* FIFO傊僨乕僞傪憲傝崬傫偱拁偊傞 */ { //..... fifo->free--; if (fifo->task != 0) { if (fifo->task->flags != 2) { /* 需要唤醒的任务处于非活跃状态 */ task_run(fifo->task, 0); /* 唤醒任务,定时器为0 */ } } return 0; }
harib13e:
我们继续来折腾优先级吧(看上图),我们将任务的优先级设计成上面的结构,最上层的LEVEL0中只要存在哪怕一个任务,则完全忽略LEVEL1和LEVEL2中的任务,只在LEVEL0中进行任务的切换。当LEVEL0中的人物全部休眠,或者全部降到下层的LEVEL,才轮到LEVEL1的任务进行切换。当LEVLE0和LEVEL1中都没有任务的时候才轮到LEVEL2出场。下面我们来看看具体的做法。
1、修改任务结构体的定义
//bootpack.h //对于每个LEVEL我们最多允许创建100个任务,一共10个LEVEL #define MAX_TASKS 1000 /* 任务总数 */ #define TASK_GDT0 3 /* TSS放到GDT的3号段 */ #define MAX_TASKS_LV 100 //任务指针 #define MAX_TASKLEVELS 10 //任务级别数量 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的编号*/ int level, priority; struct TSS32 tss; }; struct TASKLEVEL { int running; /*正在运行的任务的数量 */ int now; /* 记录当前运行的任务*/ struct TASK *tasks[MAX_TASKS_LV];//100个任务指针 }; struct TASKCTL { int now_lv; /* 当前活动中的LEVEL */ char lv_change; /* 标志:下次切换任务时,是否需要改变LEVEL */ struct TASKLEVEL level[MAX_TASKLEVELS];//LEVEL一共10级 struct TASK tasks0[MAX_TASKS]; //任务总数 };
2、我们来写几个用于操作struct TASKLEVEL的函数
//mtask.c struct TASK *task_now(void) { //用来返回现在活动中的TASK的地址 struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv]; return tl->tasks[tl->now]; } void task_add(struct TASK *task) { //向TASKLEVEL中添加一个任务 struct TASKLEVEL *tl = &taskctl->level[task->level];//当前任务的TASKLEVEL tl->tasks[tl->running] = task;//把任务task添加到task后面 tl->running++; //正在运行的数量+1 task->flags = 2; /* 活动中的标识 */ return; } void task_remove(struct TASK *task) { //从TASKLEVLE中删除一个任务 int i; struct TASKLEVEL *tl = &taskctl->level[task->level];//获取要删除task的TASKLEVEL /* task的位置 */ for (i = 0; i < tl->running; i++) { if (tl->tasks[i] == task) { /* 找到了task出去 */ break; } } tl->running--; if (i < tl->now) { tl->now--; /* 需要删除的task在now的前面 */ } if (tl->now >= tl->running) { /* now到了任务的最后一个,又跳到头部 */ tl->now = 0; } task->flags = 1; /* 任务标识=1 */ /* 都往前移动一个 */ for (; i < tl->running; i++) { tl->tasks[i] = tl->tasks[i + 1]; } return; } void task_switchsub(void) { //用来在任务切换时,决定接下来切换到那个LEVEL int i; /* 寻找最上层的LEVEL */ for (i = 0; i < MAX_TASKLEVELS; i++) { if (taskctl->level[i].running > 0) { break; /* 找到了 */ } } taskctl->now_lv = i; taskctl->lv_change = 0; return; }
3、改写任务TASK操作函数
struct TASK *task_init(struct MEMMAN *memman) { //初始化函数,开始只有LEVEL0中有任务 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中的每一个任务,并写到GDT相应的段号 taskctl->tasks0[i].flags = 0; taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;//GDT段号 set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32); } for (i = 0; i < MAX_TASKLEVELS; i++) { //初始化LEVEL taskctl->level[i].running = 0; taskctl->level[i].now = 0; } task = task_alloc();//在LEVLE0中初始化一个任务 task->flags = 2; /* 活动中 */ task->priority = 2; /* 0.02s */ task->level = 0; /* LEVEL的值 */ task_add(task); //添加到TASKLEVEL中 task_switchsub(); /* 初始化LEVLE */ load_tr(task->sel); //初始化TR寄存器 task_timer = timer_alloc();//定时器 timer_settime(task_timer, task->priority); return task; } void task_run(struct TASK *task, int level, int priority) { //在参数中指定LEVEL的值 if (level < 0) { level = task->level; /* 不改变LEVEL(不能为负) */ } if (priority > 0) { //优先级设置 task->priority = priority; } if (task->flags == 2 && task->level != level) { /* 改变活动中的LEVEL */ task_remove(task); /* 将TASK移除,flag=1执行下面的任务 */ } if (task->flags != 2) { /* 唤醒任务 */ task->level = level; task_add(task); //把任务添加到活动中 } taskctl->lv_change = 1; /* 下次任务切换时检查LEVEL */ return; } void task_sleep(struct TASK *task) { //调用task_remove简化代码 struct TASK *now_task; if (task->flags == 2) { /* 如果处于活动状态 */ now_task = task_now();//当前运行的任务地址 task_remove(task); /* 执行后FLAG=1 */ if (task == now_task) { /* 让自己休眠,需要进行切换 */ task_switchsub(); now_task = task_now(); /* 设定后,获取当前的任务 */ farjmp(0, now_task->sel);//任务切换,GDT号 } } return; }
4、最后修改fifo.c任务唤醒
int fifo32_put(struct FIFO32 *fifo, int data) /* FIFO接收到数据时,将任务唤醒 */ { //.... fifo->free--; if (fifo->task != 0) { //fifo中有需要唤醒的任务 if (fifo->task->flags != 2) { /* 唤醒到活跃状态 */ task_run(fifo->task, -1, 0); /* 修改了RUN的参数传递调用 */ } } return 0; }