OS之进程管理---孤儿进程和僵尸进程

僵尸进程

当一个进程终止时,操作系统会释放其资源,不过它位于进程表中的条目还是在的,直到它的父进程调用wait();这是因为进程表中包含了进程的退出状态。当进程已经终止,但是其父进尚未调用wait(),这样的进程叫做僵尸进程(zombie prpcess)。
所有进程终止时都会过度到这种状态,但是一般而言僵尸只是短暂存在。一旦父进程调用了wait(),僵尸进程的进程标示符和它在进程表中的条目就会释放。
这里需要注意:僵尸进程不能通过kill来进行杀死,因为kill是用来终止进程的,僵尸进程已经是终止的。

设计一个僵尸进程:
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <signal.h>


//僵尸进程
int main(void) {
	
	pid_t pid;
	
	printf("before fork pid:%d\n", getpid());
	
	int abc = 10;

	pid = fork();
	if(pid < 0) {
		fprintf(stderr, "fork failed");
		return 1;

	}
	else if(pid == 0) {
		abc++;
		printf("child:%d, parent:%d\n",getpid(), getppid());
		printf("abc:%d", abc);
		exit(0);
	}
	else {
		abc++;
		printf("parent:pid:%d \n", getpid());
		printf("abc:%d \n", abc);
		sleep(20);
	}

	printf("fork after ... \n");

}

执行结果:

shanlei@shanlei-Lenovo-ideapad-110-15ISK:/var/www/c_code/操作系统$ ./2
before fork pid:7203
parent:pid:7203 
abc:11 
child:7204, parent:7203
abc:11fork after ... 
shanlei@shanlei-Lenovo-ideapad-110-15ISK:/var/www/c_code/操作系统$ 

查看运行时的进程表:

F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY        TIME CMD
0 S  1000  7203  5576  0  80   0 -  1127 hrtime pts/0    00:00:00 2
1 Z  1000  7204  7203  0  80   0 -     0 -      pts/0    00:00:00 0
4 R  1000  7205  7084  0  80   0 -  9006 -      pts/1    00:00:00 ps

注意:在进程表中进程状态位于列S,状态位为Z的是僵尸进程,子进程的进程标识符(pid)位于列PID,而父进程的标识符则位于列PPID。

僵尸进程的危害:

如果不调用wait / waitpid的话,保留的那段信息可能会一直存在,那么将会一直占用着进程号,如果系统中存在大量的僵尸进程,将会造成进程号短缺而无法产生新进程。并且很可能造成资源泄露。

如何避免僵尸进程

1.通过信号机制,子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait/waitpid进行处理僵尸进程。代码如下:

#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <signal.h>
static void sig_child(int signo);

//僵尸进程
int main(void) {
	
	pid_t pid;
	
	printf("before fork pid:%d\n", getpid());

	//采用信号量机制
	signal(SIGCHLD, sig_child);

	int abc = 10;

	pid = fork();
	if(pid < 0) {
		fprintf(stderr, "fork failed");
		return 1;

	}
	else if(pid == 0) {
		abc++;
		printf("child:%d, parent:%d\n",getpid(), getppid());
		printf("abc:%d", abc);
		exit(0);
	}
	else {
		abc++;
		printf("parent:pid:%d \n", getpid());
		printf("abc:%d \n", abc);
		sleep(20);
	}

	printf("fork after ... \n");

}

static void sig_child(int signo) {
	pid_t pid;
	int stat;
	while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		printf("child pid=%d terminated.\n", pid);
}

2.或者fork两次,原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。(这种方法不是很理解,所以没有代码…哪位好心人能给我讲一讲吗?)

孤儿进程

如果父进程没有调用wait()就终止了,那么对于该父进程的子进程将会成为孤儿进程(orphan process)。Linux/Unix对这种情况的处理是:将init进程作为孤儿进程的父进程,进程init定期调用wait(),以便收集任何孤儿进程的退出状态,并释放孤儿进程标识符和进程表条目。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

//孤儿进程
int main(void) {

	pid_t pid;
	pid = fork();

	if(pid < 0) {
		fprintf(stderr, "fork failed");
		return 1;
	}
	else if(pid == 0) {
		printf("I am Child, pid=%d, ppid=%d\n", getpid(), getppid());
		sleep(10);
		printf("I am Child, pid=%d, ppid=%d\n", getpid(), getppid());
	}
	else {
		sleep(1);
		printf("I am Parent, pid=%d\n", getpid());
	}

	return 0;
}

执行结果:

shanlei@shanlei-Lenovo-ideapad-110-15ISK:/var/www/c_code/操作系统$ ./3
I am Child, pid=8300, ppid=8299
I am Parent, pid=8299
shanlei@shanlei-Lenovo-ideapad-110-15ISK:/var/www/c_code/操作系统$ I am Child, pid=8300, ppid=1

进程表条目:

F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
4 S     0     1     0  0  80   0 - 56380 -      ?        00:00:09 systemd
....
1 S  1000  8300     1  0  80   0 -  1127 hrtime pts/0    00:00:00 3
4 R  1000  8301  7084  0  80   0 -  9006 -      pts/1    00:00:00 ps

由于子进程sleep了10s,所以父进程终止时,子进程还没有结束,此时将会该子进程成为孤儿进程,可以看到这个时候该孤儿进程的父进程PID为1,也就是所谓的systemd(init)进程。

init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

posted @ 2019-03-19 18:15  如是说  阅读(1193)  评论(0编辑  收藏  举报