自制操作系统日志——第十六天

自制操作系统日志——第十六天

今天我们再接再厉,继续进一步的完成我们的多任务机制。



一、多任务自动管理化

在昨天我们的多任务都是通过自己手动添加,然后进行运行的,这样子当我们任务过多时,总不好再一个个给他们进行设置吧?

因此呢,我们效仿前面的定时器与图层的自动管理,来进行对多任务的管理:
首先添加并修改结构体部分:

/* mtask.c */
#define	MAX_TASKS		1000 //最大的任务数量
#define	TASK_GDT0		3    //指从第几号的GDT开始
struct TSS32//共26个int成员,104字节。摘自cpu技术资料里的设定
{
	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;//段寄存器的值。段寄存器16位,但是预先设定32,以防止为来说不定是32了
	int ldtr, iomap;//任务设置部分。先设置ldtr=0 ,iomap=0x40000000
};
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];
};
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);

然后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;
        set_segmdesc(gdt + TASK_GDT0 +i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
    }
    task = task_alloc();//接收到akkoc返回的第一个任务
    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;//全部都在使用当中
}
  • 运行与切换任务:
//将活动的task添加到tasks的末尾
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++;//先把now++,然后把now所代表的任务切换成当前任务
    if(taskctl->now == taskctl->running){//当前正在运行的任务是最后一个的话,则切换回第一个任务
        taskctl->now = 0;
        }
        farjmp(0, taskctl->tasks[taskctl->now]->sel);
    }
    return;
}
  • 最后,将原本的mt_switch mt_timer等等都进行替换即可!(在timer.c中) ; 然后还要对主程序进行修改一下:
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);

然后,make run 发现确实可以正常运行!非常好,我们已经实现自动化的管理了。。

二、任务休眠

截至前一节为止,我们的多任务系统都是给每个任务大约相同的运行时间。但在实际中,并非每个任务都需要一样的运行时间。就比如,我们的任务a,如果没有发生鼠标,键盘中断的话那么其大多时间都是空闲的,这样子就会损耗cpu的运行效率。

因此,我们需要让处于空闲状态的任务进行休眠,以此来提高cpu的运行效率。那么要如何进行休眠呢?当然这很简单,由于我们之前已经建立了任务管理表,因此我们只需将休眠的任务从tasks中剔除就可以达到休眠的效果了。

创建sleep:

void task_sleep(struct TASK *task)
{
	int i;
	char ts = 0;
	if(task->flags == 2){//如果指定任务处于唤醒中
	    if(task == taskctl->tasks[taskctl->now]){
            ts = 1;//当前任务让自己休眠,那么稍后需要进行任务切换;如果是任务A让任务B休眠则不用切换
        }
        //寻找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;
}

然后,为了防止我们休眠任务a后,若键盘中断发生像缓冲区写入数据,但无法响应的情况。我们要在fifo中进行一下修改,使得其可以唤醒任务:
首先更改fifo32的结构体:

struct FIFO32{
	int *buf;//缓冲区地址
	int p, q, size, free, flags;//p 下一写入地址;q 下一读出地址; size是总字节数,free是缓冲区中没有数据的字节数;flag判断溢出
    struct TASK *task;
};

然后,修改fifo的初始化与写入的函数:

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; //有数据读入时需要唤醒的任务!!!如果赋予0则代表禁用自动唤醒
	return;
}

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;
}

