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

任务休眠

上一篇我们实现的多任务,是为每个任务分配相同的时间。实际情况可能是A任务一直闲着没事干,B任务就一直拼命干活,这种分配显然是不合理的。

那么如何避免这种浪费呢?

我们可以把这样闲着没事干的任务,从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

  • 98
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 28
    评论
评论 28
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值