進程的優先級隊列

更詳細的講解和代碼調試演示過程,請參看視頻
Linux kernel Hacker, 從零構建本身的內核vue

前幾節,咱們實現了進程的調度,並給每一個進程賦予相應優先級,優先級高的進程,可以獲得更多的CPU時間。但在演示時,咱們發現一個問題,就是,當進程發送切換時,鼠標鍵盤的響應會被卡死,這是由於響應鼠標鍵盤事件的進程被調到後臺,沒法得到CPU運行時間,從而不能處理鼠標或鍵盤事件。算法

這種狀況對用戶來講,是不可接受的。對用戶輸入給予及時迴應,直接關係到用戶體驗。因此負責相應用戶輸入的進程,其重要性要比那些只須要在後臺運行,不用和用戶直接交互的進程高不少。由此,咱們須要實現的是,只要用戶有輸入,那麼負責處理用戶輸入的進程就不能被打斷,爲了實現這個目的,咱們須要實現進程的優先級隊列。數組

如上圖,咱們把全部進程根據level分紅三個隊列,進程越重要,它對應的level值越低,這樣,當進行進程調度時,進程調度器先查看level等於0的隊列是否還有進程,有的話,只執行該隊列的進程,若是該隊列不止一個進程,那麼每一個進程根據他們的優先級得到相應的CPU時間。微信

若是levelw爲0的隊列沒有可調度的進程,那麼level爲1的隊列中的進程纔有得到調度的機會,以此類推。markdown

由此,咱們看看相關數據結構的定義,首先是multi_task.h:數據結構

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

#define  MAX_TASKS  5
#define  MAX_TASKS_LV   3
#define  MAX_TASKLEVELS 3

#define  TASK_GDT0  7
#define  SIZE_OF_TASK  120

struct TASKLEVEL {
    int running;
    int now;
    struct TASK *tasks[MAX_TASKS_LV];
};

#define SIZE_OF_TASKLEVEL  (8+ 4*MAX_TASKS_LV)

struct TASKCTL {
    int  now_lv;
    int  lv_change;
    struct TASKLEVEL  level[MAX_TASKLEVELS];
    struct TASK tasks0[MAX_TASKS];
};

TASK結構體增長了一個level變量,用於代表進程的重要性。TASKLEVEL對應於上圖的進程隊列,相同重要性的進程,都存儲在對應TASKLEVEL的tasks數組中。app

TASKCTL 也就是進程管理器,其存儲的再也不是進程,而是進程的優先級隊列,它要找到重要性最高的進程隊列,從中取出進程進行調度。函數

multi_task.c 須要進行相應改動:ui

struct TASKCTL *get_taskctl() {
    return taskctl;
}

void init_task_level(int level) {
    taskctl->level[level].running = 0;
    taskctl->level[level].now = 0;
    int i;
    for (i = 0; i < MAX_TASKS_LV; i++) {
        taskctl->level[i].tasks[i] = 0;
    }
}

init_task_level 對進程優先級隊列進行初始化,running表示改對了中有幾個進程,now表示隊列中,哪一個進程正在被調度到前臺進行運行。lua

struct TASK  *task_init(struct MEMMAN *memman) {
    int  i;
    ....

    task = task_alloc();
    task->flags = 2;  //active
    task->priority = 100;
    task->level = 0;
    task_add(task);
    task_switchsub();
    ....
}

上面代碼用於初始化運行CMain函數的進程,它把CMain進程的level設置爲0,也就是該進程的重要性最高,只要它不被掛起,那麼它始終擁有被調度的權利。task_add會根據進程的重要性,將其加入對應的優先級隊列,一旦有新進程加入,task_switchsub 則修改相應調度信息,以便進程調度器作出適當調動。

void task_run(struct TASK *task,int level, int priority) {
    if (level < 0) {
        level = task->level;
    }

    if (priority > 0) {
        task->priority = priority;
    }

    if (task->flags == 2 && task->level != level) {
        task_remove(task); //change task flags
    }

    if (task->flags != 2) {
        task->level = level;
        task_add(task);
    }

    taskctl->lv_change = 1;
    return;
}

task_run 的做用是修改進程重要性或優先級,或者把新的進程根據其重要性添加到相應隊列。若是進程的重要性有改動,那麼經過task_remove把進程從原有的優先級隊列中去除,而後再經過task_add將進程添加到對應的隊列。

