C语言三十天自制操作系统(6)

第16天

后续多任务之旅。前一天兑现了四个职务之间活动切换,后天伊始写一个更通用的多职分切换程序。

先是定义存储每个职分的数据结构。

struct TASK {
  int sel, flags; 
  struct TSS32 tss;
};

sel表现段先择器,也就是CS的值,flags用于标记该义务是或不是被使用。

再创设一个用以存储操作系统中存有职务的数据结构。

struct TASKCTL {
  int running; 
  int now; 
  struct TASK *tasks[MAX_TASKS];
  struct TASK tasks0[MAX_TASKS];
};

数据结构有了,然后开展操作,首先大家想创设一个职分,先要得到TASKCTL中的某一个task0。

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;
      task->tss.eax = 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;
}

在TASKCTL中追寻一个还未利用的task用于存储,并对task结构举办初使化赋值,然后重临task的地点。

操作系统一先导运行的时候是单职务的,在拓展到多职责管理以前,要先初使化TASKCTL数据结构,并为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);
  }
  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;
}

率先定义一个TASKCTL类型的变量,并分配内存空间,然后用循环语句为那么些变量赋开首值,flags全体赋为0,因为还未早先利用。然后为各种义务分配gdt序号。再申请一个task,把操作系统当前运作的职分放进去,并安装2ms的定时器。

用task_alloc函数取得task变量之后,再调用task_run函数运行。

void task_run(struct TASK *task)
{
  task->flags = 2; 
  taskctl->tasks[taskctl->running] = task;
  taskctl->running++;
  return;
}

在init_task函数中曾经安装了2ms的定时器,定时器超时的时候,会调用task_switch函数

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

先安装2ms定时器,然后判断职分数,义务数若是唯有一个就无须切换了。如果多于1个,那么切换来下一个职分。假使已经是最后一个职务,那么就运行首个职务,重新循环三回。改造之后的多义务程序看上去就好多了,不管怎么职务,只要alloc一个,放进run里面,操作系统会自行且平均分配2ms的时日运作。平均分配时间也有毛病,假诺一个职务创建之后都并未利用,那么也分配2ms的话就太浪费cpu的计算能力了,大家就已毕让职责休眠的建制。

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

先是判断准务休眠的义务是或不是眼前正值运行的职务。然后搜索将要休眠的天职所处于TASKCTL变量中的地方,然后将以此任务覆盖,若是判断是正在运转的天职登时切换任务。将下来的标题是收取鼠标、键盘或者其他中断后,怎么样唤醒体眠的义务。

历次中断暴发后都会往音讯队列中发送数据,倘诺唤醒某一个任务也理应从队列入手。改造队列的数据结构,扩大存储TASK指针的字段。

struct FIFO32 {
  int *buf;
  int p, q, size, free, flags;
  struct TASK *task;
};

接下来在暂停处理程序往音讯队列写入数据的时候将义务唤醒,大家修改一下入队函数。

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

追加了return
0之前的5行,就是说中断处理程序往队列中写入音讯的时候,判断当前队列所表示的职责是还是不是处在活动状态,即使休眠的话那就提醒。

接下去大家别的再创造3个职分,每个职责都来得一下窗口,在窗口中只做一件工作,这就是不停得计数并把计数结果突显到窗口上。

要促成也相比简单,先创制3个TASK类型的指针,再调用task_alloc函数分配职责存储空间。创设SHEET指针,再调用sheet_alloc函数分配存储空间。再调用task_run函数运行。

本身在看源代码时候见到3个窗口职责的入口地址都是task_b_main函数,突然有一个疑点,入口地址都是千篇一律的的,那么这几个函数中定义的变量和音讯队列会不会搅乱。前前后后看了几许遍,task_b[i]->tss.esp
= memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 –
8;那句程序为每个职责申请了不一样的栈空间。程序入口函数中定义的int i; int
fifobuf[128],尽管是在入口函数中直接定义,不过C语言分配内存空间的时候是把栈中的内存空间拿过来使用。所以固然的天职入口函数是同样的,程序的进口也在内存中的同一地方,可是各样使命所运用的数量都是不雷同的。通过近半个小时的思想,我觉着对C语言的内存分配格局有了越发深远的摸底。

咱们明日为各种职责平均分配了2ms的运作时刻,不过一旦要把操作系统做得更好,肯定要分出职分的高低,也就是要安装每个职责的预先级。大家得以设置10
个阶段,分配运行的年月从0.01秒~0.1秒。在TASK结构体中加进int
priority字段,用于表示优先级。大家把职责a设置成10,也就是说职分a运行的光阴有0.1秒,可是由于a不运行的时候会自动休眠,所以也不会影响其余职务的运转。

动用为任务分配定时器的日子情势是最简易的主意。如果职分A是最要紧的,只是给A设置高一些的优先级,那么任何任务如故会运作。有时候大家会遇见一种状态,希望如若职务A要求周转,那么在任务A运行完此前其余职务都不可能运作。

咱俩只要给任务分3个等级,分别是level0~2。其中level0优先级最高,借使level0里的义务急需周转,那么,level1和2都无法运行。

事先我们处理多任务的数据结构有2层,首先是意味着具体任务的TASK结构,然后是把TASK结构统一保管的TASKCTL结构。现在我们在那两者从前扩充TASKLEVEL结构,用于表示任务的级别关系。

struct TASK {
  int sel, flags; 
  int level, priority;
  struct TSS32 tss;
};

level变量表示义务所处的级别。

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不再直接是管理职务,而是管理TASKLEVEL,再由TASKLEVEL管理各类职责。书中处理那有的代码不是很复杂,我也就不贴出来了。紧要注意的地点是task_switch函数里如若TASKCTL中lv_change字段为1就要重新查看LEVEL是不是有新的更尖端的层系职分须求实行。