更詳細(xì)的講解和代碼調(diào)試演示過程爱谁,請參看視頻
Linux kernel Hacker, 從零構(gòu)建自己的內(nèi)核
前幾節(jié)茅逮,我們實現(xiàn)了進(jìn)程的調(diào)度憔购,并給每個進(jìn)程賦予相應(yīng)優(yōu)先級焦匈,優(yōu)先級高的進(jìn)程赘淮,能夠得到更多的CPU時間辕录。但在演示時,我們發(fā)現(xiàn)一個問題梢卸,就是走诞,當(dāng)進(jìn)程發(fā)送切換時,鼠標(biāo)鍵盤的響應(yīng)會被卡死蛤高,這是因為響應(yīng)鼠標(biāo)鍵盤事件的進(jìn)程被調(diào)到后臺蚣旱,無法獲得CPU運(yùn)行時間,從而不能處理鼠標(biāo)或鍵盤事件戴陡。
這種情況對用戶來說塞绿,是不可接受的。對用戶輸入給予及時回應(yīng)恤批,直接關(guān)系到用戶體驗异吻。所以負(fù)責(zé)相應(yīng)用戶輸入的進(jìn)程,其重要性要比那些只需要在后臺運(yùn)行喜庞,不用和用戶直接交互的進(jìn)程高很多诀浪。由此,我們需要實現(xiàn)的是延都,只要用戶有輸入雷猪,那么負(fù)責(zé)處理用戶輸入的進(jìn)程就不能被打斷,為了實現(xiàn)這個目的窄潭,我們需要實現(xiàn)進(jìn)程的優(yōu)先級隊列春宣。
如上圖,我們把所有進(jìn)程根據(jù)level分成三個隊列嫉你,進(jìn)程越重要月帝,它對應(yīng)的level值越低,這樣幽污,當(dāng)進(jìn)行進(jìn)程調(diào)度時嚷辅,進(jìn)程調(diào)度器先查看level等于0的隊列是否還有進(jìn)程,有的話距误,只執(zhí)行該隊列的進(jìn)程簸搞,如果該隊列不止一個進(jìn)程扁位,那么每個進(jìn)程根據(jù)他們的優(yōu)先級獲得相應(yīng)的CPU時間。
如果levelw為0的隊列沒有可調(diào)度的進(jìn)程趁俊,那么level為1的隊列中的進(jìn)程才有獲得調(diào)度的機(jī)會域仇,以此類推。
由此寺擂,我們看看相關(guān)數(shù)據(jù)結(jié)構(gòu)的定義暇务,首先是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結(jié)構(gòu)體增加了一個level變量,用于表明進(jìn)程的重要性怔软。TASKLEVEL對應(yīng)于上圖的進(jìn)程隊列垦细,相同重要性的進(jìn)程,都存儲在對應(yīng)TASKLEVEL的tasks數(shù)組中挡逼。
TASKCTL 也就是進(jìn)程管理器括改,其存儲的不再是進(jìn)程,而是進(jìn)程的優(yōu)先級隊列家坎,它要找到重要性最高的進(jìn)程隊列嘱能,從中取出進(jìn)程進(jìn)行調(diào)度。
multi_task.c 需要進(jìn)行相應(yīng)改動:
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 對進(jìn)程優(yōu)先級隊列進(jìn)行初始化虱疏,running表示改對了中有幾個進(jìn)程焰檩,now表示隊列中,哪個進(jìn)程正在被調(diào)度到前臺進(jìn)行運(yùn)行订框。
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();
....
}
上面代碼用于初始化運(yùn)行CMain函數(shù)的進(jìn)程,它把CMain進(jìn)程的level設(shè)置為0兜叨,也就是該進(jìn)程的重要性最高穿扳,只要它不被掛起,那么它始終擁有被調(diào)度的權(quán)利国旷。task_add會根據(jù)進(jìn)程的重要性隆圆,將其加入對應(yīng)的優(yōu)先級隊列壕吹,一旦有新進(jìn)程加入,task_switchsub 則修改相應(yīng)調(diào)度信息,以便進(jìn)程調(diào)度器做出適當(dāng)調(diào)動荸百。
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 的作用是修改進(jìn)程重要性或優(yōu)先級,或者把新的進(jìn)程根據(jù)其重要性添加到相應(yīng)隊列看杭。如果進(jìn)程的重要性有改動慨绳,那么通過task_remove把進(jìn)程從原有的優(yōu)先級隊列中去除,然后再通過task_add將進(jìn)程添加到對應(yīng)的隊列被环。
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 被時鐘中斷調(diào)用糙及,它的邏輯發(fā)送不小變化。taskctl->now_lv表示當(dāng)前正則被調(diào)度的優(yōu)先級隊列筛欢,例如浸锨,如果now_lv的值是0唇聘,那么表示當(dāng)前l(fā)evel等于0的隊列中的進(jìn)程才可以獲得被調(diào)度的機(jī)會。task_switch 通過taskctl->now_lv得知哪個進(jìn)程隊列應(yīng)該被調(diào)度柱搜,然后從該隊列中迟郎,取出task對象進(jìn)行執(zhí)行。如果有新的進(jìn)程加入聪蘸,或有進(jìn)程的重要性被改變了宪肖,那么taskctl->lv_change的值就不等于0。假設(shè)當(dāng)前獲得調(diào)度機(jī)會的是level值為1的隊列中的進(jìn)程宇姚,但是有l(wèi)evel等于0的進(jìn)程對象添加到了level等于0的隊列中匈庭,那么此時,調(diào)度算法就會停止從level為1的隊列中去調(diào)度進(jìn)程浑劳,而是切換到level為0的隊列阱持,從中獲取要調(diào)度的進(jìn)程。
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 用于刪除進(jìn)程魔熏,如果需要?dú)⒌裟硞€進(jìn)程衷咽,那么可以使用該函數(shù)剝奪指定進(jìn)程對象獲得調(diào)度的權(quán)利。task_remove負(fù)責(zé)把進(jìn)程從它所在的隊列中刪除蒜绽,如果當(dāng)前被掛起的進(jìn)程是正在運(yùn)行的進(jìn)程镶骗,那么task_sleep會選擇下一個合適的進(jìn)程進(jìn)行調(diào)度執(zhí)行。
task_now 用于返回當(dāng)前正在被調(diào)用的進(jìn)程對象躲雅,task_add把給定進(jìn)程加入對應(yīng)的優(yōu)先級隊列鼎姊。
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主要負(fù)責(zé)把進(jìn)程對象從隊列中刪除,并修改相應(yīng)的隊列數(shù)據(jù)相赁。一旦有進(jìn)程刪除或添加相寇,那么進(jìn)程調(diào)度需要作出對應(yīng)的調(diào)整,task_switchsub的作用是根據(jù)進(jìn)程的添加或刪除钮科,修改進(jìn)程調(diào)度器的相應(yīng)信息唤衫,進(jìn)而改變進(jìn)程調(diào)度器的調(diào)度行為。
最后是主入口函數(shù)CMain也做相應(yīng)改動:
void CMain(void) {
....
for (i = 0; i < 2; i++) {
....
....
task_run(task_b[i], 1, (i+1)*5);
}
....
}
主要到绵脯,我們把運(yùn)行兩個窗體的進(jìn)程其重要性設(shè)置成1佳励,也就是只要運(yùn)行主入口函數(shù)的進(jìn)程不掛起,那么運(yùn)行兩個窗體的進(jìn)程就不能得到執(zhí)行蛆挫。
本節(jié)代碼改動雖多赃承,但邏輯簡單,理解起來應(yīng)該不難悴侵,經(jīng)過上面的改動后楣导,系統(tǒng)運(yùn)行的情況如下:
從上圖看出,如果CMain進(jìn)程運(yùn)行時畜挨,上面兩個窗體原來的計數(shù)字符串沒有顯示筒繁,這是因為兩個窗體的進(jìn)程得不到調(diào)度的機(jī)會噩凹。由于CMain進(jìn)程負(fù)責(zé)相應(yīng)鼠標(biāo)鍵盤,因此毡咏,此時我們移動鼠標(biāo)就不再出現(xiàn)卡頓的情形驮宴。
當(dāng)CMain進(jìn)程被掛起后,兩個窗體的進(jìn)程獲得執(zhí)行機(jī)會呕缭,因而窗體中的計算字符串就出現(xiàn)了:
更多技術(shù)信息堵泽,包括操作系統(tǒng),編譯器恢总,面試算法迎罗,機(jī)器學(xué)習(xí),人工智能片仿,請關(guān)照我的公眾號: