????????本章主要针对多任务的通用性处理、任务休眠操作以及任务优先级处理进行一些改进。
一 多任务的通用性处理
? ? ? ? 回顾一下前一章的任务切换处理mt_taskswitch(参考【操作系统】30天自制操作系统--(14)多任务1):
? ? ? ? 两个任务这么处理可以,但是当任务多达几十上百个之后,这么处理就不行了。所以这边需要一个TASKCTL的任务管理结构来存放所有的子任务:
#define MAX_TASKS 1000
#define TASK_GDT0 3
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 {
// sel 存放 GDT 的编号 selector
int sel, flags;
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);
? ? ? ? 基于这个任务管理结构,可以优化一下现在的任务初始化、运行切换步骤:
【1】任务初始化task_init() :向 timer.c 注册定时器,计时器每隔 0.02s 执行一次 task_switch()
struct TASK *task_init(struct MEMMAN *memman){
struct TASK *task;
struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));
int i;
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();
// 2 是活动中的标志
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;
}
【2】任务分配task_alloc() :从 taskctl 处获取一个空闲的任务
struct TASK *task_alloc(void) {
struct TASK *task;
int i;
for (i = 0; i < MAX_TASKS; i++) {
if (taskctl->tasks0[i].flags == 0) {
task = &taskctl->tasks0[i];
// 正在使用的标志
task->flags = 1;
task->tss.eflags = 0x00000202;
task->tss.eax = 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;
}
【3】任务运行task_run() : 将指定任务加入执行列表中,增加运行时任务的数量
void task_run(struct TASK *task) {
task->flags = 2;
taskctl->tasks[taskctl->running++] = task;
return;
}
【4】任务切换task_switch() :重置计时器,跳转到下一个任务进行运行
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 0;
}
? ? ? ? 至此,多任务的通用性处理已经完成,主函数中的调用依照上面的步骤即可。
二 任务休眠操作
? ? ? ? 设定任务休眠,只有当满足默某些条件时(比如缓存数组中有数据了),再切换过来执行,这样就可以把CPU的处理能力更为合理的分配。例如上一章中的A(键盘鼠标响应任务)、B(计数显示任务),正常时候,当B再全力以赴干活的时候,A是无需操作的,只有当有鼠标键盘响应时,才需要进行操作。那么与其让A闲着没事干,还不如把这些时间分出来给繁忙的B,只有当缓存数组中有数据了(有鼠标键盘的操作了),在将A拉起来执行。
? ? ? ? 任务休眠函数,需要注意区分一下(1)任务A让任务A休眠(休眠完之后马上切换到下一个任务)、(2)任务B让任务A休眠(从管理表中移除即可)这两种情况:
void task_sleep(struct TASK *task)
{
int i;
char ts = 0;
if (task->flags == 2) { /* 如果指定任务处于唤醒状态 */
if (task == taskctl->tasks[taskctl->now]) {
ts = 1; /* 让自己休眠的话,稍后需要进行任务切换 */
}
/* 寻找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;
}
? ? ? ? 当然,除了休眠的操作,还需要在缓存区中有数据时唤醒它:
struct FIFO32 {
int *buf;
int p, q, size, free, flags;
struct TASK *task;
};
int fifo32_put(struct FIFO32 *fifo, int data){
if (fifo->free == 0) {
fifo->flags |= FLAGS_OVERRUN;
return -1;
}
fifo->buf[fifo->p++] = data;
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;
}
? ? ? ? 这样,就可以现在任务休眠的效果了:
void HariMain(void)
{
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 (fifo32_status(&fifo) == 0) {
task_sleep(task_a); //开始休眠
// 后开启 io_sti(), 防止休眠处理的时候发生中断请求
io_sti();
} else {
//(中略)
}
}
}
void task_b_main(struct SHEET *sht_back)
{
//(中略)
fifo32_init(&fifo, 128, fifobuf, 0);
//(中略)
}
三 任务优先级处理
? ? ? ? 光有任务休眠还不够,当任务比较多的时候,将CPU的处理时间平均分配给所有的子任务就显得不合理了,这个时候就需要给各个任务设定优先级,来决定哪个任务运行时间需要长一点,哪个任务运行时间小一点。
? ? ? ? 设置优先级的本质是控制不同任务的超时时间。优先级高等于是超时时间长,相对于其他任务运行时间就长。
????????
struct TASK {
// sel 存放 GDT 的编号
int sel, flags;
int priority; //新增优先级的标志位
struct TSS32 tss;
};
/* task_init() */
task->priority = 2;
timer_settime(task_timer, task->priority);
/* task_run() */
void task_run(struct TASK *task, int priority) {
// 当 priority 设置为 0 时不改变优先级
if (priority > 0) {
task->priority = priority;
}
if (task->flags != 2) {
task->flags = 2;
taskctl->tasks[taskctl->running++] = task;
}
return;
}
/* task_switch */
void task_switch(void) {
// 使用 priority 设置定时器的值
struct TASK *task;
taskctl->now++;
if (taskctl->now == taskctl->running) {
taskctl->now = 0;
}
task = taskctl->tasks[taskctl->now];
timer_settime(task_timer, task->priority);
if (taskctl->running >= 2) {
farjmp(0, task->sel);
}
return;
}
????????改写 fifo.c 在任务唤醒的时候不改变其优先级:
if (fifo->task != 0) {
if (fifo->task->flags != 2) {
task_run(fifo->task, 0);
}
}
? ? ? ? 主函数中调用:
void HariMain(void)
{
//(中略)
/* sht_win_b */
for (i = 0; i < 3; i++) {
//(中略)
task_run(task_b[i], i + 1);
}
//(中略)
}
? ? ? ? 综上,创建了三个任务B0,B1,B2,优先级分别是1,2,3,运行结果如下(计数值差不多也是呈现1:2:3的关系):
四 任务优先级的优化
????????在操作系统中有一些处理,即使牺牲其他任务的性能也必须要尽快完成,比如键盘和鼠标,对于这种任务,我们需要设置较高的优先级。?但是,如果碰巧两个急迫的任务同时发生,一定会有一个得不到完美的执行。
????????我们需要设计一种结构,使即便是优先级高的任务同时运行,也能区分哪个任务优先。我们采用常见的“加一层" 的方法,在控制任务的结构体中加入struct TASKCTL用来区分任务的优先级,只有当优先级更高的任务执行完成之后优先级低的任务才会被执行。
? ? ? ? ?上面架构的原理是,最上层LEVEL 0中只要存在哪怕一个任务,则完全忽略LEVEL 1和LEVEL 2中的任务,只在LEVEL 0的任务中进行任务切换。当LEVEL 0中没有任务时(全部休眠或者全部降到下层的LEVEL 1中),接下来轮到LEVEL 1中的任务进行切换。以此类推,当LEVEL 0和LEVEL 1中都没有任务时,那就轮到LEVEL 2开始执行。
? ? ? ? 这边只给出这种优化策略的思想,具体的实现不表了。
五 “哨兵”任务
? ? ? ? 考虑这么一个情况,如果只存在任务A,而且正常情况下,没有键盘或者鼠标的缓存输入,任务A处于休眠的状态,这个时候,程序也没有其他任务执行了,就会出现异常状态。
? ? ? ? 解决办法类似于之前提到的定时器“哨兵”机制(【操作系统】30天自制操作系统--(12)定时器2),在这边在最下层加入一个哨兵任务,其中不做任何事情,只会执行HLT。这样的话就不会出现上面的异常状态了。
|