操作系统|30天自制操作系统——第十六天实现多任务(二)

任务休眠 上一篇我们实现的多任务,是为每个任务分配相同的时间。实际情况可能是A任务一直闲着没事干,B任务就一直拼命干活,这种分配显然是不合理的。
那么如何避免这种浪费呢?
【操作系统|30天自制操作系统——第十六天实现多任务(二)】我们可以把这样闲着没事干的任务,从tasks里删除,这种做法在多任务里叫作“休眠”(sleep)。当FIFO有数据过来时,我们再将A任务唤醒。(这里大家可以类比线程状态及状态切换,思路是相同的。)
首先来创建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; /* 让自己休眠的话,稍后需要进行任务切换 */ } /* 寻找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; }

当一个任务让另一个任务休眠时,只需要在task中搜索该任务,找到后用后面的成员填充过来。
当任务自己让自己休眠时,因为是令当前的线程休眠,处理完成后需要立即切换到下一个任务。
当FIFO写入数据时,我们需要将任务唤醒。在FIFO结构体中,添加唤醒任务的成员信息:
bootpack.h节选:
struct FIFO32 { int *buf; int p, q, size, free, flags; struct TASK *task; };

之后我们改写fifo32_init,它可以在参数中指定一个任务。在fifo32中实现向FIFO写入数据时,唤醒某个任务的功能。
如果某个任务已经处于唤醒状态,再对其进行唤醒会造成任务重复注册,是不行的。因此我们需要先对任务进行判断,若任务处于休眠状态,再对其进行唤醒。
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); /* 将任务唤醒 */ } } return 0; }

最后改写一下HariMain函数,我们来试试看,make run——
操作系统|30天自制操作系统——第十六天实现多任务(二)
文章图片

成功啦,数字显示的速度也很快。而当我们用键盘输入数据,鼠标不断拖动窗口的时候,数字增加的速度会降下来。
增加窗口数量 我们来增加更多的任务,分别为任务A、任务B0、任务B1、任务B2,并且让每个任务都拥有各自的窗口。
我们需要对bootpack.c里的内容进行修改,增加的代码量较大,这里就不粘贴过来了,感兴趣的同学可以查看一下harib13c文件夹里的bootpack.c文件的内容,代码也比较好理解。
在make_window8中增加了一个变量act,当act为1时,颜色不变,当act为0时窗口的标题栏会变成灰色。
在task_b_main中,删除了每0.01秒显示一次的内容,保留每1秒显示速度的功能。
操作系统|30天自制操作系统——第十六天实现多任务(二)
文章图片

四个窗口在同时工作,任务B0、任务B1、任务B2基本上是以同样的速度在计数,模拟器上它们的速度会有差异,在真机环境中还是十分接近的。
任务优先级 现在任务B0~B2都是以同样的速度运行,但是在很多情况下,某些任务相比较其他任务更加重要或更加不重要,需要提升或者降低某个任务的优先级。
之前任务的切换间隔都为0.02s,现在我们将它修改为每个任务在0.01s~0.1s的范围内设定不同的任务间隔,如此我们就能实现最大10倍的优先级差异。
bootpack.h节选:
struct TASK { int sel, flags; /* sel代表GDT编号 */ int priority; /* 优先级 */ struct TSS32 tss; };

为了使用上面的结构,需要改写mtask.c文件,这里给最开始的任务设定了0.02s的初始值。
mtask.c节选:
struct TASK *task_init(struct MEMMAN *memman) { (略) task = task_alloc(); task->flags = 2; /* 活动中标志 */ task->priority = 2; /* 0.02秒 */ taskctl->running = 1; taskctl->now = 0; taskctl->tasks[0] = task; load_tr(task->sel); task_timer = timer_alloc(); timer_settime(task_timer, task->priority); return task; }

在task_run中,我们让它可以通过参数来设定优先级。一开始先判断priority的值,等于0时表示不改变当前的优先级,这个判断是为了在唤醒正在休眠的任务时使用。即使任务正在运行,也能通过task_run改变任务的优先级。
mtask.c节选:
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; }

接着在task_switch中应用priority,在farjmp之前我们要判断任务数量是否是2个以上,如果就一个任务可没法执行切换呢。
mtask.c节选:
void task_switch(void) { 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中,将任务从休眠状态转换为唤醒状态需要调用task_run,唤醒后将优先级修改为0。
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); /* 将任务唤醒 */ } } return 0; }

最后改写一下HariMain里的相关内容,make run一下——
操作系统|30天自制操作系统——第十六天实现多任务(二)
文章图片

我们将任务B0、任务B1、任务B2的优先级分别设为1、2、3,因此速度顺序应该为B2>B2>B0,从运行结果看也是这样。
移动鼠标时发现,鼠标移动的速度有点慢。解决这个问题也很简单,把移动鼠标的任务优先级调高就可以了。
这里我们思考一个问题,优先级确实是个好东西,有些任务确实是需要尽快处理。但是这类需要尽快处理的任务可不止鼠标移动一个,比如键盘输入、音乐播放等等。那么把这些任务优先级都设置为10,同时运行的话可能会出现问题。
我们设计一种结构,使得多个高优先级的任务同时运行,也能区分哪个更优先。
操作系统|30天自制操作系统——第十六天实现多任务(二)
文章图片

这种结构的工作原理,就是在优先级之外又加了一层LEVEL。最上层的LEVEL0只要存在一个任务,下面的任务都不切换,只在LEVEL0内部切换。当LEVEL0任务全部休眠,就降到下一次LEVEL。所以我们可以把鼠标移动、键盘输入和音乐播放放在不同的LEVEL中,就不会产生混乱了。
具体的程序在这里就不演示了,今天就到这吧。
https://gitee.com/mint1993/myos.git

    推荐阅读