三十天自制操作系统(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是否有新的更高级的层次任务需要执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,004评论 0 23
  • 从哪说起呢? 单纯讲多线程编程真的不知道从哪下嘴。。 不如我直接引用一个最简单的问题,以这个作为切入点好了 在ma...
    Mr_Baymax阅读 2,676评论 1 17
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 在人类遗传基因中,都有感觉统合的基本能力,每个宝宝生下来,就拥有此能力,但是这种本能必须在婴幼儿时期和环境的互动中...
    耿锐鹏阅读 2,061评论 2 2
  • 一、Block的简单介绍 Block 就是匿名函数 它是封装了一个代码块,这个代码块在什么时候都可以执行; 使用B...
    和珏猫阅读 615评论 1 7