三十天自制操作系統(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;這句程序為每個任務申請了不同的??臻g。程序入口函數中定義的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結構?,F在我們在這兩者之前增加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閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容

  • 又來到了一個老生常談的問題,應用層軟件開發的程序員要不要了解和深入學習操作系統呢? 今天就這個問題開始,來談談操...
    tangsl閱讀 4,158評論 0 23
  • 從哪說起呢? 單純講多線程編程真的不知道從哪下嘴。。 不如我直接引用一個最簡單的問題,以這個作為切入點好了 在ma...
    Mr_Baymax閱讀 2,818評論 1 17
  • *面試心聲:其實這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個offer,總結起來就是把...
    Dove_iOS閱讀 27,199評論 30 471
  • 在人類遺傳基因中,都有感覺統合的基本能力,每個寶寶生下來,就擁有此能力,但是這種本能必須在嬰幼兒時期和環境的互動中...
    耿銳鵬閱讀 2,136評論 2 2
  • 一、Block的簡單介紹 Block 就是匿名函數 它是封裝了一個代碼塊,這個代碼塊在什么時候都可以執行; 使用B...
    和玨貓閱讀 636評論 1 7