僵尸進程是什么?
- 僵尸進程的定義:對于多進程程序,當子進程結束運行但父進程還未讀取其退出狀態時,子進程就處于僵尸態。此時,內核不會立即釋放該子進程的進程表表項,以滿足父進程后續查詢子進程退出信息的需求
- 產生原因:子進程運行結束后,父進程若沒有及時獲取其退出狀態,子進程就會一直處于僵尸態;當父進程提前結束或異常終止,而子進程繼續運行時,子進程的 PPID(父進程 ID)會被操作系統設置為 1,即由 init 進程接管。在父進程退出后到子進程退出前這段時間,子進程也處于僵尸態。
- 僵尸進程的危害:僵尸進程會一直占據內核資源,而內核資源是有限的。如果大量產生僵尸進程,可能會導致系統資源耗盡,影響系統性能。
處理僵尸進程的函數
????????pid_t wait(int *stat_loc)
????????wait
?函數會阻塞調用它的進程(通常是父進程),直到該進程的任意一個子進程結束運行。這意味著調用?wait
?后,父進程會暫停執行,等待子進程完成任務。
????????當有子進程結束時,wait
?函數返回結束運行的子進程的進程 ID(PID),同時將該子進程的退出狀態信息存儲在?stat_loc
?參數指向的內存位置。退出狀態信息包含了子進程是如何結束的,例如是正常退出還是因信號終止等。
????????通過?sys/wait.h
?頭文件中定義的宏來解析?stat_loc
?中的退出狀態信息:
WIFEXITED(stat_val)
:用于判斷子進程是否正常結束。如果子進程正常結束,該宏返回一個非零值(即真)。WEXITSTATUS(stat_val)
:當?WIFEXITED
?返回非零值時,使用此宏可以獲取子進程的退出碼。子進程通過?exit
?函數或從?main
?函數返回時設置的退出碼,可以通過這個宏獲取。WIFSIGNALED(stat_val)
:如果子進程是因為一個未捕獲的信號而終止,此宏返回一個非零值。WTERMSIG(stat_val)
:當?WIFSIGNALED
?返回非零值時,該宏返回導致子進程終止的信號值。例如,如果子進程因接收到?SIGTERM
?信號而終止,WTERMSIG
?將返回?SIGTERM
?的值。WIFSTOPPED(stat_val)
:若子進程被暫停(例如收到?SIGSTOP
?信號),此宏返回一個非零值。WSTOPSIG(stat_val)
:當?WIFSTOPPED
?返回非零值時,該宏返回導致子進程暫停的信號值。
? ? ? ? 示例代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid;int status;pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子進程printf("Child process: My PID is %d\n", (int)getpid());exit(10); // 子進程正常退出,退出碼為10} else {// 父進程pid_t terminated_pid = wait(&status);if (terminated_pid == -1) {perror("wait");return 1;}if (WIFEXITED(status)) {printf("Child %d exited normally with exit code %d\n", (int)terminated_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child %d terminated by signal %d\n", (int)terminated_pid, WTERMSIG(status));}}return 0;
}
? ? ? ? 輸出如下:
????????pid_t waitpid(pid_t pid, int *stat_loc, int options)
? ?waitpid
?函數提供了比?wait
?函數更靈活的等待方式。它可以等待由?pid
?參數指定的特定子進程。
pid > 0
:等待進程 ID 為?pid
?的子進程。pid = -1
:等待任意一個子進程,此時?waitpid
?的行為和?wait
?函數相同。pid = 0
:等待與調用進程同組的任意子進程。pid < -1
:等待進程組 ID 等于?pid
?絕對值的任意子進程。
????????與?wait
?函數類似,waitpid
?函數返回結束運行的子進程的 PID,并將子進程的退出狀態信息存儲在?stat_loc
?參數指向的內存位置,同樣可以使用?sys/wait.h
?中的宏來解析退出狀態。
????????options
?參數可以控制?waitpid
?函數的行為。最常用的取值是?WNOHANG
,當?options
?取值為?WNOHANG
?時,waitpid
?調用將是非阻塞的。如果?pid
?指定的目標子進程還沒有結束或意外終止,則?waitpid
?立即返回 0;如果目標子進程確實正常退出了,則?waitpid
?返回該子進程的 PID。若?waitpid
?調用失敗,返回 -1 并設置?errno
? ?示例代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid;int status;pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子進程printf("Child process: My PID is %d\n", (int)getpid());sleep(2); // 模擬子進程執行一些任務exit(10); // 子進程正常退出,退出碼為10} else {// 父進程while (1) {pid_t terminated_pid = waitpid(pid, &status, WNOHANG);if (terminated_pid == -1) {perror("waitpid");return 1;} else if (terminated_pid == 0) {// 子進程還未結束printf("Child is still running...\n");sleep(1);//模擬父進程處理自己的任務} else {if (WIFEXITED(status)) {printf("Child %d exited normally with exit code %d\n", (int)terminated_pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child %d terminated by signal %d\n", (int)terminated_pid, WTERMSIG(status));}break;}}}return 0;
}
? ? ? ? 輸出如下:
?
? ? ? ? 講解一下示例代碼:采用的是輪詢的方式檢測子進程是否已經退出,如果未退出waitpid()會返回0,進行下次輪檢測;如果子進程退出了,會打印子進程的退出碼。這樣做的好處是,父進程不必阻塞等待子進程退出,它可以邊等待邊處理自己的任務。
處理僵尸進程更好的方式:利用SIGCHLD信號
? ? ? ? 不斷的輪詢子進程的狀態,絕非明智之舉。要在事件(當然了這里的事件就是子進程退出)已經發生的情況下,執行非阻塞調用才能提高程序的效率。
????????當一個進程結束時,它會給其父進程發送一個SIGCHLD
信號。因此,父進程可以捕獲這個信號,并在信號處理函數中調用waitpid
函數來處理結束的子進程,從而避免僵尸進程的產生。也就是說:SIGCHLD信號觸發,相當于通知父進程,你的子進程已經退出了。
? ?示例代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>// SIGCHLD信號處理函數
static void handle_child(int sig) {pid_t pid;int stat;// 使用waitpid循環處理已結束的子進程while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {if (WIFEXITED(stat)) {printf("Child %d exited normally with exit code %d\n", (int)pid, WEXITSTATUS(stat));} else if (WIFSIGNALED(stat)) {printf("Child %d terminated by signal %d\n", (int)pid, WTERMSIG(stat));}}
}int main() {pid_t pid;// 注冊SIGCHLD信號處理函數struct sigaction sa;sa.sa_handler = handle_child;sigemptyset(&sa.sa_mask);sa.sa_flags = SA_RESTART;if (sigaction(SIGCHLD, &sa, NULL) == -1) {perror("sigaction");exit(1);}// 創建子進程pid = fork();if (pid == -1) {perror("fork");exit(1);} else if (pid == 0) {// 子進程printf("Child process: My PID is %d\n", (int)getpid());sleep(2); // 模擬子進程執行一些任務exit(10); // 子進程正常退出,退出碼為10} else {// 父進程printf("Parent process: My PID is %d, Child PID is %d\n", (int)getpid(), (int)pid);// 父進程可以繼續執行其他任務while (1) {printf("Parent is doing other things...\n");sleep(1);}}return 0;
}
? ? ? ? 輸出結果如下:
????????對于初學者來說看這段代碼有些吃力,建議大家在具備了進程信號的知識的前提下再進行閱讀。
什么是孤兒進程?
????????孤兒進程指的是父進程在子進程之前終止,導致子進程失去了父進程的管理和控制。此時,這些子進程會被 init 進程(進程 ID 為 1)收養。init 進程是 Linux 系統啟動后創建的第一個用戶態進程,它負責管理系統中所有孤兒進程的生命周期。
????????如果父進程執行完畢或因異常情況提前終止,而它創建的子進程還在運行,這些子進程就會成為孤兒進程。
? ? ? ? 寫段代碼舉個例子:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid = fork();if (pid == -1) {perror("fork");return 1;} else if (pid == 0) {// 子進程printf("Child process: My PID is %d, My PPID is %d\n", (int)getpid(), (int)getppid());sleep(5); // 模擬子進程執行任務printf("Child process: After sleeping, My PPID is %d\n", (int)getppid());} else {// 父進程printf("Parent process: My PID is %d, Child PID is %d\n", (int)getpid(), (int)pid);// 父進程提前結束return 0;}return 0;
}
? ? ? ? 輸出結果如下: