《30天自制操作系统》16_day_学习笔记

时间:2023-03-09 04:37:33
《30天自制操作系统》16_day_学习笔记

harib13a:
  今天我们要继续折腾多任务,任务的高效管理是操作系统的一个重要的任务。在今天,我们将为系统创建更加完善的任务管理系统,其中包括优先级,任务等级等。
  1、任务管理结构体

#define MAX_TASKS        1000    /* 最大任务数量 */
#define TASK_GDT0 3 /* 任务块在GDT中的初始位置:从GDT的3号段开始 */
struct TSS32 {//任务状态段,这个在前面已经提到过,这里再介绍一下
      // 26个int成员,104字节
int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;   //与任务设置相关的信息(任务切换时,除backlink,都不会被写入)
int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;//32位寄存器;eip任务返回时,找到返回的地址
int es, cs, ss, ds, fs, gs;//16位寄存器
int ldtr, iomap;      //有关任务设置的信息。任务切换时CPU不写;ldtr = 0; iomap = 0x4000_0000
};
struct TASK {//任务结构体
//sel用来存储GDT中的编号
//flag表示任务的状态
//tss是任务状态段
int sel, flags;
struct TSS32 tss;
};
struct TASKCTL {          //任务管理结构体
int running;           /* 表示正在运行中的任务数量 */
int now;              /* 记录当前正在运行的是哪一个任务 */
struct TASK *tasks[MAX_TASKS];//记录正在运行中的任务的地址
struct TASK tasks0[MAX_TASKS];//1000个任务
};

  2、初始化任务管理结构体

//初始化TASKCTL;初始化一个TASK为flag=2(正在运行)
//返回:返回正在运行这个任务的地址
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;    //初始化任务段描述的GDT
taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));//分配任务管理结构体
for (i = ; i < MAX_TASKS; i++) {
taskctl->tasks0[i].flags = ;          //所有任务的flag置0 (未使用)
taskctl->tasks0[i].sel = (TASK_GDT0 + i) * ;//任务在GDT中的编号
set_segmdesc(gdt + TASK_GDT0 + i, , (int) &taskctl->tasks0[i].tss, AR_TSS32);//1000个任务按照编号写入GDT中
}
task = task_alloc();    //临时任务分配
task->flags = ;       /* 临时任务为正在运行中flag=2 */
taskctl->running = ;    //正在运行中任务数为1
taskctl->now = ;      //当前运行任务编号为0(数组从0开始的)
taskctl->tasks[] = task; //赋值给TASKCTK的第一个任务
load_tr(task->sel);     //加载到TR寄存器中,让该任务运行
task_timer = timer_alloc();//定时器设置
timer_settime(task_timer, );
return task;
}

  3、创建初始化一个任务结构的函数

/*    这个函数做的事,就是把TASKCTL中的任务数组task0[]
的下一个没有使用的任务的任务状态段赋值,初始化,并置flag=1正在使用*/
struct TASK *task_alloc(void)
{
int i;
struct TASK *task;//临时的任务结构体
for (i = ; i < MAX_TASKS; i++) {    //按顺序查找taskctl中的所有任务
if (taskctl->tasks0[i].flags == ) {//找到第一个没有被分配的任务
task = &taskctl->tasks0[i];   //赋值给临时变量task
task->flags = ;          /* 正在使用的标识 */
task->tss.eflags = 0x00000202; /* IF = 1; */
task->tss.eax = ;        /* 其他的值都初始化为0 */
task->tss.ecx = ;
task->tss.edx = ;
task->tss.ebx = ;
task->tss.ebp = ;
task->tss.esi = ;
task->tss.edi = ;
task->tss.es = ;
task->tss.ds = ;
task->tss.fs = ;
task->tss.gs = ;
task->tss.ldtr = ;
task->tss.iomap = 0x40000000;
return task;
}
}
return ; /* 初始化成功 */
}

  4、任务运行函数task_run

