linux内核双向循环队列,读书笔记之linux内核设计与实现(2)进程调度
調(diào)度程序是內(nèi)核的組成部分,它負(fù)責(zé)選擇下一個(gè)要運(yùn)行的進(jìn)程。進(jìn)程調(diào)度程序可看作在可運(yùn)行態(tài)進(jìn)程之間分配有限的處理器時(shí)間資源的內(nèi)核子系統(tǒng)。
多任務(wù)操作系統(tǒng)就是能夠同時(shí)并發(fā)的交互執(zhí)行多個(gè)進(jìn)程的操作系統(tǒng)。多任務(wù)系統(tǒng)可以劃分為兩類:搶占式和非搶占式。Linux提供了搶占式的多任務(wù)模式。在此
模式下,由調(diào)度程序來決定什么時(shí)候停止一個(gè)進(jìn)程的運(yùn)行以便其他進(jìn)程能夠得到執(zhí)行機(jī)會(huì)。這個(gè)強(qiáng)制的掛起動(dòng)作叫做搶占。進(jìn)程在被搶占之前能夠運(yùn)行的時(shí)間是預(yù)先
設(shè)置好的,而且有一個(gè)專門的名字,叫進(jìn)程的時(shí)間片。時(shí)間片實(shí)際上就是分配給每個(gè)可運(yùn)行進(jìn)程的處理器時(shí)間段。
4.1 策略
4.1.1 進(jìn)程優(yōu)先級(jí)
進(jìn)程可以分為I/O消耗型和處理器消耗型。調(diào)度算法中最基本的一類就是基于優(yōu)先級(jí)的調(diào)度。這是一種根據(jù)進(jìn)程的價(jià)值和其對(duì)處理器時(shí)間的需求來對(duì)進(jìn)程分級(jí)的想
法。在Linux系統(tǒng)中,優(yōu)先級(jí)高的進(jìn)程使用的時(shí)間片也較長(zhǎng)。調(diào)度程序總是選擇時(shí)間片未用盡而且優(yōu)先級(jí)高的進(jìn)程運(yùn)行。用戶和系統(tǒng)都可以通過設(shè)置進(jìn)程的優(yōu)先
級(jí)來影響系統(tǒng)的調(diào)度。
Linux內(nèi)核提供了兩組獨(dú)立的優(yōu)先級(jí)范圍。第一種是nice值,范圍 -20~19,默認(rèn)是0。值越大,優(yōu)先級(jí)越低,時(shí)間片越短。第二個(gè)范圍是實(shí)時(shí)優(yōu)先級(jí),其值可配置,變化范圍:0~99。
4.2 可執(zhí)行隊(duì)列(運(yùn)行隊(duì)列)
當(dāng)內(nèi)核要尋找一個(gè)新的進(jìn)程在CPU上運(yùn)行時(shí),必須只考慮處于可運(yùn)行狀態(tài)的進(jìn)程,(即在TASK_RUNNING狀態(tài)的進(jìn)程),因?yàn)閽呙枵麄€(gè)進(jìn)程鏈表是相當(dāng)?shù)托У?#xff0c;所以引入了處于可運(yùn)行狀態(tài)的進(jìn)程的雙向循環(huán)鏈表,也叫運(yùn)行隊(duì)列(runqueue)。
運(yùn)行隊(duì)列容納了系統(tǒng)中所有可以運(yùn)行的進(jìn)程,它是一個(gè)雙向循環(huán)隊(duì)列,該隊(duì)列通過task_struct結(jié)構(gòu)中的兩個(gè)指針run_list(Pointers to the next and previous elements in the runqueue list to which the process belongs)鏈表來維持。隊(duì)列的標(biāo)志有兩個(gè):一個(gè)是“空進(jìn)程”idle_task、一個(gè)是隊(duì)列的長(zhǎng)度。
有兩個(gè)特殊的進(jìn)程永遠(yuǎn)在運(yùn)行隊(duì)列中待著:當(dāng)前進(jìn)程和空進(jìn)程。前面我們討論過,當(dāng)前進(jìn)程就是由cureent指針?biāo)赶虻倪M(jìn)程,也就是當(dāng)前運(yùn)行著的進(jìn)程,但是請(qǐng)注意,current指針在調(diào)度過程中(調(diào)度程序執(zhí)行時(shí))是沒有意義的;空進(jìn)程是個(gè)比較特殊的進(jìn)程,只有系統(tǒng)中沒有進(jìn)程可運(yùn)行時(shí)它才會(huì)被執(zhí)行,Linux將它看作運(yùn)行隊(duì)列的頭,當(dāng)調(diào)度程序遍歷運(yùn)行隊(duì)列,是從idle_task開始、至idle_task結(jié)束的,在調(diào)度程序運(yùn)行過程中,允許隊(duì)列中加入新出現(xiàn)的可運(yùn)行進(jìn)程,新出現(xiàn)的可運(yùn)行進(jìn)程插入到隊(duì)尾,這樣的好處是不會(huì)影響到調(diào)度程序所要遍歷的隊(duì)列成員,可見,idle_task是運(yùn)行隊(duì)列很重要的標(biāo)志。
另一個(gè)重要標(biāo)志是隊(duì)列長(zhǎng)度,也就是系統(tǒng)中處于可運(yùn)行狀態(tài)(TASK_RUNNING)的進(jìn)程數(shù)目,用全局整型變量nr_running表示,在/kernel/fork.c中定義如下:
int nr_running=1;
若nr_running為0,就表示隊(duì)列中只有空進(jìn)程。在這里要說明一下:若nr_running為0,則系統(tǒng)中的當(dāng)前進(jìn)程和空進(jìn)程就是同一個(gè)進(jìn)程。但是Linux會(huì)充分利用CPU而盡量避免出現(xiàn)這種情況。
由于可執(zhí)行隊(duì)列是調(diào)度程序的核心數(shù)據(jù)結(jié)構(gòu)體,所以有一組宏定義用于獲取與“給定處理器或進(jìn)程”相關(guān)的可執(zhí)行隊(duì)列。cpu_rq(processor)宏用于返回給定處理器可執(zhí)行隊(duì)列的指針。this_rq()宏用來返回當(dāng)前處理器的可執(zhí)行隊(duì)列。宏task_rq(task)返回給定任務(wù)所在的隊(duì)列指針。
在
對(duì)可執(zhí)行隊(duì)列進(jìn)行操作以前,應(yīng)該先鎖住它。因?yàn)槊總€(gè)可執(zhí)行隊(duì)列唯一的對(duì)應(yīng)一個(gè)處理器。在其擁有者讀取或者改寫隊(duì)列成員的時(shí)候,可執(zhí)行隊(duì)列包含的鎖用來防止
隊(duì)列被其它代碼改動(dòng)。鎖住運(yùn)行隊(duì)列最常見的情況發(fā)生在你想鎖住的運(yùn)行隊(duì)列上恰巧有一個(gè)特定的任務(wù)在運(yùn)行。此時(shí)需要用到task_rq_lock()和
task_rq_unlock()函數(shù):
struct runqueue *rq;
unsigned long flags;
rq = task_rq_lock(task,&flags);
對(duì)任務(wù)隊(duì)列的操作
task_rq_unlock(rq, &flags);
一個(gè)疑問,解鎖后,之前的任務(wù)還繼續(xù)運(yùn)行嗎?
睡眠和喚醒
休
眠(被阻塞)的進(jìn)程處于一個(gè)特殊的不可執(zhí)行狀態(tài)。
進(jìn)程休眠有種原因,但肯定都是為了等待一些事件。事件可能是一段時(shí)間,從文件I/O讀取更多的數(shù)據(jù),或者是某個(gè)硬件事件。一個(gè)進(jìn)程還有可能在嘗試獲取一個(gè)
已被占用的內(nèi)核信號(hào)量時(shí)被迫進(jìn)入休眠。休眠的一個(gè)常見原因是文件I/O——如進(jìn)程對(duì)一個(gè)文件執(zhí)行了read()操作,而這需要從磁盤里讀取。還有,進(jìn)程在
獲取鍵盤輸入的時(shí)候也需要等待。無論哪種情況,內(nèi)核的操作都相同:進(jìn)程把它自己標(biāo)記成休眠狀態(tài),把自己從可執(zhí)行隊(duì)列移出,放入等待隊(duì)列,然后調(diào)用
schedule()選擇和執(zhí)行一個(gè)其他進(jìn)程。喚醒的過程剛好相反;進(jìn)程被設(shè)置為可執(zhí)行狀態(tài),然后再?gòu)牡却?duì)列中移到可執(zhí)行隊(duì)列。??? 等待隊(duì)列:休眠通過等待隊(duì)列來處理,等待隊(duì)列是由等待某些事件發(fā)生的進(jìn)程組成的簡(jiǎn)單鏈表。
4.3 搶占和上下文切換
上下文切換,也就是從一個(gè)可執(zhí)行進(jìn)程切換到另一個(gè)可執(zhí)行進(jìn)程,由定義在kernel/sched.c中的context_switch()函數(shù)處理。每當(dāng)一個(gè)新的進(jìn)程被選出來準(zhǔn)備投入運(yùn)行的時(shí)候,schedule()就會(huì)調(diào)用該函數(shù)。它完成兩項(xiàng)基本工作:
(1)調(diào)用定義在中的switch_mm(),該函數(shù)負(fù)責(zé)把虛擬內(nèi)存從上一個(gè)進(jìn)程映射切換到新的進(jìn)程中(前面已經(jīng)提及,虛擬內(nèi)存讓進(jìn)程在獲取和使用內(nèi)存時(shí)覺得自己擁有整個(gè)系統(tǒng)的所有內(nèi)存資源)。
(2)調(diào)用定義在中的switch_to(),該函數(shù)負(fù)責(zé)從上一個(gè)進(jìn)程的處理器狀態(tài)切換到新進(jìn)程的處理器狀態(tài)(虛擬處理器)。這包括保存、恢復(fù)棧信息和寄存器信息。
4.3.1 用戶搶占
內(nèi)
核即將返回用戶空間的時(shí)候,如果need_resched標(biāo)志被設(shè)置,會(huì)導(dǎo)致schedule()被調(diào)用,此時(shí)就會(huì)發(fā)生用戶搶占。內(nèi)核無論是從中斷處理程
序還是在系統(tǒng)調(diào)用后返回,都會(huì)檢查need_resched標(biāo)志。如果它被設(shè)置了,那么內(nèi)核會(huì)選擇一個(gè)其他進(jìn)程投入運(yùn)行。從中斷處理程序或系統(tǒng)調(diào)用返回的代碼都是和體系結(jié)構(gòu)相關(guān)的,在entry.S(此文件不僅包含內(nèi)核入口部分的程序,內(nèi)核退出部分的代碼也在其中)文件中通過匯編語(yǔ)言來實(shí)現(xiàn)。
4.3.2 內(nèi)核搶占
只
要當(dāng)前進(jìn)程沒有持有鎖,內(nèi)核就可以進(jìn)行搶占。鎖是非搶占區(qū)的標(biāo)志。所以,如果沒有持有鎖,那么正在執(zhí)行的代碼就是可重新導(dǎo)入的,也就是可搶占的。
為了支持內(nèi)核搶占,每個(gè)進(jìn)程的thread_info引入了preempt_count計(jì)數(shù)器。初始值為0,每當(dāng)使用鎖的時(shí)候就加1,釋放鎖的時(shí)候就減
1。當(dāng)=0時(shí),內(nèi)核就可以被搶占。從中斷返回內(nèi)核空間的時(shí)候,內(nèi)核會(huì)檢查need_resched和preempt_count的值。如果前者被設(shè)置,且
后者為0,則說明有一個(gè)更為重要的任務(wù)需要執(zhí)行并且可以安全的搶占,此時(shí),調(diào)度程序就會(huì)被調(diào)用。
4.4 與調(diào)度相關(guān)的系統(tǒng)調(diào)用
總結(jié)
以上是生活随笔為你收集整理的linux内核双向循环队列,读书笔记之linux内核设计与实现(2)进程调度的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 打算开源一个低代码平台,第二天,包含【工
- 下一篇: linux内核 task cmd,lin