更詳細的講解和代碼調試演示過程,請參看視頻
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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。