Zombies、Wait
- 前言
- 一、僵尸行程
- 二、行程等待
- 總結
前言
fork函式:從已存在行程中創建一個新行程,新行程為子行程,而原行程為父行程,fork之后通常要用if分流,回傳值為0,則為子行程,回傳值為大于0(暨為創建出子行程的pid),則為父行程,回傳值小于0則創建子行程失敗,父子行程的pcb中有相同的代碼段,因此通過分流來讓父子行程執行不同代碼,



Q1:子行程到底是從fork這行代碼開始運行,還是從fork之后的代碼開始運行?
A1: 因為父行程在執行完畢fork指令之后,程式計數器(pcb中)當中保存的是fork之后的指令,子行程拷貝父行程的pcb,因此也將程式計數器拷貝過去,所以從fork之后的執行開始運行,
Q2:子行程先運行or父行程先運行?
A2: 搶占式運行(cpu少,行程數量多造成的并發情況),
提示:以下是本篇文章正文內容
一、僵尸行程
僵尸狀態:當行程退出并且父行程沒有讀取到子行程退出的回傳代碼時就會產生僵尸行程,僵尸行程會已終止狀態“Z”保留在行程表中,并且會一直在等待父行程讀取退出狀態代碼,
僵尸行程:當子行程比父行程先結束,而父行程又沒有回收子行程,釋放子行程占用的資源,此時子行程將成為一個僵尸行程,
模擬場景:父行程死回圈,子行程退出,
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 int i = 10;
7 pid_t ret = fork();
8 if(ret < 0)
9 {
10 perror("fork");
11 return -1;
12 }
13 else if(ret == 0)
14 {
15 i+=10;
16 printf("我是子行程,pid是:%d,i = %d\n",getpid(),i);
17 printf("我的父行程pid是:%d\n",getppid());
18 }
19 else
20 {
21 while(1)
22 {
23 printf("我是父行程,pid是%d, i = %d\n", getpid(),i);
24 printf("父行程的父行程pid是%d\n",getppid());
25 sleep(1);
26 }
27 }
28 return 0;
29 }

getpid():獲取行程的pid;
getppid():獲取父行程的pid,
ps aux:查看行程命令
ps -ef:查看父子關系
kill 【pid】:殺死該行程
kill -9 【pid】: 強殺這個行程
在執行之后:
可以看出子行程先于父行程退出,父行程并未回收其資源,說明在作業系統內核中子行程的pcb依然存在而并未被釋放,子行程變為僵尸行程而且無法被kill命令殺掉(因為無法殺掉一個已經死去的行程),
僵尸行程的危害:
占用作業系統的資源,記憶體泄漏,
如何解決僵尸行程問題?
1.重啟作業系統(不推薦);
2.殺掉父行程,why?殺掉父行程后子行程變為孤兒行程然后被1號init行程領養,最后被1號行程回收(不推薦);
3.行程等待,
二、行程等待
父行程等待子行程退出,獲取退出子行程回傳值,釋放子行程資源,避免產生僵尸行程,

pid_t wait(int * status)
等待任意一個子行程退出,通過status獲取退出回傳值,釋放資源,(默認為一個阻塞等待介面——為了完成一個功能發起的呼叫介面,若功能完成條件不具備(例如子行程沒退出),一直等待)
引數:
status(傳址操作)用來獲取子行程退出狀態,不關心可設定為NULL,
回傳值:成功則回傳退出子行程的pid;錯誤(例如沒有子行程)則回傳-1,
pid_t waitpid(pid_t pid, int * status, int options)
可以等待任意一個子行程退出,也可以等待指定的子行程退出,
引數
pid若為-1,則為等待任意子行程,若大于0,則等待指定子行程;
status(傳址操作)用來獲取子行程退出狀態,不關心可設定為NULL,內部有兩個分支:1.WIFEXITED(status)獲取例外退出信號;
2.WEXITSTATUS(status)獲取退出狀態資訊;
options:默認為0為阻塞等待;若設定為WNOHANG則為非阻塞等待——(若完成條件不具備,立即報錯回傳)
**回傳值:**大于0表示退出子行程的pid;等于0表示無子行程退出;錯誤回傳-1.
來個例子說明一下:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <stdlib.h>
4 #include <sys/wait.h>
5
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid<0)
10 {
11 perror("fork");
12 return -1;
13 }
14 else if(pid == 0)
15 {
16 printf("子行程將在5秒后退出\n");
17 sleep(5);
18 exit(99);
19 }
20 while(1)
21 {
22 printf("父行程還活著\n");
23 sleep(1);
24 }
25 }
我們先不用wait介面,執行五秒后子行程退出成為僵尸行程,

然后我們加上wait(NULL),此時為阻塞等待子行程退出,

然后我們將wait介面換位waitpid(-1,NULL,0),此時完全等價于wait(NULL),

設為非阻塞之后(保持其他代碼不變)

然后對waitpid介面做回圈處理

然后我們再來看一下status里面接收的退出狀態資訊,因為我們代碼里給的子行程退出為eixt(99),
5
6 int main()
7 {
8 pid_t pid = fork();
9 if(pid<0)
10 {
11 perror("fork");
12 return -1;
13 }
14 else if(pid == 0)
15 {
16 printf("子行程將在5秒后退出\n");
17 sleep(5);
18 exit(99);
19 }
20 //wait(NULL);
21 int ret, status;
22 while((ret= waitpid(-1,&status,WNOHANG)) == 0)
23 {
24 printf("現在沒有子行程退出,再等一秒試試\n");
25 sleep(1);
26 }
27 if(ret > 0)
28 {
29 printf("退出子行程的pid為:%d ; 退出狀態資訊為:%d\n", ret, status);
30 }
31 while(1)
32 {
33 printf("父行程還活著\n");
34 sleep(1);
35 }
36 }

通過上圖我們可以得出status這個整型中,高十六位不使用,低十六位中的高八位(一個位元組)保存的是退出狀態的資訊,
而實際中低八位保存了兩個東西,第七位保存了core dump標志,第0-6位保存了終止信號(例如11號信號為段錯誤),如下圖,

若想查看例外回傳信號值:status & 0x7f;對應上文的WIFEXITED(status)
若想查看退出狀態資訊:status >> 8; status & 0xff;對應上文的WEXITSTATUS(status)
注:上面這兩個介面,如果在呼叫之前已經有子行程退出,則直接進行處理并回傳退出資訊,不再等待,
總結
僵尸行程會造成資源泄漏,處理僵尸行程可以退出父行程,最好的避免僵尸行程的方法是:行程等待,
番外:
孤兒行程
產生:父行程先于子行程退出,子行程成為孤兒行程,
特性:運行在后臺,父行程成為1號行程,
守護行程:特殊的孤兒行程,產生孤兒行程后設定新的會話(setsid),運行在后臺,脫離與終端的關系,脫離登錄會話,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/271581.html
標籤:python
上一篇:用python實作爬取CSDN熱門評論URL并存入redis
下一篇:Java學習筆記10