最后,进行改写主函数!

  • 先初始化fifo缓冲区,将fifo的自动唤醒设置为0,表示禁止中断;
  • task_init返回一个地址,赋予task_a;
  • 当fifo为空时,将任务a进行休眠。不过这里,我们要先休眠,再开发中断,否则先开放中断的话,在处理休眠过程中如果发生了可能会使得自动唤醒功能出错!!
	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=0
		if (fifo32_status(&fifo) == 0)
		{
			task_sleep(task_a);
			io_sti();void task_b_main(struct SHEET *sht_back)
{fifo32_init(&fifo, 128, fifobuf, 0);

然后,make run一下!

哇哦,这里我们会发现任务b的速度快了两倍不止!!
这是昨天的测速:
在这里插入图片描述

这是,今天添加了休眠后的测速:
在这里插入图片描述

解释一下原因: 假设我们处理器的能力为每秒200次:

  • 那么由于昨天的任务a与任务b所占的时间相同,因此任务b的处理能力为100;
  • 其中10用于显示count,90用于计算count;
  • 而今天的休眠后,由于a已经休眠了,因此b的处理能力为198(这是因为休眠的任务a也不是完全不消耗处理能力的)
  • 那么显示count的还是10,则就会有188用于计算count。
  • 因此,大约就是2倍多吧!!

然后,我们设置多个任务同时进行吧!!!这里请查看源代码了,太长了就不放进来了,就看看效果图:
在这里插入图片描述

设定任务优先级

这里,我们利用任务的优先级,来确定哪一个任务应该获得的运行时间最长!以确保比较重要的任务的优先级足够,不至于运行时会有所卡顿。

我们拟设置共用n个taskctl,给每一个taskctl设置不同的级别。级别越低的taskctl里的任务越优先执行。例如,在level 0 里只要一任务,就可以忽略level 1 里的所有任务!!!

除此之外,我们还要设计一个同层内的优先级,利用priority给同一级别里的任务分配不同的运行时长,当priority值越大,则代表该任务在本层里的得到的运行时长越长。。

首先,我们设置Bootpack.h:

/* mtask.c */
#define	MAX_TASKS		1000 //最大的任务数量
#define	TASK_GDT0		3    //指从第几号的GDT开始
#define	MAX_TASKS_LV	100  //每个level最多100个任务
#define	MAX_TASKLEVELS	10   //共十个level
struct TSS32//共26个int成员,104字节。摘自cpu技术资料里的设定
{
	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;//段寄存器的值。段寄存器16位,但是预先设定32,以防止为来说不定是32了
	int ldtr, iomap;//任务设置部分。先设置ldtr=0 ,iomap=0x40000000
};
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 变成了管理level以及每一个单独的task的。tasklevel就是管理本leve中的每一个任务。然后,我们需要新创建几个用于操作tasklevel的函数:

//返回现在活动中的struct task的地址
struct TASK *task_now(void)
{
    struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
    return tl->tasks[tl->now];
}

//向tasklevel中增加一个任务
void task_add(struct TASK *task)
{
    struct TASKLEVEL *tl = &taskctl->level[task->level];
    tl->tasks[tl->running] = task;
    tl->running++;
    task->flags = 2;//活动中
    return;
}

//从tasklevel中删除一个任务
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){
        tl->now = 0; //now出现异常,进行修正
    }
    task->flags = 1;//休眠中

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

    return;
}

//决定切换到哪一个level中
void task_switchsub(void)
{
    int i;
    //寻找最上层的leve
    for(i = 0; i < MAX_TASKLEVELS; i++){
        if(taskctl->level[i].running > 0)
        {
            break;//找到了
        }
    }
    taskctl->now_lv = i;
    taskctl->lv_change = 0;
    return;
}

然后,继续修改一下前面的task任务部分:

//返回一个内存地址,将当前运行的调用该函数的程序作为一个任务
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();//接收到akkoc返回的第一个任务
    task->flags = 2;//活动中的标志
    task->priority = 2;//0.02s
    task->level = 0; //先将当前调用程序的任务暂时设置为0,即最高
    task_add(task);
    task_switchsub();//level 设置
    load_tr(task->sel);
    task_timer = timer_alloc();
    timer_settime(task_timer, task->priority);
    return task;
}
//将活动的task添加到tasks的末尾
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(task->flags != 2){
        //从休眠下唤醒的情况
        task->level = level;
        task_add(task);
    }

    taskctl->lv_change = 1 ;//下次切换任务时检查level
    return;
}

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;
}

void task_sleep(struct TASK *task)
{
    struct TASK *now_task;
    if(task->flags == 2){
        //如果处于活动中
        now_task = task_now();
        task_remove(task); //执行此语句flags变为1
        if(task == now_task){
            //如果是让自己休眠,则需要进行任务切换
            task_switchsub();
            now_task = task_now();//在设定完成后取得当前值
            farjmp(0, now_task->sel);
        }
    }
    return;
}

然后,我们修改一下fifo的放数据的函数:

int fifo32_put(struct FIFO32 *fifo, int data) 
//向fifo传递数据并保存
{
	if(fifo->task != 0){
		if(fifo->task->flags != 2 ){//如果任务处于休眠状态
		task_run(fifo->task, -1, 0);//唤醒任务
		}
	}

然后继续,进一步的修改主函数:

	task_run(task_a, 1, 0);/* sht_win_b */
	for (i = 0; i < 3; i++) {
		task_run(task_b[i], 2, i + 1);
	}

然后,我们运行一下,可以放下若不动鼠标键盘的话,其他三个计数更快;若动力鼠标或者键盘则计数会慢(因为我们给鼠标数据这个任务的优先级较高):

不动鼠标键盘:
在这里插入图片描述

发生鼠标键盘的中断后的:
在这里插入图片描述

可以看出计数确实有明显的差异!


总结

至此,我们多任务的基础部分已经算大致完成了!!感觉越来越有范了,嘿嘿嘿。加油加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值