內容概要
- 一、僵尸行程產生原因
- 二、僵尸行程的危害
- 三、僵尸行程避免
- 3.1、方式一:呼叫wait()/waitpid()函式
- 3.2、方式二:呼叫signal()函式注冊信號SIGCHLD的處理操作
- 3.3、方式三:多次fork()并產生孤兒行程
一、僵尸行程產生原因
??僵尸行程:行程結束,父行程沒有對其資源回收,
??僵尸行程的產生:
1、子行程先于父行程結束
2、父行程不結束/不退出 ?? —>不會被init收養/不會被init回收
3、父行程不執行wait等函式 —>wait等函式用于回收子行程
??1、僵尸行程測驗示例如下所示,
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
/* 定義行程PID記錄的變數 */
pid_t pid = 0;
/* 輸出除錯陳述句 */
printf("Zombie process test!\n");
/* 呼叫fork()函式創建行程 */
pid = fork();
/* 函式回傳值判斷 */
if (pid == -1) /* 回傳錯誤 */
{
perror("fork error");
return -1;
}
else if (pid == 0) /* 子行程 */
{
/* 子行程 */
printf("I'm child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
}
else /* 父行程 */
{
sleep(1); /* 延時片刻,保證子行程先運行 */
printf("I'm parent, return PID = %d, parent pid = %d\n", pid, getpid());
while (1); /* 父行程保持不退出 */
}
return 0;
}
??編譯上述程式并行程運行之后,顯示效果下圖1.2所示,
??此時在另外一個終端可以查看當前行程的狀態,使用指令ps -ajx 查看,顯示效果下圖1.3所示,
??ps工具標識行程的5種狀態碼表示如下所示,
D?不可中斷狀態?uninterruptible sleep (usually IO)
R?運行狀態?runnable (on run queue)
S?中斷狀態?sleeping
T?停止狀態?traced or stopped
Z?僵死狀態?a defunct (”zombie”) process
二、僵尸行程的危害
??unix提供了一種可以保證父行程獲取子行程結束時的狀態資訊的機制,也就是:
??任何一個子行程(init除外)在退出的時候,內核釋放該行程所有的資源,包括打開的檔案,占用的記憶體等,但是仍然會保留一些資訊(包括行程號,退出狀態,運行時間等),稱為僵尸行程(Zombie)的資料結構,直到父行程通過 wait() 或 waitpid() 來取時才被釋放,
??但問題時如果父行程不呼叫 wait() 或 waitpid() 的話, 那保留的資訊就不會被釋放,其行程號就會一直被占用,但是系統所能使用的行程號是有限的,如果大量的產生僵死行程,將因為沒有可用的行程號而導致系統不能產生新的行程,
??綜上所示,僵尸行程的危害一般為:
1、僵尸行程會造成一定的資源浪費,占用不必要的資源
2、當行程id達到了最大值的時候,因為有僵尸行程占用了部分行程id,使得無法再打開新的行程,
??所以,僵尸行程的危害是大大的,是要避免的,
說明:
??任何一個子行程(init除外)在退出之后,并非馬上就消失掉,而是留下一個稱為僵尸行程(Zombie)的資料結構,等待父行程處理,這是每個子行程在結束時都要經過的階段,如果子行程在退出之后,父行程沒有來得及處理,這時用ps命令就能看到子行程的狀態是“Z”,如果父行程能及時處理,用ps命令就可能來不及看到子行程的僵尸狀態,但這并不意味著子行程不經過僵尸狀態,
??
??如果父行程在子行程結束之前退出(即孤兒行程),則子行程將由init接管,init將會以父行程的身份對僵尸狀態的子行程進行處理,
三、僵尸行程避免
3.1、方式一:呼叫wait()/waitpid()函式
??可以通過在父行程中呼叫wait()函式或者waitpid()函式,使得在子行程退出的時刻被父行程回收即可,
??1、wait()和waitpid()函式原型如下所示,
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
pid_t waitpid(pid_t pid, int *wstatus, int options);
??2、wait()和waitpid()函式說明如下所示,
??pid_t wait(int *wstatus)函式說明,
功能:
??wait()函式用于使父行程(也就是呼叫wait()的行程)阻塞,直到一個子行程結束或者該行程接到了一個指定的信號為止,如果該父行程沒有子行程或者他的子行程已經結束,則wait()會立即回傳-1,
引數:
??wstatus:指向的整形物件來保存子行程結束時的狀態,另外子行程的結束狀態可由Linux中一些特定的宏來測定,
回傳值:
??成功:已回收的子行程的行程等
??失敗:-1
??pid_t waitpid(pid_t pid, int *wstatus, int options)函式說明,
功能:
??waitpid()的作用和wait()一樣,但它并不一定等待第一個終止的子行程,waitpid()有若干選項,可提供一個非阻塞版本的wait()功能,實際上wait()函式只是waitpid()函式的一個特例,在Linux內部實作wait()函式時直接呼叫的就是waitpid()函式,
引數:
??pid:引數pid的值有下面幾種情況,分別表示為:
????pid < -1:回收其組ID等于pid的絕對值的任一子行程
????pid = -1:回收任何一個子行程,此時和wait()作用—樣
????pid = 0:回收其組ID等于呼叫行程的組ID的任一子行程
????pid > 0:回收行程ID等于pid的子行程
??wstatus:與wait()函式中引數相同
??options:引數options的值等于或等于以下零個或多個常數,分別為:
????WNOHANG:如果指定的子行程沒有結束,則waitpid()函式不阻塞立即回傳,且回傳值為0
????WUNTRACED:由pid指定的任意子行程如果已經被暫停,且其狀態子暫停以來還沒有報告過,則回傳其狀態
????WCONTINUED(自Linux 2.6.10起) :如果已經停止的子行程通過傳遞的SIGCONT恢復,則回傳其狀態
回傳值:
??> 0:已經結束的子行程的行程號
??= 0:使用選項WNOHANG且沒有子行程退出
??=-1:發生錯誤
??3、wait()和waitpid()函式示例如下所示,
#include <stdio.h>
#include <stdlib.h> /* exit */
#include <sys/types.h> /* waitpid */
#include <sys/wait.h> /* waitpid */
#include <unistd.h> /* getpid等 */
int main(int argc, const char *argv[])
{
/* 定義行程PID記錄的變數 */
pid_t pid = 0;
/* 定義接收行程退出時候的狀態 */
int status = 0;
/* 定義一個函式的回傳值 */
pid_t retval = 0;
/* 輸出除錯陳述句 */
printf("Zombie process test!\n");
/* 呼叫fork()函式創建行程 */
pid = fork();
/* 函式回傳值判斷 */
if (pid == -1) /* 回傳錯誤 */
{
perror("fork error");
return -1;
}
else if (pid == 0) /* 子行程 */
{
/* 子行程 */
printf("I'm child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
/* 子行程延時片刻 */
printf("sleep(5) ...\n");
sleep(5);
/* 呼叫函式退出子行程 */
exit(EXIT_SUCCESS);
}
else /* 父行程 */
{
printf("I'm parent, return PID = %d, my pid = %d\n", pid, getpid());
/************************************************************************
* 情景一:回收任意一個子行程
* 如果不關注子行程退出狀態,則引數status可為NULL,下同
*************************************************************************/
// retval = wait(&status); /* 或者使用 wait(NULL); */
// retval = waitpid(-1, &status, 0); /* 或者使用 waitpid(-1, NULL, 0); */
/************************************************************************
* 情景二:回收行程ID為pid的子行程
*************************************************************************/
// retval = waitpid(pid, &status, 0); /* 或者使用 waitpid(pid, NULL, 0); */
/************************************************************************
* 情景三:組ID等于pid的絕對值的任一子行程
*************************************************************************/
// retval = waitpid(-pid, &status, 0); /* 或者使用 waitpid(-pid, NULL, 0); */
/************************************************************************
* 情景四:組ID等于呼叫行程的組ID的任一子行程
*************************************************************************/
retval = waitpid(0, &status, 0); /* 或者使用 waitpid(0, NULL, 0); */
printf("Parent wait/waitpid OK, status = %d, retval = %d\n", status, retval);
getchar(); /* 父行程保持不退出,方便查閱相關狀態 */
}
return 0;
}
??上面的程式撰寫了 wait() 和 waitpid() 函式的集中情況,有興趣的伙伴可以自行編譯并測驗,本次只測驗目前代碼中有效的部分效果,
??編譯上面的程式并執行,執行效果如下圖1.4所示,
??此時在另外一個終端可以查看當前行程的狀態,使用指令ps -ajx 查看,顯示效果下圖1.5所示,
??4、waitpid()函式使用 非阻塞模式 示例如下所示,
#include <stdio.h>
#include <stdlib.h> /* exit */
#include <sys/types.h> /* waitpid */
#include <sys/wait.h> /* waitpid */
#include <unistd.h> /* getpid等 */
int main(int argc, const char *argv[])
{
/* 定義行程PID記錄的變數 */
pid_t pid = 0;
/* 定義接收行程退出時候的狀態 */
int status = 0;
/* 定義一個函式的回傳值 */
int retval = 0;
/* 輸出除錯陳述句 */
printf("Zombie process test!\n");
/* 呼叫fork()函式創建行程 */
pid = fork();
/* 函式回傳值判斷 */
if (pid == -1) /* 回傳錯誤 */
{
perror("fork error");
return -1;
}
else if (pid == 0) /* 子行程 */
{
/* 子行程 */
printf("I'm child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
/* 子行程延時片刻 */
printf("sleep(5) ...\n");
sleep(5);
/* 呼叫函式退出子行程 */
exit(EXIT_SUCCESS);
}
else /* 父行程 */
{
usleep(20); /* 延時片刻,保證子行程先運行 */
printf("I'm parent, return PID = %d, parent pid = %d\n", pid, getpid());
/**************************************************************
* 使用選項 WNOHANG 設定為非阻塞
* 如果需要回收多個子行程,那么可以使用下面的方式進行逐個回收
**************************************************************/
while ((retval = waitpid(-1, &status, WNOHANG)) == 0)
{
printf("my PID = %d, retval = %d\n", getpid(), retval);
sleep(1);
}
printf("Parent wait/waitpid OK, status = %d, retval = %d\n", status, retval);
getchar(); /* 父行程保持不退出,方便查閱相關狀態 */
}
return 0;
}
??編譯上面的程式并執行,執行效果如下圖1.6所示,
3.2、方式二:呼叫signal()函式注冊信號SIGCHLD的處理操作
??在子行程狀態變化的時候,父行程會收到SIGCHLD信號,Linux系統提供了一個信號處理函式signal(函式原型如下所示),那么想要避免僵尸行程產生,我們可以從此信號入手進行相關的操作,
??1、如果父行程不關注子行程的退出狀態,那么在創建子行程的時候告訴系統,所以在子行程退出之后,系統不再等待父行程行程相關資源的回收,而是直接將此子行程回收,
??2、如果父行程需要關注子行程的退出狀態,但是父行程同時需要處理大量的業務,并且因為 wait() 函式會阻塞,那么可以用 signal() 函式為 SIGCHLD 信號注冊 handler 方法,在父行程收到該信號,在 handler 中呼叫 wait/waitpid 進行回收,
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
引數:
??signum:指定信號的代碼
??handler:有三種可選值
????1、SIG_IGN:忽略改信號
????2、SIG_DFL:采用系統默認的方式進行信號處理
????3、自定義的信號處理函式
回傳值:
??成功:以前的信號處理函式
??錯誤:-1
說明:
????typedef void (*sighandler_t)(int);
??等價于:
????typedef void (*)(int) sighandler_t;
??所以為了便于理解,上面的函式可以寫為:
????sighandler_t signal(int signum, void (*handler)(int));
??該函式第二個引數和回傳值型別都是指向一個無回傳值并且帶一個整形引數的函式的指標,
??那么下面就上述兩種場景分別行程代碼測驗,
?? 1、使用 signal 函式忽略子行程退出的信號,由 init 行程回收子行程的資源,
#include <signal.h> /* signal */
#include <stdio.h>
#include <stdlib.h> /* exit */
#include <sys/types.h> /* waitpid */
#include <sys/wait.h> /* waitpid */
#include <unistd.h> /* getpid等 */
int main(int argc, const char *argv[])
{
/* 定義行程PID記錄的變數 */
pid_t pid = 0;
/* 定義接收行程退出時候的狀態 */
int status = 0;
/* 輸出除錯陳述句 */
printf("Zombie process test!\n");
/* 使用信號,忽略子行程退出的信號 */
signal(SIGCHLD, SIG_IGN);
/* 呼叫fork()函式創建行程 */
pid = fork();
/* 函式回傳值判斷 */
if (pid == -1) /* 回傳錯誤 */
{
perror("fork error");
return -1;
}
else if (pid == 0) /* 子行程 */
{
/* 子行程 */
printf("I'm child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
/* 子行程延時片刻 */
printf("sleep(5) ...\n");
sleep(5);
printf("I'm child, The time has come and I'm going to quit.\n");
/* 呼叫函式退出子行程 */
exit(EXIT_SUCCESS);
}
else /* 父行程 */
{
usleep(20); /* 延時片刻,保證子行程先運行 */
printf("I'm parent, return PID = %d, my pid = %d\n", pid, getpid());
getchar(); /* 父行程保持不退出,方便查閱相關狀態 */
}
return 0;
}
??編譯上面的程式并執行,執行效果如下圖1.7所示,
??此時在另外一個終端可以查看當前行程的狀態,使用指令ps -ajx 查看,顯示效果下圖1.8所示,
?? 2、進行 signal() 函式為 SIGCHLD 信號注冊 handler 方法回收子行程的資源,
注意:
??相對于上面的代碼,在進行 signal() 函式為 SIGCHLD 信號注冊 handler 方法,
需要注意的是,子行程狀態變化的時候父行程會收到SIGCHLD信號,而在子行程中呼叫sleep()函式將行程狀態變為了 睡眠狀態,此時父行程同樣會收到 SIGCHLD 信號,如果此時進行資源回收,可能會產生意想不到的后果,
??綜上所述,測驗程式如下所示,
#include <signal.h> /* signal */
#include <stdio.h>
#include <stdlib.h> /* exit */
#include <sys/types.h> /* waitpid */
#include <sys/wait.h> /* waitpid */
#include <unistd.h> /* getpid等 */
void func_handler(int signo)
{
/* 定義接收行程退出時候的狀態 */
int status = 0;
/* 定義接收函式回傳的狀態 */
pid_t retval = 0;
/* 呼叫waitpid回收子行程,如果使用wait則會阻塞 */
while ((retval = waitpid(-1, &status, WNOHANG)) > 0)
printf("Parent wait/waitpid OK, status = %d, retval = %d\n", status, retval);
}
int main(int argc, const char *argv[])
{
/* 定義行程PID記錄的變數 */
pid_t pid = 0;
/* 輸出除錯陳述句 */
printf("Zombie process test!\n");
/* 使用信號 */
signal(SIGCHLD, func_handler);
/* 創建3個行程行程多行程測驗 */
for (int i = 0; i < 3; i++)
{
/* 呼叫fork()函式創建行程 */
pid = fork();
/* 函式回傳值判斷 */
if (pid == -1) /* 回傳錯誤 */
{
perror("fork error");
return -1;
}
else if (pid == 0) /* 子行程 */
{
unsigned long n = 0, m = 0;
/* 子行程 */
printf("I'm child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
/* 子行程延時片刻 */
printf("sleep(5) ...\n");
/* 呼叫sleep函式會改變行程的狀態并向父行程發送SIGCHLD信號 */
// sleep(5);
/* 此處根據自己真實環境撰寫模擬延時功能,延時時間大概為5秒,可自行調整 */
while (n < 50000)
{
if (m > 100000)
m = 0, n++;
m++;
}
printf("I'm child, The time has come and I'm going to quit.\n");
/* 呼叫函式退出子行程 */
exit(EXIT_SUCCESS);
}
else /* 父行程 */
{
usleep(20); /* 延時片刻,保證子行程先運行 */
printf("I'm parent, return PID = %d, my pid = %d\n", pid, getpid());
}
sleep(1);
}
/* 在終端輸出換行,方便行程除錯資訊的顯示觀察 */
putchar(10);
/* 爆出父行程不退出 */
getchar();
return 0;
}
??編譯上面的程式并執行,執行效果如下圖1.7所示,
??此時在另外一個終端可以查看當前行程的狀態,使用指令ps -ajx 查看,顯示效果下圖1.8所示,
3.3、方式三:多次fork()并產生孤兒行程
??關于孤兒行程,請查看博文:Linux – 多行程編程之 - 基礎實作、孤兒行程
一般情況下,呼叫 fork() 函式兩次,第一次 fork 之后,在子行程中再次進行 fork,這樣在系統中就會存在三個同樣的行程,然后將第一次 fork 之后的子行程(也就是第二次 fork 的父行程)退出,那么第二次 fork 之后的子行程就會變成孤兒行程,作為孤兒行程,就會被 init 行程接管,在行程退出后,init 行程回收其資源,
??但是需要注意的是,第一次 fork 之后的子行程退出后,也是需要回收的,那么此回收作業就需要第一次 fork 的父行程進行回收,不然同樣會產生僵尸行程,
??綜上所述,測驗程式如下所示,
#include <stdio.h>
#include <stdlib.h> /* exit */
#include <sys/types.h> /* waitpid */
#include <sys/wait.h> /* waitpid */
#include <unistd.h> /* getpid等 */
int Create_Second_SubProc()
{
/* 定義行程PID記錄的變數 */
pid_t pid_second = 0;
/* 呼叫fork()函式創建第二代子行程行程 */
pid_second = fork();
if (pid_second == -1) /* 回傳錯誤 */
{
perror("2nd fork error");
return -1;
}
else if (pid_second == 0) /* 子行程 */
{
/* 子行程 */
printf("I'm 2nd child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
/* 子行程延時片刻 */
printf("sleep(5) ...\n");
sleep(5);
printf("I'm 2nd child, The time has come and I'm going to quit.\n");
/* 呼叫函式退出子行程 */
exit(EXIT_SUCCESS);
}
else /* 父行程 */
{
/* 創建成功,此處是一代子行程的代碼 */
printf("I'm 2nd parent, return PID = %d, my pid = %d\n", pid_second, getpid());
/* 呼叫函式退出子行程 */
exit(EXIT_SUCCESS);
}
}
int main(int argc, const char *argv[])
{
/* 定義行程PID記錄的變數 */
pid_t pid_first = 0;
/* 定義接收行程退出時候的狀態 */
int status = 0;
/* 定義接收函式回傳的狀態 */
pid_t retval = 0;
/* 輸出除錯陳述句 */
printf("Zombie process test!\n");
/* 呼叫fork()函式創建新的子行程 */
pid_first = fork();
/* 函式回傳值判斷 */
if (pid_first == -1) /* 回傳錯誤 */
{
perror("1st fork error");
return -1;
}
else if (pid_first == 0) /* 子行程 */
{
/* 子行程 */
printf("I'm 1st child, my PID = %d, my parent PID = %d\n", getpid(), getppid());
/* 子行程延時片刻 */
printf("sleep(5) ...\n");
sleep(5);
/* 呼叫函式再次創建子行程 */
Create_Second_SubProc();
}
else /* 父行程 */
{
usleep(20); /* 延時片刻,保證子行程先運行 */
printf("I'm parent, return PID = %d, my pid = %d\n", pid_first, getpid());
/* 一代子行程退出后,父行程必須要行程回收,不然就成為僵尸行程 */
if (waitpid(pid_first, &status, 0) == pid_first)
{
printf("I'm parent, 1st child wait success. status = %d\n", status);
}
getchar(); /* 父行程保持不退出,方便查閱相關狀態 */
}
return 0;
}
??編譯上面的程式并執行,執行效果如下圖1.7所示,
??此時在另外一個終端可以查看當前行程的狀態,使用指令ps -ajx 查看,顯示效果下圖1.8所示,
??
??好啦,廢話不多說,總結寫作不易,如果你喜歡這篇文章或者對你有用,請動動你發財的小手手幫忙點個贊,當然 關注一波 那就更好了,就到這兒了,么么噠(*  ̄3)(ε ̄ *),
?
?
上一篇:Linux – 多行程編程之 - 基礎實作、孤兒行程
下一篇:Linux – 多行程編程之 - 守護行程
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/357236.html
標籤:其他
