在Unix/Linux中一般情況下,子進程是由父進程通過fork函數創建的。但是子進程的結束和父進程的運行時一個異步過程,所以子進程被父進程創建出來以後父進程無法預測子進程什麼時候結束。所以父進程通常會通過調用wait()或者waitpid()系統調用來獲得子進程的終止狀態!
孤兒進程:
顧名思義,孤兒進程是由於他們的父進程結束運行,但是子進程仍在繼續運行,這時候這些子進程會變成孤兒進程,並且這些孤兒進程會被init(1號)進程所收養,並由init進程來完成對他們的狀態收集工作,一般來說孤兒進程對我們沒有什麼危害。
殭屍進程:
當一個進程使用了fork創建了子進程,當子進程結束運行退出時,其父進程沒有調用wait()或waitpid()來獲取子進程的狀態信息時,這些子進程的進程描述符會仍然保存在系統中,我們稱這種進程爲殭屍進程,殭屍進程會對系統帶來危害。
孤兒進程測試
代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
int n = 1;
//如果fork成功,則返回兩次
pid_t pid = fork();
if(-1 == pid)
{
perror("fork:");
exit(-1);
}
if(pid == 0)
{//子進程
while(1)
{
n++;
printf("child pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
exit(0);
}
else
{//父進程
while(1)
{
printf("father pid=%d,n=%d\n",getpid(),n);
sleep(1);
}
}
printf("main ending!\n");
return 0;
}
由圖可知,分別輸出父進程的進程號、變量n的值和子進程的進程號與其父進程的進程號
我們這個時候開啓另一個終端,然後用kill命令殺死父進程
我們這時候可以發現,子進程的父進程號變爲1
並且由上圖可知,子進程與父進程分別屬於兩塊不同的內存空間,所以在子進程中對n進行++運算不會改變父進程中n的值。
殭屍進程測試
代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
pid_t pid;
//循環創建子進程
while (1)
{
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
else if (pid == 0)
{
printf("I am a child process.\nI am exiting.\n");
//子進程退出,成爲殭屍進程
exit(0);
}
else
{
//父進程休眠10s繼續創建子進程
sleep(10);
//輸出進程信息
system("ps -o pid,ppid,state,tty,command");
continue;
}
}
return 0;
}
該代碼中父進程每隔10創建一個子進程,但是父進程中並沒有wait()或者waitpid(),所以這些子進程都會變爲殭屍進程
另外開一個終端可以查到
當父進程退出時
殭屍進程也隨之消失
所以要消滅殭屍進程只需要將父進程退出即可,但是在服務器上一個父進程一般不會退出,這時候要消滅殭屍進程就需要以下方法
殭屍進程解決方法
1.通過信號機制
子進程退出時向父進程發送SIGCHILD信號,父進程處理SIGCHILD信號。在信號處理函數中調用wait進行處理殭屍進程。
測試代碼如下:
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<signal.h>
//處理SIGCHILD信號
void handlesig(int signo)
{
printf("signo=%d\n",signo);
pid_t pid;//記錄終止進程的ID
int status;
//WNOHANG非阻塞等待
while((pid = waitpid(-1,&status,WNOHANG)) > 0)
{
printf("pid=%d,already exited!\n",pid);
}
}
int main()
{
pid_t pid = fork();
//掛載信號,當進程收到SIGCHLD信號時,就調用handlesig函數
signal(SIGCHLD,handlesig);
if(-1 == pid)
{
perror("fork");
exit(-1);
}
if(pid == 0)
{//子進程
printf("pid=%d,ppid=%d\n",getpid(),getppid());
exit(0);
}
else
{//父進程
sleep(10);
printf("pid=%d,ppid=%d\n",getpid(),getppid());
printf("father proccess exited!\n");
while(1)
{
sleep(1);
printf("父進程不退出,測試是否會產生殭屍進程!");
}
}
return 0;
}
如圖可知,並沒有退出父進程,但是也沒有產生殭屍進程。
2.fork兩次
原理如圖:
測試代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
int main()
{
pid_t pid;
//創建第一個子進程
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一個子進程
else if (pid == 0)
{
//子進程再創建子進程
printf("I am the first child process.pid:%d\tppid:%d\n", getpid(), getppid());
pid = fork();
if (pid < 0)
{
perror("fork error:");
exit(1);
}
//第一個子進程退出
else if (pid >0)
{
printf("first procee is exited.\n");
exit(0);
}
//第二個子進程
//睡眠3s保證第一個子進程退出,這樣第二個子進程的父親就是init進程裏
sleep(3);
printf("I am the second child process.pid: %d\tppid:%d\n", getpid(), getppid());
exit(0);
}
//父進程處理第一個子進程退出
if (waitpid(pid, NULL, 0) != pid)
{
perror("waitepid error:");
exit(1);
}
//輸出進程信息
system("ps -o pid,ppid,state,tty,command");
exit(0);
return 0;
}