殭屍進程

什麼是殭屍進程?

當一個子進程終止的時候,父進程存在但是沒有調用wait或這waitpid獲取子進程的退出狀態。這樣,這個子進程就不能完全從內存中清楚,從而變成殭屍狀態。

子進程自己爲什麼不完全退出呢?

因爲子進程結束時,父進程可能需要獲取子進程的退出狀態,進程id等信息,這些信息被保存在一個結構體中,父進程通過調用wait或者waitpid可以獲取這些信息,同時把子進程從內存中完全清除。

殭屍進程產生的必要條件?

子進程比父進程更早結束,同時父進程沒有調用wait或者waitpid。

因爲,如果父進程比子進程先結束的話,那麼子進程就變成了“孤兒進程”,然後,孤兒進程(子進程)會被過繼給init進程(1號進程),init進程在系統啓動時會被自動建立,這個init進程的一項功能就是清除過繼給自己的殭屍進程。

產生殭屍進程的例子:

提醒:千萬不要運行此程序!!!

#include <unistd.h>
#include <stdlib.h>

int main(void){
	int i = 0;
	for(;i<100;i++) {
		if(fork()==0){
		}
	}
	while(1);
	return 0;
}
由於本人一時激動,在子進程中沒有調用exit(0)清楚自己,所以造成了大量開闢進程的情況,因爲子進程並沒有返回,反而也在執行fork(),所以本人很不幸的強制關機。

下面這個纔可以測試運行:

#include <unistd.h>
#include <stdlib.h>

int main(void){
	int i = 0;
	for(;i<100;i++) {
		if(fork()==0){
                        exit(0);
		}
	}
	while(1);
	return 0;
}

編譯運行,ps -e查看,發現產生了100個殭屍進程。

root@chenjingui-pc:/home/workspace/unp/echo# ps -e | grep 'zoom <defunct>' | cat -n
     1	 2453 pts/2    00:00:00 zoom <defunct>
     2	 2454 pts/2    00:00:00 zoom <defunct>
此處省掉36個殭屍。。。
    99	 2551 pts/2    00:00:00 zoom <defunct>
   100	 2552 pts/2    00:00:00 zoom <defunct>

如何殺掉已經存在的殭屍進程?

1.不要試圖手動找到這些殭屍進程的id一個一個殺掉。因爲這是殺不掉的。

2。殺掉殭屍進程的父進程,讓這些殭屍進程變成“孤兒進程”,從而過繼給init進程(1號進程),init會清理這些殭屍進程。

如何防止產生殭屍進程?

1.父進程調用wait或者waitpid

因爲子進程結束的時候,會向父進程發送SIGCHLD信號,只需要捕捉此信號然後調用wait或waitpid即可。

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void handle_signal(int sig)
{
	if(sig==SIGCHLD){
		wait(NULL);
	}
}
int main(void){
	int i = 0;
	signal(SIGCHLD,handle_signal);
	for(;i<100;i++) {
		if(fork()==0){
			exit(0);
		}
	}
	while(1);
	return 0;
}
理論上,這樣本應該不會產生殭屍進程了,但是ps -e查看,還有幾個殭屍進程?why???

因爲unix信號一般是不排隊的,這個程序開了100個子進程,而幾乎是在同一時間退出,所以,程序在發生SIGCHLD信號中斷,進而執行handle_signal的時候,如果再有N個(N>1)SIGCHLD信號達到,那麼將會有N-1個SIGCHLD就被拋棄了,也就接下來到來的N個sigchld只按1個算。所以一個SIGCHLD只能激發一次handle_signal,但是N個SIGCHLD信號並不能保證一定激發N次handle_signal(因爲同時到達的SIGCHLD被拋棄了)。

這時候,waitpid隆重登場了。先see代碼

#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

void handle_signal(int sig)
{
	if(sig==SIGCHLD){
		while(waitpid(-1,NULL,WNOHANG)>0);
	}
}
int main(void){
	int i = 0;
	signal(SIGCHLD,handle_signal);
	for(;i<100;i++) {
		if(fork()==0){
			exit(0);
		}
	}
	while(1);
	return 0;
}

waitpid原型:

pid_t waitpid(pid_t pid, int *status, int options);

第一個參數pid可以指定獲取哪一個進程的退出,第二參數status報錯退出狀態,第三個參數指定選項(例如,非阻塞)

當pid爲-1時,那麼這次等待就是只要有子進程退出,此函數就返回。第二個參數傳NULL,表示並不獲取退出狀態,第三個三處指明如果沒有結束了的子進程的話,立即返回。

而該函數的返回值表示退出的進程id。返回值=0表示沒有退出的子進程,=-1表示出錯。

我們接收到一次SIGCHLD信號的時候,將會激發handle_signal函數,如果此時又有一個SIGCHLD到達,那麼肯定說明又一個子進程退出了,而這次在handle_signal函數中循環獲取退出了的子進程的狀態。

例如:有三個子進程A,B,C

首先A進程退出,發出SIGCHLD信號,handle_signal函數被激發。這時可以清理的子進程有A

剛要執行handle_signal函數的時候,B,C進程同時退出了,然後B,C進程也發出SIGCHLD信號,但是信號一般時不排隊的,所以B,C的信號只記一次。

總之,雖然B,C進程發出了兩次SIGCHLD信號,但是卻只激發了一次handle_signal函數的執行,但是handle_signal卻通過循環清楚了所有可以清楚的子進程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章