更詳細的講解和代碼調試演示過程,請參看視頻
Linux kernel Hacker, 從零構建本身的內核vue
有了進程的自動調度後,接下來的任務在於,如何將空閒進程掛起,空閒進程每每是那些沒有具體任務須要處理的進程,所以,若是繼續讓其運行的話,那麼必然會耗費寶貴的CPU資源,若是能讓它先掛起,等到它須要執行具體任務時,再把它調度到前臺,那纔是一種合理的進程管理機制。數組
咱們實現的進程調度,是依賴於進程控制器,也就是taskctl中的任務數組來實現的,當咱們想要啓動某個進程時,在該數組中找到對應的任務對象,而後把它加載到CPU那就能夠了。微信
同理,若是要讓某個進程休眠,那麼只要把它對應的TASK對象從任務數組中移除,那麼它天然就不會獲得調度的機會。markdown
所以,修改multi_task.c 增長一個task_sleep函數:數據結構
void task_sleep(struct TASK *task) { int i; char ts = 0; if (task->flags == 2) { if (task == taskctl->tasks[taskctl->now]) { ts = 1; } 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) { //若是當前掛起的任務正好是當前正在前臺運行的任務,那麼將第0個任務調度到前臺 if (taskctl->now >= taskctl->running) { taskctl->now = 0; } farjmp(0, taskctl->tasks[taskctl->now]->sel); } } return; }
該函數的邏輯是,根據要掛起的任務,在整個任務數組中查找,找到其對應的數組下標,而後把後面的任務向前覆蓋,這樣的話,要移除的任務就在數組中就會被覆蓋掉,從而實現將任務從數組中移除的目的。app
須要注意的是,若是要掛起的任務,正好是當前正在前臺運行的進程,那麼ts==1,咱們就把下標爲0的任務調度到前臺,而且把任務的數量也就是running的值減一,這樣,處於數組最後的那個任務將不會有機會被調度。函數
任務掛起是實現了,那麼當咱們想從新把任務調度到前臺時,該怎麼作呢?咱們能夠利用現有的隊列機制,回憶一下,一旦鼠標,鍵盤的事件發生時,咱們會把硬件產生的數據加入到他們對應的隊列中,而後在CMain主循環中,將隊列中的數據取出來處理。同理,當咱們掛起一個任務時,咱們把掛起的任務對象放入到一個隊列中,當想要從新調度這個對象時,咱們往隊列裏發送一個數據,而後在主循環中對該隊列進行檢查,一旦發現隊列中含有數據的話,那麼就把隊列中寄存的任務從新加入調度數組。代碼修改以下,在golbal_define.c中:ui
void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf, struct TASK *task) { fifo->size = size; fifo->buf = buf; fifo->free = size; fifo->flags = 0; fifo->p = 0; fifo->q = 0; fifo->task = task; return ; }
在初始化一個隊列時,把一個任務對象添加進去,若是隊列不須要寄存任務對象,那麼把task設置爲0就能夠。lua
int fifo8_put(struct FIFO8 *fifo, unsigned char data) { if (fifo == 0) { return -1; } 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; }
當隊列中有數據加入時,順便查看該隊列是否寄存着一個任務對象,若是是,那麼把該任務對象加入調度數組。url
因爲timer.c中,對計時器的運行須要使用到隊列,既然隊列的數據結構有變更,所以timer.c中,須要作一點小改動:
static struct TIMERCTL timerctl; extern struct TIMER *task_timer; void init_pit(void) { io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; int i; for (i = 0; i < MAX_TIMER; i++) { timerctl.timer[i].flags = 0; //not used timerctl.timer[i].fifo = 0; } }
上面的改動在於,把每一個timer對象的fifo隊列成員設置爲0。
接下來的改動主要在主入口函數中:
void CMain(void) { .... fifo8_init(&timerinfo, 8, timerbuf, 0); .... fifo8_init(&keyinfo, 32, keybuf, 0); .... task_a = task_init(memman); keyinfo.task = task_a; .... }
上面代碼的邏輯是,先經過task_init獲得CMain函數所對應的任務對象,並把該任務對象寄存在鍵盤事件列表中,也就是keyinfo.task = task_a;
void CMain(void) { .... task_run(task_b); ... int pos = 0; int stop_task_A = 0; for(;;) { io_cli(); .... else if (fifo8_status(&timerinfo) != 0) { io_sti(); int i = fifo8_get(&timerinfo); if (i == 10) { showString(shtctl, sht_back, pos, 144, COL8_FFFFFF, "A"); timer_settime(timer, 100); pos += 8; if (pos > 40 && stop_task_A == 0) { io_cli(); task_sleep(task_a); io_sti(); } } .... } }
上面代碼的邏輯時,當CMain函數在主循環中,連續打印字符」A」,當打印的字符超過5個時,經過task_sleep(task_a)把CMain進程掛起。這樣的話,系統運行時,咱們會發現原來是字符A和B 是同時打印到桌面上的,此時便只剩下字符B在繼續打印了。
因爲咱們把task_A寄存到鍵盤隊列,那麼當咱們點擊鍵盤,因而鍵盤數據就會存儲到鍵盤隊列中,因爲鍵盤隊列存儲了任務Ad的任務對象,那麼此時他會把對應任務對象從新加入到調度隊列中,由此字符A會從恢復打印狀態,也就是說,打印字符A的進程從新得到了被調度的機會。
參看視頻,能夠得到更加生動的演示展現。
本文分享自微信公衆號 - Coding迪斯尼(gh_c9f933e7765d)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。