操作系统|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——
文章图片
成功啦,数字显示的速度也很快。而当我们用键盘输入数据,鼠标不断拖动窗口的时候,数字增加的速度会降下来。
增加窗口数量 我们来增加更多的任务,分别为任务A、任务B0、任务B1、任务B2,并且让每个任务都拥有各自的窗口。
我们需要对bootpack.c里的内容进行修改,增加的代码量较大,这里就不粘贴过来了,感兴趣的同学可以查看一下harib13c文件夹里的bootpack.c文件的内容,代码也比较好理解。
在make_window8中增加了一个变量act,当act为1时,颜色不变,当act为0时窗口的标题栏会变成灰色。
在task_b_main中,删除了每0.01秒显示一次的内容,保留每1秒显示速度的功能。
文章图片
四个窗口在同时工作,任务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一下——
文章图片
我们将任务B0、任务B1、任务B2的优先级分别设为1、2、3,因此速度顺序应该为B2>B2>B0,从运行结果看也是这样。
移动鼠标时发现,鼠标移动的速度有点慢。解决这个问题也很简单,把移动鼠标的任务优先级调高就可以了。
这里我们思考一个问题,优先级确实是个好东西,有些任务确实是需要尽快处理。但是这类需要尽快处理的任务可不止鼠标移动一个,比如键盘输入、音乐播放等等。那么把这些任务优先级都设置为10,同时运行的话可能会出现问题。
我们设计一种结构,使得多个高优先级的任务同时运行,也能区分哪个更优先。
文章图片
这种结构的工作原理,就是在优先级之外又加了一层LEVEL。最上层的LEVEL0只要存在一个任务,下面的任务都不切换,只在LEVEL0内部切换。当LEVEL0任务全部休眠,就降到下一次LEVEL。所以我们可以把鼠标移动、键盘输入和音乐播放放在不同的LEVEL中,就不会产生混乱了。
具体的程序在这里就不演示了,今天就到这吧。
https://gitee.com/mint1993/myos.git
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- 你的口红,真的很美!
- 30天健身笔记(45)
- 【V课会】第3季-30天小学思维导图实战营
- 苹果手机如何利用库乐队自制铃声
- 2021-05-06节俭生活第30天,活得不如一条狗通透
- 用了30天整理的一些GO语言学习资料,2019请你加油
- 小程序|【自制壁纸生成器】2022新年壁纸领取,换一张手机壁纸,迎接2022叭~
- 【都市】生日|【都市】生日 [01] 【87】 一鸣30天中篇小说挑战营第二期
- 自制海苔芝麻--孩子拌饭吃