引用
- Linux進程管理專題
- Linux進程管理與調度-之-目錄導航
- 蝸窩科技-進程管理
- 郭健: Linux進程調度技術的前世今生之“前世”
- 郭健: Linux進程調度技術的前世今生之“今生”
- 宋寶華:Linux的任督二脈——進程調度和內存管理
- 宋寶華: Linux殭屍進程可以被“殺死”嗎?
- 宋寶華: 聊一聊進程深度睡眠的TASK_KILLABLE這個狀態
- 宋寶華: 關於Linux進程優先級數字混亂的徹底澄清
- Linux進程優先級和nice值
- Linux的進程線程及調度
- Linux內核學習筆記(6)-- 進程優先級詳解(prio、static_prio、normal_prio、rt_priority)
一. 進程基礎知識
- 進程:資源封裝,是處於執行期的程序以及它所管理的資源(如 打開的文件,掛起的信號,進程狀態,地址空間等)的總稱。用PCB(Processing Control Block)來描述,在linux中,用 task_struct 結構體來描述。
- 線程:調度單位。用thread_info來描述。
- 進程內核棧:void *stack
對每個進程,Linux內核都把兩個不同的數據結構緊湊的存放在一個單獨爲進程分配的內存區域中:一個是內核態的進程堆棧,另一個是緊挨着進程描述符的小數據結構thread_info,叫做線程描述符。
- Linux把thread_info(線程描述符)和內核態的線程堆棧存放在一起,這塊區域通常是8192(佔兩個頁框),其實地址必須是8192的整數倍。
- 進程最常用的是進程描述符結構task_struct,而不是thread_info結構的地址。爲了獲取當前CPU上運行進程的task_struct結構,內核提供了current宏,由於task_struct *task在thread_info的起始位置,該宏本質上等價於current_thread_info()->task。
- pid的數量是有限的,爲 32768 (cat /proc/sys/kernel/pid_max)
-
1.2 進程的各種狀態,生命週期
5個互斥狀態:state域能夠取5個互爲排斥的值(通俗一點就是這五個值任意兩個不能一起使用,只能單獨使用)。系統中的每個進程都必然處於以上所列進程狀態中的一種。 | |
狀態 | 描述 |
TASK_RUNNING | 表示進程要麼正在執行,要麼正要準備執行(已經就緒),正在等待cpu時間片的調度 |
TASK_INTERRUPTIBLE | 進程因爲等待一些條件而被掛起(阻塞)而所處的狀態。這些條件主要包括:硬中斷、資源、一些信號……,一旦等待的條件成立,進程就會從該狀態(阻塞)迅速轉化成爲就緒狀態TASK_RUNNING |
TASK_UNINTERRUPTIBLE | 意義與TASK_INTERRUPTIBLE類似,除了不能通過接受一個信號來喚醒以外,對於處於TASK_UNINTERRUPIBLE狀態的進程,哪怕我們傳遞一個信號或者有一個外部中斷都不能喚醒他們。只有它所等待的資源可用的時候,他纔會被喚醒。這個標誌很少用,但是並不代表沒有任何用處,其實他的作用非常大,特別是對於驅動刺探相關的硬件過程很重要,這個刺探過程不能被一些其他的東西給中斷,否則就會讓進城進入不可預測的狀態 |
TASK_STOPPED | 進程被停止執行,當進程接收到SIGSTOP、SIGTTIN、SIGTSTP或者SIGTTOU信號之後就會進入該狀態 |
TASK_TRACED | 表示進程被debugger等進程監視,進程執行被調試程序所停止,當一個進程被另外的進程所監視,每一個信號都會讓進城進入該狀態 |
2個終止狀態:兩個附加的進程狀態既可以被添加到state域中,又可以被添加到exit_state域中。只有當進程終止的時候,纔會達到這兩種狀態. | |
狀態 | 描述 |
EXIT_ZOMBIE | 進程的執行被終止,但是其父進程還沒有使用wait()等系統調用來獲知它的終止信息,此時進程成爲殭屍進程 |
EXIT_DEAD | 進程的最終狀態 |
TASK_KILLABLE:Linux 中的新進程狀態(TASK_UNINTERRUPTIBLE + TASK_WAKEKILL = TASK_KILLABLE) | |
狀態 | 描述 |
TASK_KILLABLE | 當進程處於這種可以終止的新睡眠狀態中,它的運行原理類似於 TASK_UNINTERRUPTIBLE,只不過可以響應致命信號 |
-
1.3 進程地址空間
可用pmap查看
-
1.4 內核線程
用ps查看線程時,名字爲[..]這樣的線程,都是內核線程。
例如:中斷線程化使用的irq內核線程;軟中斷使用的內核線程ksoftirqd;以及work使用的kworker內核線程。
內核線程沒有地址空間,所以task_struct->mm指針爲NULL。
內核線程沒有用戶上下文。
內核線程只工作在內核空間,不會切換至用戶空間。但內核線程同樣是可調度且可搶佔的。普通線程即可工作在內核空間,也可工作在用戶空間。
內核線程只能訪問3GB以上地址,而普通線程可訪問所有4GB地址空間。
常見內核線程 | prio | policy |
irq | 49 | SCHED_FIFO |
softirq | 120 | SCHED_NORMAL |
worker | 120 | SCHED_NORMAL |
init | 120 | SCHED_NORMAL |
kthreadd | 120 | SCHED_NORMAL |
cfinteractive | 0 | SCHED_FIFO |
中斷內核線程優先級很高,爲49,並且使用了實時調度策略。softirq和worker都是普通內核線程。
init_workqueues中創建了綁定CPU0的兩個kworker,分別是nice=0和nice=-20。apply_workqueue_attrs創建unbund worker,即kworker/uX:0。
其它特殊內核線程init優先級爲120,kthreadd優先級爲120.
cfinteractive優先級最高,主要處理CPU Frequency負載更新。
-
1.5 幾個特殊的內核進程
1. kernel_thread(): kernel_thread接口,使用該接口創建的線程,必須在該線程中調用daemonize()函數,這是因爲只有當線程的父進程指向”Kthreadd”時,該線程纔算是內核線程,而恰好daemonize()函數主要工作便是將該線程的父進程改成“kthreadd”內核線程;默認情況下,調用deamonize()後,會阻塞所有信號,如果想操作某個信號可以調用allow_signal()函數。
2. kthread_create(): kthread_create接口,則是標準的內核線程創建接口,只須調用該接口便可創建內核線程;默認創建的線程是存於不可運行的狀態,所以需要在父進程中通過調用wake_up_process()函數來啓動該線程。
3. kthread_run(): 創建並啓動線程的函數; 線程一旦啓動起來後,會一直運行,除非該線程主動調用do_exit函數,或者其他的進程調用kthread_stop函數,結束線程的運行。
4. kthread_should_stop(), kthread_stop(): 停止線程
5. kthread_should_park(), kthread_parkme(), kthread_park(),kthread_unpark(): 當在其他某個地方,調用 kthread_park(practice_task_p)後,線程將在kthread_parkme()處掛起睡眠,直到其他某個地方執行了kthread_unpark(practice_task_p)後,線程才被喚起,繼續執行。
4. 內核線程的退出: 當線程執行到函數末尾時會自動調用內核中do_exit()函數來退出或其他線程調用kthread_stop()來指定線程退出。
-
1.6 內核搶佔
使用搶佔式內核可以保證系統響應時間. 最高優先級的任務一旦就緒, 總能得到CPU的使用權。當一個運行着的任務使一個比它優先級高的任務進入了就緒態, 當前任務的CPU使用權就會被剝奪,或者說被掛起了,那個高優先級的任務立刻得到了CPU的控制權。如果是中斷服務子程序使一個高優先級的任務進入就緒態,中斷完成時,中斷了的任務被掛起,優先級高的那個任務開始運行。
缺點:不能直接使用不可重入型函數。(即需要考慮高低優先級線程之間相關數據的競態情況,需要加鎖保護)
搶佔點:
1. 用戶搶佔:發生在用戶空間的搶佔現象
- 當內核即將返回用戶空間時, 內核會檢查need_resched是否設置TIF_NEED_RESCHED, 如果設置, 則調用schedule(),此時,發生用戶搶佔.
- 從中斷(或異常)處理程序返回用戶空間時;
1. 時鐘中斷處理例程檢查當前任務的時間片,當任務的時間片消耗完時,scheduler_tick()函數就會設置need_resched標誌; |
2. 信號量、等到隊列、completion等機制喚醒時都是基於waitqueue的,而waitqueue的喚醒函數爲default_wake_function,其調用try_to_wake_up將被喚醒的任務更改爲就緒狀態並設置need_resched標誌。 |
3. 設置用戶進程的nice值時,可能會使高優先級的任務進入就緒狀態; |
4. 改變任務的優先級時,可能會使高優先級的任務進入就緒狀態; |
5. 新建一個任務時,可能會使高優先級的任務進入就緒狀態; |
6. 對CPU(SMP)進行負載均衡時,當前任務可能需要放到另外一個CPU上運行 |
2. 內核搶佔:一個在內核態運行的進程, 可能在執行內核函數期間被另一個進程取代。linux內核通過在thread_info結構中添加了一個自旋鎖標識preempt_count, 稱爲搶佔計數器(preemption counter)來作爲內核搶佔的標記。
- 當從中斷處理程序正在執行,且返回內核空間之前。當一箇中斷處理例程退出,在返回到內核態時(kernel-space)。這是隱式的調用schedule()函數,當前任務沒有主動放棄CPU使用權,而是被剝奪了CPU使用權。(中斷之後返回內核態時通過preempt_schedule_irq觸發內核搶佔)
- 當內核代碼再一次具有可搶佔性的時候,如解鎖(spin_unlock_bh)及使能軟中斷(local_bh_enable)等, 此時當kernel code從不可搶佔狀態變爲可搶佔狀態時(preemptible again)。也就是preempt_count從正整數變爲0時。這也是隱式的調用schedule()函數。(內核重新開啓內核搶佔時使用preempt_schedule檢查內核搶佔)
- 如果內核中的任務顯式的調用schedule(), 任務主動放棄CPU使用權。
- 如果內核中的任務阻塞(這同樣也會導致調用schedule()), 導致需要調用schedule()函數。任務主動放棄CPU使用權。
二. 進程調度
-
吞吐 vs 響應:吞吐和響應之間的矛盾
1. 響應:最小化某個任務的響應時間,哪怕犧牲其他任務爲代價。
2. 吞吐:全局視野,整個系統的workload被最大化處理。
-
I/O 消耗型 vs CPU消耗型
1. IO bound: CPU利用率低,進程的運行效率主要受限於I/O速度。
2. CPU bound:多數時間花在CPU上面(做運算)
-
優先級
int prio, static_prio, normal_prio; unsigned int rt_priority; |
|
字段 | 描述 |
static_prio | 用於保存靜態優先級,可以通過nice系統調用來進行修改;(100 ~ 139) |
rt_priority | 用於保存實時優先級;0 - MAX_RT_PRIO-1 (0 - 99) |
normal_prio | 值取決於靜態優先級和調度策略; |
prio | 用於保存動態優先級,調度器最終使用的。0 ~ 139(包括 0 和 139) |
1. prio動態優先級:prio 的值是調度器最終使用的優先級數值,即調度器選擇一個進程時實際選擇的值。prio 值越小,表明進程的優先級越高。prio 值的取值範圍是 0 ~ MAX_PRIO,即 0 ~ 139(包括 0 和 139),根據調度策略的不同,又可以分爲兩個區間,其中區間 0 ~ 99 的屬於實時進程,區間 100 ~139 的爲非實時進程;當進程爲實時進程時, prio 的值由實時優先級值(rt_priority)計算得來;當進程爲非實時進程時,prio 的值由靜態優先級值(static_prio)得來。
prio = MAX_RT_PRIO - 1 - rt_priority // 進程爲實時進程
prio = static_prio // 進程爲非實時進程
2. static_prio 靜態優先級:靜態優先級不會隨時間改變,內核不會主動修改它,只能通過系統調用 nice 去修改 static_prio。
通過調用 NICE_TO_PRIO(nice) 來修改 static_prio 的值, static_prio 值的計算方法如下:
static_prio = MAX_RT_PRIO + nice +20
MAX_RT_PRIO 的值爲100,nice 的範圍是 -20 ~ +19,故 static_prio 值的範圍是 100 ~ 139。 static_prio 的值越小,表明進程的靜態優先級越高。
3. normal_prio歸一化優先級:normal_prio 的值取決於靜態優先級和調度策略,可以通過 _setscheduler 函數來設置 normal_prio 的值 。對於非實時進程,normal_prio 的值就等於靜態優先級值 static_prio;對於實時進程,normal_prio = MAX_RT_PRIO-1 - p->rt_priority。
4. rt_priority實時優先級:rt_priority 值的範圍是 0 ~ 99,只對實時進程有效。由式子:
prio = MAX_RT_PRIO-1 - p->rt_priority;
知道,rt_priority 值越大,則 prio 值越小,故 實時優先級(rt_priority)的值越大,意味着進程優先級越高。
rt_priority 的值也是取決於調度策略的,可以在 _setscheduler 函數中對 rt_priority 值進行設置。
-
調度策略 policy;調度類SCHED_CLASS
字段 POLICY | 描述 | 所在調度器類 |
SCHED_NORMAL | (也叫SCHED_OTHER)用於普通進程,通過CFS調度器實現。SCHED_BATCH用於非交互的處理器消耗型進程。SCHED_IDLE是在系統負載很低時使用 | CFS |
SCHED_BATCH | SCHED_NORMAL普通進程策略的分化版本。採用分時策略,根據動態優先級(可用nice()API設置),分配 CPU 運算資源。注意:這類進程比上述兩類實時進程優先級低,換言之,在有實時進程存在時,實時進程優先調度。但針對吞吐量優化 | CFS |
SCHED_IDLE | 優先級最低,在系統空閒時才跑這類進程(如利用閒散計算機資源跑地外文明搜索,蛋白質結構分析等任務,是此調度策略的適用者) | CFS |
SCHED_FIFO | 先入先出調度算法(實時調度策略),相同優先級的任務先到先服務,高優先級的任務可以搶佔低優先級的任務 | RT |
SCHED_RR | 輪流調度算法(實時調度策略),後者提供 Roound-Robin 語義,採用時間片,相同優先級的任務當用完時間片會被放到隊列尾部,以保證公平性,同樣,高優先級的任務可以搶佔低優先級的任務。不同要求的實時任務可以根據需要用sched_setscheduler()API 設置策略 | RT |
SCHED_DEADLINE | 新支持的實時調度策略,針對突發型計算,且對延遲和完成時間高度敏感的任務適用。基於Earliest Deadline First (EDF) 調度算法 | |
調度器類 SCHED_CLASS | 描述 | |
idle_sched_class | 每個cup的第一個pid=0線程:swapper,是一個靜態線程。調度類屬於:idel_sched_class,所以在ps裏面是看不到的。一般運行在開機過程和cpu異常的時候做dump | |
stop_sched_class | 優先級最高的線程,會中斷所有其他線程,且不會被其他任務打斷。作用:1.發生在cpu_stop_cpu_callback 進行cpu之間任務migration;2.HOTPLUG_CPU的情況下關閉任務。 | |
rt_sched_class | RT,作用:實時線程 | |
fair_sched_class | CFS(公平),作用:一般常規線程 |
目前系統中,Scheduling Class的優先級順序爲StopTask > RealTime > Fair > IdleTask
-
RT調度策略和普通進程在調度算法上的差異
Linux的RT調度策略和普通進程在調度算法上面有差異,RT的SCHED_FIFO和SCHED_RR採用的是一個bitmap:
每次從第0bit開始往後面搜索第一個有進程ready的bit,然後調度這個優先級上面的進程執行,所以,在內核裏面,prio數值越小,優先級越高。但是,從用戶態的API裏面,則是數值越大,優先級越高。
下面的代碼,一個線程通過調用API把自己設置爲SCHED_FIFO,優先級50。
這個上面的50,對應內核的49 (從內核的視角上面來看,又會用99減去用戶在chrt裏面設置的優先級)。
如果我們把優先級設置爲51:
這個51,對應內核bitmap上面的48。
所以,你會發現,從用戶的視角來看,數值變大,優先級變高。
對於RT的進程而言,TOP的視角里面的 PR= -1 -用戶視角。
注:只有最高優先級的RT進程,纔在top裏面顯示爲rt。(用戶視角的99---內核bitmap視角的0)
-
普通進程的優先級 nice
普通的講nice的人相對來說比較簡單,我們更關注它的nice值,-20~19之間,nice越低,優先級越高,權重越大,在CFS的紅黑樹左邊的機會大。
你發現.nice爲5的進程,在top命令顯示PR是25。
下面我們看nice是-5的:
它顯示的是PR=15。
由此大家可以發現規律,對於普通的採用CFS策略的NORMAL進程,top裏面的 PR=20+NICE
由此發現,在top裏面,RT策略的PR都顯示爲負數;最高優先級的RT,顯示爲rt。top命令裏面也是,數字越小,優先級越高。
-
ps 中的RTPRIO, PRI,NI
一個是PRI,一個是NI,這到底是什麼東西?相對而言,PRI也還是比較好理解的,即進程的優先級,或者通俗點說就是程序被CPU執行的先後順序,此值越小進程的優先級別越高。那NI呢?就是我們所要說的nice值了,其表示進程可被執行的優先級的修正數值。如前面所說,PRI值越小越快被執行,那麼加入nice值後,將會使得PRI變爲:PRI(new)=PRI(old)+nice。這樣,當nice值爲負值的時候,那麼該程序將會優先級值將變小,即其優先級會變高,則其越快被執行。
到目前爲止,更需要強調一點的是,進程的nice值不是進程的優先級,他們不是一個概念,但是進程nice值會影響到進程的優先級變化。
注:這裏的PRI = 139 - 內核中prio。即該值越大,優先級越高。
-
rt的門限
-
CFS調度
-
怎樣修改進程優先級?
- 實時進程調度
-
非實時進程的調度和動態優先級
-
怎樣查看linux系統中的實時進程和普通進程?
ps -eo state,uid,pid,ppid,rtprio,time,comm
1. RTPRIO
"-": 表示普通進程
"數字": 表示優先級爲xx的實時進程