任督二脈之進程管理(1)
進程生命周期,進程生命周期創建、退出、停止,以及僵屍進程是什麽意思。
一、進程的定義
進程--線程。進程是資源分配單位;搞清楚進程就是搞清楚進程資源情況。進程控制塊PCB是OS的通用叫法。task_struct結構體描述進程的資源情況。如下圖所屬:
1)*mm描述內存資源
2)*fs:文件系統資源
3)文件資源:註意與fs的區別,打開文件的fd數組fd_array記錄打開文件的fd
4)*signal:該進程的信號處理函數(用戶理解為多態)
5)pid:數量有限
節選自《linux操作系統原理與應用》:
傳統上,這樣的數據結構(task_struct)叫做進程控制塊PCB。linux中PCB是一個相當龐大的結構體,其域多達80多項,它的所有域按其功能可分為以下幾類:
- 狀態信息 描述進程的動態變化
- 鏈接信息 描述進程的父子關系
- 各種標識符 用簡單數字對進程進行標識
- 進程間通信信息 描述多個進程在同一任務 上的協作工作
- 時間和定時器信息 描述進程在生命周期內使用CPU時間的統計、計費等信息
- 調度信息 描述進程優先級、調度策略等信息
- 文件系統信息 對進程使用文件情況進行記錄
- 虛擬內存信息 描述每個進程擁有的地址空間
- 處理器環境信息 描述進程的執行環境(處理器的寄存器及堆棧等)
二、pid
1)pid數量有限,所以不能無限創建進程:32位-32768 64位-131072
2)fork炸彈的原理
改寫一下代碼
1 :() #函數定義 2 { 3 :|:& #調用自己,然後|管道,管道裏面也遞歸調用:創建進程,然後&後臺執行4 } 5 ; #函數結束 6 : #調用自己
|是管道,&是後臺執行。
一直在創建進程,把pid耗盡,kill、killall等命令也要創建一個進程執行,但是pid已經耗盡,無法執行,用戶感覺系統死掉。
三、task_struct管理
1)形成鏈表:最方便,但是進程之間的關系是樹型關系,父子進程關系,pstree命令可以查看,所以也可以用樹。
zsh@zsh-vm:~$ pstree systemd─┬─ModemManager─┬─{gdbus} │ └─{gmain} ├─NetworkManager─┬─dhclient │ ├─dnsmasq │ ├─{gdbus} │ └─{gmain} ├─VGAuthService ├─accounts-daemon─┬─{gdbus} │ └─{gmain} ├─acpid
2)形成樹:這樣可以反應進程之間的關系,找父子關系比較簡單,但是有時候需要檢索一個進程的pid,比如 kill -2 8934,這種情況下樹檢索比較慢了。使用哈希可以快速查找
3)形成哈希
總結:快速遍歷使用鏈表,想查找父子進程用樹,想通過pid快速查找進程用哈希。所以linux裏面這三種數據結構都有,使得各種場景快速達到目的,以空間換時間。
四、進程生命周期
1)就緒態:fork出來就是就緒態,linux裏面就緒和運行的狀態標誌是一樣的
2)運行態:linux裏面就緒和運行的狀態標誌是一樣的,時間片用完或者被搶占進入就緒態
3)睡眠態:等資源就進入睡眠態,等到資源就進入就緒態
4)僵屍態:進程死掉先成為僵屍,用於描述task_struct及成員還沒有消失,但是進程占用的資源已經消失;需要父進程wait4(waitpid等)等待僵屍才消失(父進程清理子進程)。所以僵屍狀態是很短的。
僵屍態的原因是父進程可以獲取子進程的退出碼exit_code,即退出原因。
例子:殺死子進程,觀察父進程能監控到子進程死亡原因。
1 #include <stdio.h> 2 #include <sys/wait.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 6 int main() 7 { 8 pid_t pid, wait_pid; 9 int status; 10 11 pid = fork(); 12 13 if(pid == -1) 14 { 15 perror("Cannot create new process"); 16 exit(1); 17 } else if (pid == 0) 18 { 19 printf("child process id: %ld\n", (long)getpid()); 20 pause(); 21 _exit(0); 22 } else 23 { 24 #if 0 /* define 1 to make child always a zombie */ 25 printf("ppid: %d\n", getpid()); 26 while(1); 27 #endif 28 do 29 { 30 wait_pid = waitpid(pid, &status, WUNTRACED | WCONTINUED); 31 32 if(wait_pid == -1) 33 { 34 perror("cannot using waitpid function"); 35 exit(1); 36 } 37 38 if(WIFEXITED(status)) 39 { 40 printf("child process exits, status = %d\n", WEXITSTATUS(status)); 41 } 42 43 if(WIFSIGNALED(status)) 44 { 45 printf("child process is killed by signal %d\n", WTERMSIG(status)); 46 } 47 48 if(WIFSTOPPED(status)) 49 { 50 printf("child process is stopped by signal %d\n", WSTOPSIG(status)); 51 } 52 53 if(WIFCONTINUED(status)) 54 { 55 printf("child process resume running...\n"); 56 } 57 58 }while(!WIFEXITED(status) && !WIFSIGNALED(status)); 59 60 exit(0); 61 } 62 }View Code
kill -9 pid是殺不死僵屍進程的,因為僵屍進程已經死掉了。父進程一直不清理僵屍進程,可以通過殺死僵屍進程的父進程來清理。
僵屍進程的資源已經釋放,所以不會造成內存泄漏。
工程中觀察進程是否內存泄漏:多點連續采樣法。震蕩收斂沒有泄漏,震蕩發散(上升)是內存泄漏。
僵屍太多也不好,占用pid。
5)停止態:人為停止進程,發送停止信號:1)ctrl+z,作業控制(JC),發送contine信號繼續運行(fg\bg),kill發送信號等;2)GDB調試;
cpulimit工具控制進程的cpu利用率:cpulimit -l 20 -p pid , 20為允許的cpu利用率,實際結果不會那麽精
確。
五、 fork
1)先看一個例子,fork叉子
結果為打印6個(1*2+2*2):
2)fork返回值:子進程返回0,父進程返回子進程的pid。
運行結果:
父子進程哪個先跑默認不確定,但是有內核可調試開關/proc,傾向於讓子進程先跑。
3)子死父清場:linux裏面總是白發人送黑發人
4)深度睡眠:必須等到資源才能醒,不響應信號,所以kill -9也殺不死。why?major page fault,代碼段命運命中,還在硬盤,進程就進入深度睡眠,如果響應信號,那麽信號處理函數有可能也在硬盤,沒有載入內存,再次發生page fault,嵌套。中斷響應嗎?中斷是正在執行被中斷,已經睡眠了,怎麽中斷。
時鐘中斷也不可以喚醒。
淺度睡眠:資源來了醒,信號來了也可以醒。時鐘中斷可以喚醒。
睡眠是內核調用進入,驅動也可以。用戶態不可以直接調用睡眠。
答疑:getppid()獲取父進程的pid。
書籍:operating system three piecies 全英文
課後作業代碼地址:
任督二脈之進程管理(1)