Day16
今天继续多任务:自动管理、休眠、优先级。基本上实现了一个操作系统任务管理该有的功能。虽然今天代码量有点多,不过慢慢来看吧。
一些感想
因为代码有几个版本变化,所以这里就不总结了。
一个进程管理都应该有什么
- 定义好的结构体,用于存储各种管理信息。
- 定时切换功能,这个要操作系统自动切换,不需要程序员手动切换。
- 休眠功能,或者说把等待IO的进程扔到等待队列里
- 优先级,不同进程分到的时间片大小不一样
前两点是昨天+今天开头做的,后两点则是为了更好的用户体验做的。
任务休眠
当一个任务在等待IO的时候,可以先把它从正在运行的任务中删除,等到对应的IO中断来了,再唤醒(run)它
设定任务优先级
作者采用了这样的结构
将任务分为分到不同的层(LEVEL)中,上层会屏蔽下面的所有层(所以只在当层进行切换)。只有当层任务处理完,进入休眠之后,才会运行下层的任务。
自动改变任务的优先级/休眠
到目前为止,优先级都是开始设定好的,但是估计当以后比如可以使用API之后,对于IO之类的任务,应该可以自动让其休眠/唤醒吧。
不知道以后会不会有改动。
来看代码吧,因为觉得每一步都很有用,所以每次修改也都记录了下来,而不是直接放最终版本。
任务管理自动化
harib13a
其实做任务管理的时候,想到之前的图层管理、定时器管理,这样写也不会觉得新奇了。
首先是结构体定义:
#define MAX_TASKS 1000 // 最大任务数量
#define TASK_GDT0 3 // 定义从GDT的几号开始分配给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用来存放GDT的编号
struct TSS32 tss;
};
struct TASKCTL
{
int running; // 正在运行的任务数量
int now; // 这个变量用来记录当前正在运行的是哪个任务
struct TASK *tasks[MAX_TASKS];
struct TASK tasks0[MAX_TASKS];
};
初始化:
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;
set_segmdesc(gdt + TASK_GDT0 + i, 103, (int)&taskctl->tasks0[i].tss, AR_TSS32);
}
task = task_alloc();
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;
}
这里的初始化返回的地址,可以理解为当前运行的这个程序(调用初始化的程序)也被当作一个任务来管理了。
分配一个任务
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; // 使用中的标志
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; // 全部正在使用
}
使得任务处于活动状态
void task_run(struct TASK *task)
{
task->flags = 2; // 活动中标志
taskctl->tasks[taskctl->running] = task;
taskctl->running++;
return;
}
自动切换
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;
}
目前是一个轮换的切换
定义完成这些东西之后,就可以改写当初的主函数了
struct TASK *task_b;
...
task_init(memman);
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);
简单来说,初始化管理程序、分配一个任务并设置各种参数,放到运行队列里。
让任务休眠
harib13b
一个任务在等待IO的时候,就sleep它,当IO来了之后再run它
sleep:
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;
}
之后,要相应修改FIFO
struct FIFO32
{
int *buf;
int p, q, size, free, flags;
struct TASK *task;
};
设置了当IO来的时候,需要唤醒的任务
改写init
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;
}
改写put
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;
}
改写一下主函数
fifo32_init(&fifo, 128, fifobuf, 0);
...
task_a = task_init(memman);
fifo.task = task_a;
...
if(fifo32_status(&fifo) == 0)
{
task_sleep(task_a);
io_sti();
}
这样休眠功能也做好了。如果实际运行一下,当鼠标键盘不操作的时候,计数会变更快。
增加窗口数量
harib13c
额外添加了2个计数窗口的副本,这样也为之后设置优先级做铺垫。
虽然书上放了很长的代码,但是其实改动部分也只有相关窗口的初始化以及设定,所以就不放代码了。
设定任务优先级(1)
harib13d
在每个任务里都调整自己任务的时间间隔
改动一下TASK,增加priority
struct TASK
{
int sel, flags; // sel代表GDT编号
int priority;
struct TSS32 tss;
};
相应改动init
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;
}
其实就是默认把优先级设置为2
改动run
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;
}
switch:
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) // 当只有1个任务的时候,就不要切换
{
farjmp(0, task->sel);
}
return;
}
每次定时器超时时间与优先级对应。
改写从fifo唤醒的函数,注意不要改变优先级
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); // 将其唤醒,0代表不改变优先级
}
}
return 0;
}
然后在主函数设定三个窗口的优先级。已经可以正确设定了
实际运行的话,运行的时间比例大致与设定的优先级比例相同
设定任务优先级(2)
harib13e
尽管可以把需要的任务设定很高的优先级,但有时候造成卡顿的话,还是会很不高兴,所以可以考虑再分成不同级别。
#define MAX_TASKS 1000 // 最大任务数量
#define TASK_GDT0 3 // 定义从GDT的几号开始分配给TSS
#define MAX_TASKS_LV 100
#define MAX_TASKLEVELS 10
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用来存放GDT的编号
int level, priority;
struct TSS32 tss;
};
struct TASKLEVEL
{
int running; // 正在运行的任务数量
int now; // 这个变量用来记录当前正在运行的是哪个任务
struct TASK *tasks[MAX_TASKS_LV];
};
struct TASKCTL
{
int now_lv; // 现在活动中的LEVEL
char lv_change; // 在下次任务切换时是否需要改变LEVEL
struct TASKLEVEL level[MAX_TASKLEVELS];
struct TASK tasks0[MAX_TASKS];
};
这样的话,最终的taskctl和tasktimer只会有一个(用来管理多个LEVEL),作者于是定义成了全局变量
接下来是一些用来管理tasklevel的方法
返回当前执行的是哪个任务:
struct TASK *task_now(void)
{
struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
return tl->tasks[tl->now];
}
增加一个任务
void task_add(struct TASK *task)
{
struct TASKLEVEL *tl = &taskctl->level[task->level];
tl->tasks[tl->running] = task;
tl->running++;
task->flags = 2; // 活动中
return;
}
删除一个任务
void task_remove(struct TASK *task)
{
int i;
struct TASKLEVEL *tl = &taskctl->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;
}
然后要调整之前的函数
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;
}
开始的任务设置为0(最高)
修改run:
void task_run(struct TASK *task, int level, int priority)
{
if(level < 0)
{
level = task->level; // 不改变LEVEL
}
if(priority > 0)
{
task->priority = priority;
}
if(task->flags == 2 && task->level != level) // 改变活动中的LEVEL(先删除后增加)
{
task_remove(task); // 这里执行之后flag的值会变为1,于是下面的if语句块也会被执行
}
if(task->flags != 2)
{
// 从休眠状态唤醒的情形
task->level = level;
task_add(task);
}
taskctl->lv_change = 1; // 下次任务切换时检查LEVEL(有可能跑到更高级的LEVEL了)
return;
}
因为有了remove,所以更改一下sleep
void task_sleep(struct TASK *task)
{
struct TASK *now_task;
if(task->flags == 2)
{
// 如果处于活动状态
now_task = task_now();
task_remove(task); // 执行此语句的话flag将变为1(休眠状态)
if(task == now_task)
{
// 如果让自己休眠,则需要进行任务切换
task_switchsub();
now_task = task_now(); // 在设定后获取当前任务的值
farjmp(0, now_task->sel);
}
}
return;
}
更改switch:当存在lv_change的时候要重新检查
void task_switch(void)
{
struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
struct TASK *new_task, *now_task = tl->tasks[tl->now];
tl->now++;
if(tl->now == tl->running)
{
tl->now = 0;
}
if(taskctl->lv_change != 0)
{
task_switchsub();
tl = &taskctl->level[taskctl->now_lv];
}
new_task = tl->tasks[tl->now];
timer_settime(task_timer, new_task->priority);
if(new_task != now_task)
{
farjmp(0, new_task->sel);
}
return;
}
最后是修改更改fifo了,要添加LV的修改
int fifo32_put(struct FIFO32 *fifo, int data)
// 向FIFO写入数据并累积起来
{
...
fifo->free--;
if(fifo->task != 0)
{
if(fifo->task->flags != 2) // 如果任务处于休眠状态
{
task_run(fifo->task, -1, 0); // 将任务唤醒
}
}
return 0;
}
在主函数里,就可以设定任务的等级与优先级了。
总算把这么长的多任务写得差不多了。。