//这个函数做的事:将参数task的flag=2运行中,再将改任务的地址放到TASKCTL的tasks[]中
void task_run(struct TASK *task)
{
task->flags = ;               /* task的flag=2 正在运行 */
taskctl->tasks[taskctl->running] = task; //task的地址放到ctl的tasks[]中
taskctl->running++;             //运行中任务计数+1
return;
}

  5、最后,任务切换task_switch()

//将now指向的运行中的任务向后移动一个
void task_switch(void)
{
timer_settime(task_timer, );//任务切换的定时器
if (taskctl->running >= ) { //运行中的任务数>2
taskctl->now++;      //now指向下一个运行中的任务
if (taskctl->now == taskctl->running) {
taskctl->now = ;  //到达了尾部,又回到开始
}
farjmp(, taskctl->tasks[taskctl->now]->sel);//JMP到now指向的新的任务的GDT
}
return;
}

harib13b:
  前面我们已经完成了任务管理系统的任务初始化,创建等工作。下面我们继续完善,添加任务删除(休眠)操作task_sleep.

//mtask.c
void task_sleep(struct TASK *task)
{
int i;
char ts = ;
if (task->flags == ) { /* 如果指定的任务处于唤醒状态 */
if (task == taskctl->tasks[taskctl->now]) {//要休眠的是正在运行的任务
ts = ;         /* 让自己休眠,稍后需要进行任务切换 */
}
for (i = ; i < taskctl->running; i++) {/*寻找task所在的位置 */
if (taskctl->tasks[i] == task) {
/* task的位置找到了 */
break;
}
}
taskctl->running--;
if (i < taskctl->now) {
taskctl->now--;          /* 正在运行的前移一个单位 */
}
for (; i < taskctl->running; i++) {//所有活跃的任务迁移一个单位
taskctl->tasks[i] = taskctl->tasks[i + ];
}
task->flags = ; /* 不工作的状态 */
if (ts != ) { //需要休眠的是正在运行的任务(自己)进行任务切换
if (taskctl->now >= taskctl->running) {
      /* now的值到了最后一个,跳到最前面的。 */
taskctl->now = ;
}
         //跳到GDT相应的段号。
farjmp(, taskctl->tasks[taskctl->now]->sel);
}
}
return;
}

  接下来是FIFO中写入数据时将任务唤醒的功能,添加用于记录唤醒任务的信息成员。
  注 意:这里是有数据写入的时候就唤醒任务。

//bootpack.h
struct FIFO32 {
int *buf;
int p, q, size, free, flags;
struct TASK *task;//写入数据时,需要唤醒的任务。
};
//fifo.c
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 = ;
fifo->p = ;     /* 写入的位置 */
fifo->q = ;     /* 读取的位置 */
fifo->task = task; /* 写入数据是需要唤醒的任务 */
return;
} int fifo32_put(struct FIFO32 *fifo, int data)
/* FIFO中写入数据 */
{
if (fifo->free == ) {//没有空闲的空间了
fifo->flags |= FLAGS_OVERRUN;
return -;    //返回一个错误
}
fifo->buf[fifo->p] = data;
fifo->p++;
if (fifo->p == fifo->size) {
fifo->p = ;
}
fifo->free--;
if (fifo->task != ) {       //有要唤醒的任务
if (fifo->task->flags != ) { /* 任务不是活跃状态 */
task_run(fifo->task);   /* task_run()唤醒 */
}
}
return ;
}

harib13c:
  增加窗口的数量,增加了B0、B1、B2三个任务窗口,它们的设定相同。

void HariMain(void)
{ //....
unsigned char *buf_back, buf_mouse[], *buf_win, *buf_win_b;
struct SHEET *sht_back, *sht_mouse, *sht_win, *sht_win_b[];
struct TASK *task_a, *task_b[];
//....
/* sht_win_b */
for (i = ; i < ; i++) {
sht_win_b[i] = sheet_alloc(shtctl);
buf_win_b = (unsigned char *) memman_alloc_4k(memman, * );
sheet_setbuf(sht_win_b[i], buf_win_b, , , -);
sprintf(s, "task_b%d", i);
make_window8(buf_win_b, , , s, );
task_b[i] = task_alloc();
task_b[i]->tss.esp = memman_alloc_4k(memman, * ) + * - ;
task_b[i]->tss.eip = (int) &task_b_main;
task_b[i]->tss.es = * ;
task_b[i]->tss.cs = * ;
task_b[i]->tss.ss = * ;
task_b[i]->tss.ds = * ;
task_b[i]->tss.fs = * ;
task_b[i]->tss.gs = * ;
*((int *) (task_b[i]->tss.esp + )) = (int) sht_win_b[i];
task_run(task_b[i]);
}
//....
}

