三十日間の自制操作システム(6)

6978 ワード

16日目
マルチタスクの旅を続ける.前の日に2つのタスクの間で自動切り替えができました.今日からもっと一般的なマルチタスク切り替えプログラムを書き始めます.
まず、各タスクを格納するデータ構造を定義します.
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の中のあるtask 0を獲得します.
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を申請して、オペレーティングシステムが現在実行しているタスクを入れて、2 msのタイマーを設定します.
taskを使うalloc関数はtask変数を取得した後、task(u)を呼び出します.run関数が動作します
void task_run(struct TASK *task)
{
  task->flags = 2; 
  taskctl->tasks[taskctl->running] = task;
  taskctl->running++;
  return;
}
initにいますtask関数にはすでに2 msのタイマーが設定されていますが、タイマーがタイムアウトした時はtask(u)を呼び出します.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;
}
まず2 msタイマーを設定して、タスク数を判断します.タスク数は一つだけであれば、切り替えは不要です.1つ以上の場合は、次のタスクに切り替えます.最後のタスクであれば、最初のタスクを実行して、もう一度繰り返します.改造後のマルチタスクプログラムはよさそうです.どんなタスクでも、allocが一つであれば、runに入れます.オペレーティングシステムは自動的に2 msの時間を割り当てて運行します.平均分配時間にも欠点があります.もし一つのミッションが作成されてから全部使っていないなら、2 msを割り当てると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つのタスクを作成します.各タスクはウィンドウを表示し、ウィンドウの中で一つだけのことをします.つまり、カウントして、カウント結果をウィンドウに表示します.
実現も簡単です.まずTASKタイプの指針を3つ作って、task(u)を呼び出します.alloc関数はタスク格納空間を割り当てます.SHEETポインタを作成し、sheet_を呼び出します.alloc関数は記憶空間を割り当てます.またtask_を呼び出しますrun関数が動作します
ソースコードを見ていると、3つのウィンドウのタスクの入り口の住所がtask(u)です.b_メール関数には、突然の疑問があります.入り口のアドレスは同じです.この関数で定義されている変数とメッセージの列は混同されますか?前と後ろを何回も見ました.task_b[i]->tss.esp=memman_alloc_4 k(memman、64*1024)+64*1024−8;このプログラムはタスクごとに異なるスタック空間を申請します.プログラム入口関数で定義されているint i.int fifobuf[128]は、入口関数で直接定義されていますが、C言語でメモリ空間を割り当てるときはスタックの中のメモリ空間を持ってきて使います.したがって、タスクの入り口関数は同じですが、プログラムの入り口もメモリの同じ位置にありますが、タスクごとに使用されるデータは違います.半時間近くの思考を通して、C言語のメモリ割り当て方式についてもっと深く理解したと思います.
私たちは現在、各タスクに平均的に2 msの運転時間を割り当てていますが、オペレーティングシステムをよりよくするためには、タスクの軽重緩急、すなわちタスクごとの優先度を設定する必要があります.10段階を設定して、運転時間を0.01秒から0.1秒まで割り当てることができます.優先度を表すためにTASK構造体にint proritフィールドを追加します.タスクaを10に設定します.つまり、タスクaの実行時間は0.1秒ですが、aが動作しないと自動的に休止しますので、他のタスクの実行にも影響しません.
タスク割り当てタイマーとしての時間の使い方が一番簡単です.タスクAが最も重要である場合、Aの優先度が高いだけであれば、他のタスクはまだ実行されます.時には私達は1種の情況に出会うことができて、もし任務Aが実行しなければならないならばを望んで、それでは任務Aが運行し終わる前にその他の任務はすべて実行することができません.
私達は任務に3つの等級を付けます.それぞれレベル0~2です.その中でlevel 0の優先度が一番高く、level 0のタスクが実行されると、level 1と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が新しいより高度なレベルのタスクを実行する必要があるかを再確認します.