第16天:多任务(2)

16.1、任务管理自动化

#define MAX_TASKS		1000	
#define TASK_GDT0		3		/* TSS从三号段开始 */
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段选择符 */
	struct TSS32 tss;
};
struct TASKCTL {
	int running; /* 正在运行的任务数量(这样说严谨,应该是flags>=1的任务,创建了但没有占用CPU的) */
	int now; /* 正在运行的任务(flags=2) */
	struct TASK *tasks[MAX_TASKS];
	struct TASK tasks0[MAX_TASKS];
};

管理方式就跟之前的定时器管理差不多了。

接下来初始化一下这些管理变量:

// mtask.c
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;
	taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));
	for (i = 0; i < MAX_TASKS; i++) {
		taskctl->tasks0[i].flags = 0;
		taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;// 从3号段往下分配
		set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
	}
	//初始化时要分配一个任务出来,并让他运行,等切换到其他任务时,这个任务段就保存bootpack运行的环境
	task = task_alloc();
	task->flags = 2; /* 正在占用CPU运行 */
	taskctl->running = 1; // 正在运行的任务有一个(分配出去的任务)
	taskctl->now = 0; // 正在运行的任务是下标位0号的任务
	taskctl->tasks[0] = task; // 把任务放在索引为0的位置
	load_tr(task->sel); // tr指向三号段
	task_timer = timer_alloc();
	timer_settime(task_timer, 2);
	return task; // 返回这个任务
}

初始化函数中用到的task_alloc函数:

