系統編程-回收子進程-孤兒和殭屍進程,wait和waitpid方法

系統編程-回收子進程-孤兒和殭屍進程,wait和waitpid方法

在這裏插入圖片描述

1. 孤兒進程

孤兒進程: 父進程先於子進程結束,則子進程成爲孤兒進程,子進程的父進程成爲init進程,稱爲init進程領養孤兒進程。
危害:沒什麼危害…

產生孤兒進程的舉例:

#include <stdio.h>
#include <unistd.h>

int main(){
	pid_t pid = fork();
    if(pid == 0){// child
		while(1){//強制不讓子進程退出,變爲孤兒進程
       		printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
			sleep(1);
		}
    }else if(pid>0){// parent
        printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
		sleep(5);
		printf("I am parent,I will die!\n");
    }
	return 0;
}

2. 殭屍進程

殭屍進程: 子進程終止,父進程尚未回收,子進程殘留資源(PCB)存放於內核中,此子進程便成爲殭屍(zombie)進程。
注意:殭屍進程是不能使用kill命令清除掉的。因爲kill命令只是用來終止進程的,而殭屍進程已經終止。

產生殭屍進程的舉例:

#include <stdio.h>
#include <unistd.h>

int main(){
	pid_t pid = fork();
    if(pid == 0){// child
       		printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
			sleep(2);
			printf("I am child,I will die!\n");
		}
    }else if(pid>0){// parent
		while(1){//強制不讓父進程退出,子進程變爲殭屍進程
        	printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
			sleep(5);
		}
    }
	return 0;
}

危害:殭屍進程會佔用系統資源,如果很多,則會嚴重影響服務器的性能;
如何解決殭屍進程
①殺死他的父進程使其變成孤兒進程,進而被系統處理。
②wait函數

3. 子進程回收

  一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留着,內核在其中保存了一些信息:如果是正常終止則保存着退出狀態,如果是異常終止則保存着導致該進程終止的信號是哪個。這個進程的父進程可以調用waitwaitpid獲取這些信息,然後徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變量$?查看,因爲Shell是它的父進程,當它終止時Shell調用waitwaitpid得到它的退出狀態同時徹底清除掉這個進程。

3.1 wait

父進程調用wait函數可以回收子進程終止信息。該函數有三個功能:
① 阻塞等待子進程退出
② 回收子進程殘留資源
③ 獲取子進程結束狀態(退出原因)。

wait函數的原型是:

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

  進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成殭屍進程的子進程,wait就會收集這個子進程的信息,並把它徹底銷燬後返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這裏,直到有一個這樣的進程出現爲止

  參數status用來保存被回收進程退出時的一些狀態,如果我們不想知道這個子進程是如何死掉的,只想把它消滅掉的話,那麼我們可以設定這個參數爲NULL,就像下面這樣:

pid_t pid = wait(NULL);

  如果成功,wait會返回被回收子進程的進程ID,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置爲ECHILD
  返回的status只是一個整型變量,不能很精確的描述出狀態,因此需要藉助宏函數來進一步判斷進程終止的具體原因。經常用到的宏函數爲如下兩組:

//正常死亡WIFEXITED
if(WIFEXITED(status)){
	pfintf("%d:"WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
	pfintf("%d:"WTERMSIG(status));
}
  1. WIFEXITED(status) 爲非0 → 進程正常結束

WEXITSTATUS(status) 如上宏爲真,使用此宏 → 獲取進程退出狀態 (return/exit的參數)

  1. WIFSIGNALED(status) 爲非0 → 進程異常終止

WTERMSIG(status) 如上宏爲真,使用此宏 → 取得使進程終止的那個信號的編號。

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
	pid_t pid = fork();
	if(pid==0){
		printf("I am child,will die!\n");
		sleep(2);
		//while(1){
			printf("I am child,Come and hit me!!\n");
			sleep(1);
		//}
		return 101;
	}
	else if(pid>0){
		printf("I am parent,wait for child die\n");
		int status;
		pid_t wpid = wait(&status);
		printf("wait ok,wpid = %d,pid = %d\n",wpid,pid);
		//正常死亡WIFEXITED
		if(WIFEXITED(status)){
			printf("child exit with %d\n:",WEXITSTATUS(status));
		}
		//非正常死亡
		if(WIFSIGNALED(status)){
			printf("child killed by %d:",WTERMSIG(status));
		}
	}
}

子進程正常死亡運行結果:
在這裏插入圖片描述
子進程一直運行時(while(1)),kill掉之後運行結果:
在這裏插入圖片描述

3.2 waitpid

作用同wait,但可指定pid進程清理,可以不阻塞。
waitpid函數的原型是:

#include <sys/types.h>
#include <sys/wait.h>

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

返回值:
成功:返回清理掉的子進程ID
失敗:-1(出錯,無子進程);
返回0值,參3傳WNOHANG,且子進程正在運行。

pid
從參數的名字pid和類型pid_t 就可以看出,這裏需要的是一個進程ID。當pid取不同的值時,在這裏有不同的意義。

  • pid >0時,回收指定ID的子進程,只等待進程ID等於pid的子進程,不管其他已經有多少個子進程運行結束退出了,只要指定的子進程還沒結束,waitpid就會一直等下去;
  • pid = -1時,回收任意子進程(相當於wait,等待任何一個子進程退出,沒有 任何限制;
  • pid = 0時,回收和當前調用waitpid一個組的所有子進程,如果 子進程已經加入了別的進程組,waitpid不會對它做任何理睬;
  • pid < -1時,回收指定進程組內的任意子進程,這個進程組的ID等於pid的絕對值;

options

  • 0:(相當於wait)阻塞回收
  • WNOHANG:非阻塞回收,用輪詢結構回收。

3. wait和waitpid的區別

  • 在一個子進程終止前,wait使其調用者阻塞,而waitpid則提供了非阻塞版本;
  • waitpid等待一個指定的子進程,而wait等待第一個終止的子進程;
  • waitpid支持作業控制(以WUNTRACED選項,由pid指定的任一子進程狀態,且其狀態自暫停以來還未報告過,則返回其狀態);

注意:一次waitwaitpid調用只能清理一個子進程,清理多個子進程應使用循環。

wait循環回收:
在這裏插入圖片描述
運行結果:
在這裏插入圖片描述
waitpid循環回收:
在這裏插入圖片描述
運行結果:
在這裏插入圖片描述

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