harib13d:
  到这里,任务是以相同的速度运行的,任务切换时间都为0.02s,下面我们通过设定不同的任务切换间隔来设定任务的优先级。
  1、修改任务结构体TASK

//bootpack.h
struct TASK {
int sel, flags; /* sel偼GDT偺斣崋偺偙偲 */
int priority;  //这里的优先级实际上是设定了任务切换的时间。
struct TSS32 tss;
};

  2、任务的相应的函数mtask.c

/* mtask.c节选 */
#include "bootpack.h" struct TASKCTL *taskctl;
struct TIMER *task_timer;
struct TASK *task_init(struct MEMMAN *memman)
{ //...
task->priority = ; /* 0.02s */
//...
task_timer = timer_alloc();//设定任务切换的时间的定时器
timer_settime(task_timer, task->priority);
return task;
} void task_run(struct TASK *task, int priority)
{
if (priority > ) {
//初始化任务的时候,加入了设定优先级的参数。
task->priority = priority;
}
if (task->flags != ) {
task->flags = ; /* 摦嶌拞儅乕僋 */
taskctl->tasks[taskctl->running] = task;
taskctl->running++;
}
return;
} void task_switch(void)
{ //.....
//定时器时间超时才进行任务的切换
timer_settime(task_timer, task->priority);
if (taskctl->running >= ) {
farjmp(, task->sel);
}
return;
}

  3、改写fifo.c中的任务唤醒

int fifo32_put(struct FIFO32 *fifo, int data)
/* FIFO傊僨乕僞傪憲傝崬傫偱拁偊傞 */
{ //.....
fifo->free--;
if (fifo->task != ) {
if (fifo->task->flags != ) { /* 需要唤醒的任务处于非活跃状态 */
task_run(fifo->task, ); /* 唤醒任务,定时器为0 */
}
}
return ;
}

harib13e:

    《30天自制操作系统》16_day_学习笔记
  我们继续来折腾优先级吧(看上图),我们将任务的优先级设计成上面的结构,最上层的LEVEL0中只要存在哪怕一个任务,则完全忽略LEVEL1和LEVEL2中的任务,只在LEVEL0中进行任务的切换。当LEVEL0中的人物全部休眠,或者全部降到下层的LEVEL,才轮到LEVEL1的任务进行切换。当LEVLE0和LEVEL1中都没有任务的时候才轮到LEVEL2出场。下面我们来看看具体的做法。

  1、修改任务结构体的定义

//bootpack.h
//对于每个LEVEL我们最多允许创建100个任务,一共10个LEVEL
#define MAX_TASKS 1000 /* 任务总数 */
#define TASK_GDT0 3 /* TSS放到GDT的3号段 */
#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];//100个任务指针
};
struct TASKCTL {
int now_lv;    /* 当前活动中的LEVEL */
char lv_change; /* 标志:下次切换任务时,是否需要改变LEVEL */
struct TASKLEVEL level[MAX_TASKLEVELS];//LEVEL一共10级
struct TASK tasks0[MAX_TASKS];     //任务总数
};

  2、我们来写几个用于操作struct TASKLEVEL的函数