void task_switch(void) {
    struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
    struct TASK *new_task, *now_task = tl->tasks[tl->now];
    tl->now++;
    if (tl->now == tl->running) {
        tl->now = 0;
    }

    if (taskctl->lv_change != 0) {
        task_switchsub();
        tl = &taskctl->level[taskctl->now_lv];
    }

    new_task = tl->tasks[tl->now];
    timer_settime(task_timer, new_task->priority);
    if (new_task != now_task && new_task != 0) {
        farjmp(0, new_task->sel);
    }

    return;
}

task_switch 被時鐘中斷調用,它的邏輯發送不小變化。taskctl->now_lv表示當前正則被調度的優先級隊列,例如,若是now_lv的值是0,那麼表示當前level等於0的隊列中的進程才能夠得到被調度的機會。task_switch 經過taskctl->now_lv得知哪一個進程隊列應該被調度,而後從該隊列中,取出task對象進行執行。若是有新的進程加入,或有進程的重要性被改變了,那麼taskctl->lv_change的值就不等於0。假設當前得到調度機會的是level值爲1的隊列中的進程,可是有level等於0的進程對象添加到了level等於0的隊列中,那麼此時,調度算法就會中止從level爲1的隊列中去調度進程,而是切換到level爲0的隊列,從中獲取要調度的進程。

int  task_sleep(struct TASK *task) {
   struct TASK *cur_task = 0;

   if (task->flags == 2) {
       cur_task = task_now();
       task_remove(task);

       if (task == cur_task) {
          task_switchsub();
          cur_task = task_now();

          if (cur_task != 0)
          {
              farjmp(0, cur_task->sel);
          }
       }
   }

   return 0;
}

struct TASK *task_now(void) {
    struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
    return tl->tasks[tl->now];
}

void task_add(struct TASK *task) {
    struct TASKLEVEL *tl = &taskctl->level[task->level];
    tl->tasks[tl->running] = task;
    tl->running++;
    task->flags = 2;
    return;
}

task_sleep 用於刪除進程,若是須要殺掉某個進程,那麼可使用該函數剝奪指定進程對象得到調度的權利。task_remove負責把進程從它所在的隊列中刪除,若是當前被掛起的進程是正在運行的進程,那麼task_sleep會選擇下一個合適的進程進行調度執行。

task_now 用於返回當前正在被調用的進程對象,task_add把給定進程加入對應的優先級隊列。

void task_remove(struct TASK *task) {
    int i ;
    struct TASKLEVEL *tl = &taskctl->level[task->level];
    for (i = 0; i< tl->running; i++) {
        if (tl->tasks[i] == task) {
            tl->tasks[i] = 0;
            break;
        }
    }

    tl->running--;
    if (i < tl->now) {
        tl->now--;
    }

    if (tl->now >= tl->running) {
        tl->now = 0;
    } 

    task->flags = 1;

    for (; i < tl->running; i++) {
        tl->tasks[i] = tl->tasks[i+1];
    }

    return;
}

void task_switchsub(void) {
    int i;
    for (i = 0; i < MAX_TASKLEVELS; i++) {
        if (taskctl->level[i].running > 0) {
           break;
        }
    }

    taskctl->now_lv = i;
    taskctl->lv_change = 0;
}

task_remove主要負責把進程對象從隊列中刪除,並修改相應的隊列數據。一旦有進程刪除或添加,那麼進程調度須要做出對應的調整,task_switchsub的做用是根據進程的添加或刪除,修改進程調度器的相應信息,進而改變進程調度器的調度行爲。

最後是主入口函數CMain也作相應改動:

void CMain(void) {
    ....
    for (i = 0; i < 2; i++) {
       ....

       ....
       task_run(task_b[i], 1, (i+1)*5);
    }
    ....
}

主要到,咱們把運行兩個窗體的進程其重要性設置成1,也就是隻要運行主入口函數的進程不掛起,那麼運行兩個窗體的進程就不能獲得執行。

本節代碼改動雖多,但邏輯簡單,理解起來應該不難,通過上面的改動後,系統運行的狀況以下:

從上圖看出,若是CMain進程運行時,上面兩個窗體原來的計數字符串沒有顯示,這是由於兩個窗體的進程得不到調度的機會。因爲CMain進程負責相應鼠標鍵盤,所以,此時咱們移動鼠標就再也不出現卡頓的情形。

當CMain進程被掛起後,兩個窗體的進程得到執行機會,於是窗體中的計算字符串就出現了:


本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索