Day16

今天继续多任务:自动管理、休眠、优先级。基本上实现了一个操作系统任务管理该有的功能。虽然今天代码量有点多,不过慢慢来看吧。

一些感想

因为代码有几个版本变化,所以这里就不总结了。

一个进程管理都应该有什么

  • 定义好的结构体,用于存储各种管理信息。
  • 定时切换功能,这个要操作系统自动切换,不需要程序员手动切换。
  • 休眠功能,或者说把等待IO的进程扔到等待队列里
  • 优先级,不同进程分到的时间片大小不一样

前两点是昨天+今天开头做的,后两点则是为了更好的用户体验做的。

任务休眠

当一个任务在等待IO的时候,可以先把它从正在运行的任务中删除,等到对应的IO中断来了,再唤醒(run)它

设定任务优先级

作者采用了这样的结构
priority
将任务分为分到不同的层(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;
}

在主函数里,就可以设定任务的等级与优先级了。

总算把这么长的多任务写得差不多了。。