// mtask.c
struct TASK *task_alloc(void)
{
	int i;
	struct TASK *task;
	for (i = 0; i < MAX_TASKS; i++) {
		if (taskctl->tasks0[i].flags == 0) {
			task = &taskctl->tasks0[i];
			task->flags = 1; /* 分配出去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; /* 执行到这说明没有可以分配的了 */
}

在初始化的时候,第一个任务时运行起来的,那后面分配的任务怎么运行起来呢?

// mtask.c

// 让任务成为等待占用CPU的就绪状态
void task_run(struct TASK *task)
{
	task->flags = 2; /* 设置为2 */
	taskctl->tasks[taskctl->running] = task; // 把该任务添加到任务数据的结尾
	taskctl->running++;
	return;
}

// 就绪状态的任务,等到定时器中断后,就可以切换后执行
// 所以说task_switch就是让时钟中断函数调用的
void task_switch(void)
{
	timer_settime(task_timer, 2);
	if (taskctl->running >= 2) {// 有两个以上任务才能切换,如果只有初始化函数中的一个任务将不会切换
		taskctl->now++; // 切换到下一个任务前,now先加一
		if (taskctl->now == taskctl->running) {// 到任务队列末尾了,再从头切换
			taskctl->now = 0;
		}
		farjmp(0, taskctl->tasks[taskctl->now]->sel);// 跳到now指定任务段,切换任务执行
	}
	return;
}

HariMain函数:

void HariMain(void){
	struct TASK *task_b;

	task_init(memman); //初始化任务管理空间(给任务管理结构体分配内存,指定每个任务运行的段(TSS))
	task_b = task_alloc(); // 分配一个可用的任务段
	task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
	task_b->tss.eip = (int) &task_b_main;
	task_b->tss.es = 1 * 8;
	task_b->tss.cs = 2 * 8;
	task_b->tss.ss = 1 * 8;
	task_b->tss.ds = 1 * 8;
	task_b->tss.fs = 1 * 8;
	task_b->tss.gs = 1 * 8;
	*((int *) (task_b->tss.esp + 4)) = (int) sht_back;
	task_run(task_b);
}

16.2、让任务休眠

当有些任务根本没啥东西可以执行,还每次把CPU让给它,就太浪费了,那就让他休眠了吧。

那问题就是怎么休眠呢?把它从运行的(flags=2)任务队列删除就好了啊。
那以后怎么唤醒呢?使用task_run函数啊,这个函数会把这个任务设置为占用CPU的就绪状态,并把该任务添加到任务列表的结尾,等待任务切换。

看代码:

void task_sleep(struct TASK *task)
{
	int i;
	char ts = 0;
	if (task->flags == 2) {/* 如果任务正在占用CPU(flags=2),或者占用CPU的就绪状态(flags=2) ,这两个状态才会要求休眠(flags=1)*/
		if (task == taskctl->tasks[taskctl->now]) {
			ts = 1; /* 和now指向的任务相同,说明该任务正在占用CPU,要把这个任务休眠,必须让ts=1,稍后进行任务的切换 */
		}
		/* 找出要休眠任务的数组下标 */
		for (i = 0; i < taskctl->running; i++) {
			if (taskctl->tasks[i] == task) {
				/* 找到了 */
				break;
			}
		}
		taskctl->running--; // 正在运行的任务减一
		if (i < taskctl->now) {
			taskctl->now--; /*如果这个任务排在正在占用CPU任务的前面,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;
}

解释一下为什么要修正now的值:
now修正
如果不修正now,now就下标越界了,下标为9的任务已经休眠,按顺序应该切换到下标为0 的任务了。

睡眠了怎么唤醒呢?
答:在fifo32中添加一个task变量,指示缓冲区有数据时要唤醒这个任务。

struct FIFO32 {
	int *buf;
	int p, q, size, free, flags;
	struct TASK *task; // 缓冲区有数据要唤醒的任务
};

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)
{
	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:

void HariMain(void){
	struct TASK *task_a, *task_b;
	// -
	fifo32_init(&fifo, 128, fifobuf, 0);// task先设置为0,因为任务还没创建
	// -
	task_a = task_init(memman);// 任务分配内存。(task_init初始化任务就会创建一个task,用来保存bootpack环境)
	fifo.task = task_a; // 刚刚的0在这设置为task_a,设置为以后要唤醒的任务
	// -
	for (;;) {
		io_cli();
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a); // 让任务task_a睡眠(就是当前任务),等缓冲区有数据再被唤醒
			io_sti();// 开启中断。如果先开启中断再睡眠的话,中断已开启的瞬间缓冲区写入了数据,任务task_a却休眠了如果是task_a要处理的数据就不好了。
		} else {
		// -
		}
	}
}

void task_b_main(struct SHEET *sht_back)
{
	// -
	fifo32_init(&fifo, 128, fifobuf, 0);// 任务B不睡眠,所以设置为0
	// -
}

16.3、增加窗口数量

16.4、设定任务优先级

16.4.1、基于时间的优先级

其实优先级就是任务执行时间,时间越长代表优先级越高:

struct TASK {
	int sel, flags;
	int priority;// 优先级
	struct TSS32 tss;
};

任务初始化时:

struct TASK *task_init(struct MEMMAN *memman)
{
	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->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();
	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;
}


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)
{
	struct TASK *task;
	taskctl->now++;
	if (taskctl->now == taskctl->running) {
		taskctl->now = 0;
	}
	task = taskctl->tasks[taskctl->now];
	timer_settime(task_timer, task->priority); // 优先级来设定定时器,之前都是0.02秒的定时器
	if (taskctl->running >= 2) {
		farjmp(0, task->sel);
	}
	return;
}

唤醒任务代码也要改:

int fifo32_put(struct FIFO32 *fifo, int data)
{
	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, 0); /* 任务唤醒,并不改边优先级,task_run中if要求优先级大于0 */
		}
	}
	return 0;
}

16.4.1、设定优先层级

设定level,每个level都有很多任务,不同任务的优先级(占用CPU时间)不同,每个level也有不同优先级。

#define MAX_TASKS 1000
#define MAX_TASKS_LV    100
#define MAX_TASKLEVELS  10
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]; // 每个level也有100个task
};
struct TASKCTL {
	int now_lv; /*现在活动中的LEVEL */
	char lv_change; /*在下次任务切换时是否需要改变LEVEL */
	struct TASKLEVEL level[MAX_TASKLEVELS]; // 一共有10个level,level 0优先级最高
	struct TASK tasks0[MAX_TASKS];
};

对task的操作:

