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就下标越界了,下标为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),当缓冲区没有数据就休眠,等到有数据就唤醒去处理数据,是键盘还是鼠标数据等等。