【 *** 作系统】30天自制 *** 作系统--(15)多任务2

【 *** 作系统】30天自制 *** 作系统--(15)多任务2,第1张

        本章主要针对多任务的通用性处理、任务休眠 *** 作以及任务优先级处理进行一些改进。

一 多任务的通用性处理

        回顾一下前一章的任务切换处理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。这样的话就不会出现上面的异常状态了。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/990566.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存