struct TASKCTL *taskctl;
struct TIMER *task_timer;

// 获取正在运行的任务地址
struct TASK *task_now(void)
{
	struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];// 正在运行的level
	return tl->tasks[tl->now]; // level中正在运行的task
}

// 把task加入到它所在level的末尾,并把task唤醒
void task_add(struct TASK *task)
{
	struct TASKLEVEL *tl = &taskctl->level[task->level];// 找到task所在的level
	tl->tasks[tl->running] = task; // 插入到末尾
	tl->running++;
	task->flags = 2; /*就绪,等待CPU调用*/
	return;
}

// 休眠(只关注任务本身,不关注level)
void task_remove(struct TASK *task)
{
	int i;
	struct TASKLEVEL *tl = &taskctl->level[task->level];// 找到task所在的level

	/*寻找task所在的位置*/
	for (i = 0; i < tl->running; i++) {
		if (tl->tasks[i] == task) {
			/*在这里 */
			break;
		}
	}

	tl->running--;
	if (i < tl->now) {
		tl->now--; /*需要移动成员,要相应地处理 */
	}
	if (tl->now >= tl->running) {
		/*如果now的值出现异常,则进行修正*/
		tl->now = 0;
	}
	task->flags = 1; /* 休眠中 */

	/* 移动 */
	for (; i < tl->running; i++) {
		tl->tasks[i] = tl->tasks[i + 1];
	}
	return;
}

// 找出正在运行的最上层的level
void task_switchsub(void)
{
	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;
}

基本操作函数完成了,后面开始初始化:

// 初始化所有1000个task,每个level分100个task,初始化分配的第一个task放在level 0
struct TASK *task_init(struct MEMMAN *memman)
{
	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->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);
	}
	for (i = 0; i < MAX_TASKLEVELS; i++) {
		taskctl->level[i].running = 0;
		taskctl->level[i].now = 0;
	}
	task = task_alloc();
	task->flags = 2; /*活动中标志*/
	task->priority = 2; /* 0.02秒*/
	task->level = 0; /*最高LEVEL */
	task_add(task);
	task_switchsub(); /* LEVEL 设置*/
	load_tr(task->sel);
	task_timer = timer_alloc();
	timer_settime(task_timer, task->priority);
	return task;
}

// 修改task的level和优先级(占用CPU时间)
void task_run(struct TASK *task, int level, int priority)
{
	if (level < 0) {
		level = task->level; /*不改变LEVEL */
	}
	if (priority > 0) {
		task->priority = priority;
	}
	// 如果要修改的task正在运行或者就绪状态,并且要修改的level不和它之前的相同,要先让它休眠(从之前level的任务队列中删除)
	if (task->flags == 2 && task->level != level) { 
		/*改变活动中的LEVEL */
		task_remove(task); /*这里执行之后flag的值会变为1,于是下面的if语句块也会被执行*/
	}
	// 在这里再唤醒。设置新的level,并添加到对应的level的任务队列里
	if (task->flags != 2) {
		/*从休眠状态唤醒的情形*/
		task->level = level;
		task_add(task);
	}
	taskctl->lv_change = 1; /*下次任务切换时检查LEVEL,因为任务唤醒之后,可能本来没有任务的level,插入了新的task */
	return;
}

// 睡眠(关注level)
void task_sleep(struct TASK *task)
{
	struct TASK *now_task;
	if (task->flags == 2) {
		/*如果处于活动状态*/
		now_task = task_now(); // 查询现在正在运行的task
		task_remove(task); /*执行此语句的话flags将变为1,先把task给休眠了 */
		if (task == now_task) {// 休眠的task如果是正在运行的task,要进行任务切换
			task_switchsub(); // 找出不为空的level
			now_task = task_now(); /* 找出这个level中now指向的task */
			farjmp(0, now_task->sel);// 切换到这个task
		}
	}
	return;
}

目前位置,会去休眠的就是任务A(就是bootpack),当缓冲区没有数据就休眠,等到有数据就唤醒去处理数据,是键盘还是鼠标数据等等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值