第一次作業:基於Linux 2.6 的源碼 分析進程模型
1.前言
本文的內容是基於Linux 2.6的源碼,深入分析進程模型。
- 什麽是進程
- 操作系統是怎麽組織進程的
- 進程狀態如何轉換
- 進程是如何調度的
- 談談自己對該操作系統進程模型的看法
2.什麽是進程
進程是處於執行期的程序以及它所包含的所有資源的總稱,包括虛擬處理器,虛擬空間,寄存器,堆棧,全局數據段等。
Windows10進程如圖所示
3.操作系統是如何組織進程的
一、描述進程——PCB
進程信息被放在一個叫做進程控制塊的結構中,可以理解為進程屬性的集合,稱之為:PCB,在Linux下,PCB是一個叫做task_struct的結構體,這個結構體裏面存放了進程的有關信息。
task_struct結構體的內容分類:
①標識符(PID):描述本進程的唯一的標識符,用來區別其他進程
獲取pid的方法有很多,最推薦的一種就是通過系統調用getpid()來獲取進程的pid,
②狀態:任務狀態,退出碼,退出信號等
狀態分類:R運行狀態(runing) S睡眠狀態(sleeping) D磁盤休眠狀態(Disk sleeping) T停止狀態(stopped) X死亡狀態(dead) Z僵屍狀態(zombie)
③優先級:相對於其他進程的優先級
PRI:進程可執行的優先級,值越小優先級越高
NI:代表nice值,表示進程可被執行的優先級的修正數值
④程序計數器:程序中即將被執行的下一條指令的地址
⑤內存指針:包括程序代碼和進程相關數據的指針,還有和其他進程共享的內存塊的指針
⑥上下文數據:進程執行時處理器的寄存器中的數據
⑦I/O狀態信息:包括顯示的I/O請求,分配給進程的I/O設備和被進程使用的文件列表
⑧記賬信息:可能包括處理器的時間總和,使用的時鐘數總和,時間限制,記賬號等
⑨其他信息
二、組織進程
因為進程需要不斷的關閉和開啟,和數據結構中鏈表的結構很相似,所以運行在系統的進程都以task_struct鏈表的形式存在內核裏面,此時我們就把進程組織起來了。
三、查看進程
現在進程已經被描述和組織起來,那麽有些方法可以讓我們查看進程呢?
①進程信息可以被 /proc 文件查看; 比如:要查看PID(進程的標識符)為1的進程,就需要查看 /proc/1 這個文件夾
②通過系統調用獲取進程標識符:
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main(){ printf("pid:%d\n",getpid()); printf("ppid:%d\n",getppid()); return 0; }
getppid()函數是獲取父進程的標識符,getpid()函數是獲取子進程的標識符
四、創建進程
通過系統調用創建進程,也就是使用fork()函數
#include<stdio.h> #include<sys/types.h> #include<unistd.h> int main(){ pid_t ret = fork(); printf("hello proc:%d,ret = %d\n",getpid(),ret); return 0; }
五、進程的狀態:
R運行狀態(running):表明進程要麽在運行中,要麽在運行隊列裏面
S睡眠狀態(sleeping):意味著進程在等待事件的完成,這個睡眠也叫做可中斷睡眠
D磁盤休眠狀態(Disk sleep):這個狀態的睡眠通常在等待I/O的結束,也叫做不可中斷睡眠
T停止狀態(stopped):可以發送SIGSTOP信號給進程,讓進程停止。也可以發送SIGCONT信號讓進程繼續運行。
X死亡狀態(dead):這個狀態只是一個返回狀態,你不會在任務列表裏面看到這個狀態
Z僵屍狀態(zombie):子進程退出,父進程沒有讀取子進程的退出碼,子進程進入Z狀態
僵屍進程:
#include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> int main(){ pid_t id = fork(); if(id<0){ perror("fork failed!\n"); return 1; } else if (id>0){//這個循環裏面,我們讓父進程sleep30s printf("father pid:%d\n",getpid()); sleep(30); } else {//這個循環裏面,我們讓子進程sleep5s,之後退出 printf("child pid:%d\n",getpid()); sleep(5); exit(EXIT_SUCCESS); } return 0; }
僵屍進程的危害:
①因為進程的狀態必須被維護,退出狀態也屬於進程的基本信息,所以就保存在PCB中,在Z狀態中,子進程不退出,PCB就要一直被維護
②子進程進入僵屍狀態之後會一直占用內存,造成內存的資源浪費
③內存的泄漏
4.進程狀態如何轉換
1. Linux進程狀態有:
TASK_RUNNING : 就緒態或者運行態,進程就緒可以運行,但是不一定正在占有CPU,對應進程狀態的R
TASK_INTERRUPTIBLE:睡眠態,但是進程處於淺度睡眠,可以響應信號,一般是進程主動sleep進入的狀態,對應進程狀態S
TASK_UNINTERRUPTIBLE:睡眠態,深度睡眠,不響應信號,典型場景是進程獲取信號量阻塞,對應進程狀態D
TASK_ZOMBIE:僵屍態,進程已退出或者結束,但是父進程還不知道,沒有回收時的狀態,對應進程狀態Z
TASK_STOPED:停止,調試狀態,對應進程狀態T
2. 進程調度時機:
進程調度會引起進程狀態轉換,由上圖可知如下情況會觸發調度,進程終止或進程睡眠時主動exit或sleep釋放CPU;淺度睡眠的進程被CFS調度選中喚醒,深度睡眠進程由於信號量,鎖等的釋放而被喚醒;進程收到信號量等;還有一種最常見的中斷,異常。
5.進程是如何調度的
Linux進程調度的目標
1.高效性:高效意味著在相同的時間下要完成更多的任務。調度程序會被頻繁的執行,所以調度程序要盡可能的高效;
2.加強交互性能:在系統相當的負載下,也要保證系統的響應時間;
3.保證公平和避免饑渴;
4.SMP調度:調度程序必須支持多處理系統;
5.軟實時調度:系統必須有效的調用實時進程,但不保證一定滿足其要求;
Linux進程優先級
進程提供了兩種優先級,一種是普通的進程優先級,第二個是實時優先級。前者適用SCHED_NORMAL調度策略,後者可選SCHED_FIFO或SCHED_RR調度策略。任何時候,實時進程的優先級都高於普通進程,實時進程只會被更高級的實時進程搶占,同級實時進程之間是按照FIFO(一次機會做完)或者RR(多次輪轉)規則調度的。
實時進程的調度
實時進程,只有靜態優先級,因為內核不會再根據休眠等因素對其靜態優先級做調整,其範圍在0~MAX_RT_PRIO-1間。默認MAX_RT_PRIO配置為100,也即,默認的實時優先級範圍是0~99。而nice值,影響的是優先級在MAX_RT_PRIO~MAX_RT_PRIO+40範圍內的進程。
不同與普通進程,系統調度時,實時優先級高的進程總是先於優先級低的進程執行。知道實時優先級高的實時進程無法執行。實時進程總是被認為處於活動狀態。如果有數個 優先級相同的實時進程,那麽系統就會按照進程出現在隊列上的順序選擇進程。假設當前CPU運行的實時進程A的優先級為a,而此時有個優先級為b的實時進程B進入可運行狀態,那麽只要b<a,系統將中斷A的執行,而優先執行B,直到B無法執行(無論A,B為何種實時進程)。
不同調度策略的實時進程只有在相同優先級時才有可比性:
1. 對於FIFO的進程,意味著只有當前進程執行完畢才會輪到其他進程執行。由此可見相當霸道。
2. 對於RR的進程。一旦時間片消耗完畢,則會將該進程置於隊列的末尾,然後運行其他相同優先級的進程,如果沒有其他相同優先級的進程,則該進程會繼續執行。
,對於實時進程,高優先級的進程就是大爺。它執行到沒法執行了,才輪到低優先級的進程執行。等級制度相當森嚴啊。
非實時進程調度
Linux對普通的進程,根據動態優先級進行調度。而動態優先級是由靜態優先級(static_prio)調整而來。Linux下,靜態優先級是用戶不可見的,隱藏在內核中。而內核提供給用戶一個可以影響靜態優先級的接口,那就是nice值,兩者關系如下:
static_prio=MAX_RT_PRIO +nice+ 20
nice值的範圍是-20~19,因而靜態優先級範圍在100~139之間。nice數值越大就使得static_prio越大,最終進程優先級就越低。
ps -el 命令執行結果:NI列顯示的每個進程的nice值,PRI是進程的優先級(如果是實時進程就是靜態優先級,如果是非實時進程,就是動態優先級)
而進程的時間片就是完全依賴 static_prio 定制的,見下圖,摘自《深入理解linux內核》,
我們前面也說了,系統調度時,還會考慮其他因素,因而會計算出一個叫進程動態優先級的東西,根據此來實施調度。因為,不僅要考慮靜態優先級,也要考慮進程的屬性。例如如果進程屬於交互式進程,那麽可以適當的調高它的優先級,使得界面反應地更加迅速,從而使用戶得到更好的體驗。Linux2.6 在這方面有了較大的提高。Linux2.6認為,交互式進程可以從平均睡眠時間這樣一個measurement進行判斷。進程過去的睡眠時間越多,則越有可能屬於交互式進程。則系統調度時,會給該進程更多的獎勵(bonus),以便該進程有更多的機會能夠執行。獎勵(bonus)從0到10不等。
系統會嚴格按照動態優先級高低的順序安排進程執行。動態優先級高的進程進入非運行狀態,或者時間片消耗完畢才會輪到動態優先級較低的進程執行。動態優先級的計算主要考慮兩個因素:靜態優先級,進程的平均睡眠時間也即bonus。計算公式如下,
dynamic_prio = max (100, min (static_prio - bonus + 5, 139))
在調度時,Linux2.6 使用了一個小小的trick,就是算法中經典的空間換時間的思想,使得計算最優進程能夠在O(1)的時間內完成。
Linux進程狀態機
6.談談自己對操作系統進程模型的看法
進程是操作系統提供的最古老的也是最重要的抽象概念之一,它們將一個單獨的CPU變換成多個虛擬CPU。沒有進程的抽象,現代計算將不復存在。所以進程非常的重要 ,所以要好好地學習進程,以更進一步的學習操作系統的後面的知識。
第一次作業:基於Linux 2.6 的源碼 分析進程模型