//mtask.c
struct TASK *task_now(void)
{ //用来返回现在活动中的TASK的地址
struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
return tl->tasks[tl->now];
}
void task_add(struct TASK *task)
{ //向TASKLEVEL中添加一个任务
struct TASKLEVEL *tl = &taskctl->level[task->level];//当前任务的TASKLEVEL
tl->tasks[tl->running] = task;//把任务task添加到task后面
tl->running++;          //正在运行的数量+1
task->flags = ;         /* 活动中的标识 */
return;
}
void task_remove(struct TASK *task)
{ //从TASKLEVLE中删除一个任务
int i;
struct TASKLEVEL *tl = &taskctl->level[task->level];//获取要删除task的TASKLEVEL      /* task的位置 */
for (i = ; i < tl->running; i++) {
if (tl->tasks[i] == task) {
/* 找到了task出去 */
break;
}
}
tl->running--;
if (i < tl->now) {
tl->now--; /* 需要删除的task在now的前面 */
}
if (tl->now >= tl->running) {
      /* now到了任务的最后一个,又跳到头部 */
tl->now = ;
}
task->flags = ; /* 任务标识=1 */
          /* 都往前移动一个 */
for (; i < tl->running; i++) {
tl->tasks[i] = tl->tasks[i + ];
}
return;
}
void task_switchsub(void)
{ //用来在任务切换时,决定接下来切换到那个LEVEL
int i;
/* 寻找最上层的LEVEL */
for (i = ; i < MAX_TASKLEVELS; i++) {
if (taskctl->level[i].running > ) {
break; /* 找到了 */
}
}
taskctl->now_lv = i;
taskctl->lv_change = ;
return;
}

  3、改写任务TASK操作函数

struct TASK *task_init(struct MEMMAN *memman)
{ //初始化函数,开始只有LEVEL0中有任务
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 = ; i < MAX_TASKS; i++) {          //初始化TASKCTL中的每一个任务,并写到GDT相应的段号
taskctl->tasks0[i].flags = ;
taskctl->tasks0[i].sel = (TASK_GDT0 + i) * ;//GDT段号
set_segmdesc(gdt + TASK_GDT0 + i, , (int) &taskctl->tasks0[i].tss, AR_TSS32);
}
for (i = ; i < MAX_TASKLEVELS; i++) {       //初始化LEVEL
taskctl->level[i].running = ;
taskctl->level[i].now = ;
}
task = task_alloc();//在LEVLE0中初始化一个任务
task->flags = ; /* 活动中 */
task->priority = ; /* 0.02s */
task->level = ; /* LEVEL的值 */
task_add(task); //添加到TASKLEVEL中
task_switchsub(); /* 初始化LEVLE */
load_tr(task->sel); //初始化TR寄存器
task_timer = timer_alloc();//定时器
timer_settime(task_timer, task->priority);
return task;
}
void task_run(struct TASK *task, int level, int priority)
{ //在参数中指定LEVEL的值
if (level < ) {
level = task->level; /* 不改变LEVEL(不能为负) */
}
if (priority > ) {   //优先级设置
task->priority = priority;
}
if (task->flags == && task->level != level) { /* 改变活动中的LEVEL */
task_remove(task); /* 将TASK移除,flag=1执行下面的任务 */
}
if (task->flags != ) {
/* 唤醒任务 */
task->level = level;
task_add(task);   //把任务添加到活动中
}
taskctl->lv_change = ; /* 下次任务切换时检查LEVEL */
return;
}
void task_sleep(struct TASK *task)
{ //调用task_remove简化代码
struct TASK *now_task;
if (task->flags == ) {
/* 如果处于活动状态 */
now_task = task_now();//当前运行的任务地址
task_remove(task);   /* 执行后FLAG=1 */
if (task == now_task) {
/* 让自己休眠,需要进行切换 */
task_switchsub();
now_task = task_now();   /* 设定后,获取当前的任务 */
farjmp(, now_task->sel);//任务切换,GDT号
}
}
return;
}

  4、最后修改fifo.c任务唤醒

int fifo32_put(struct FIFO32 *fifo, int data)
/* FIFO接收到数据时,将任务唤醒 */
{ //....
fifo->free--;
if (fifo->task != ) {         //fifo中有需要唤醒的任务
if (fifo->task->flags != ) {   /* 唤醒到活跃状态 */
task_run(fifo->task, -, ); /* 修改了RUN的参数传递调用 */
}
}
return ;
}