Linux 系统调用 wait()和waitpid

       一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用waitwaitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用waitwaitpid得到它的退出状态同时彻底清除掉这个进程。

       如果一个进程已经终止,但是它的父进程尚未调用waitwaitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程。任何进程在刚终止时都是僵尸进程,正常情况下,僵尸进程都立刻被父进程清理了,为了观察到僵尸进程,我们自己写一个不正常的程序,父进程fork出子进程,子进程终止,而父进程既不终止也不调用wait清理子进程:

实例:

wait.c

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

int main(void)
{
	pid_t pid=fork();
	if(pid<0) {
		perror("fork");
		exit(1);
	}
	if(pid>0) { /* parent */
		while(1);
	}
	/* child */
	return 0;	  
}

~/SourceCode/learn/C_learn$ ./wait &
[1] 8633

~/SourceCode/learn/C_learn$ ps u
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
liubaoli  7634  0.0  0.0  24648  4368 pts/2    Ss   12月10   0:00 bash
liubaoli  8633 97.7  0.0   4224   776 pts/23   R    15:04   0:03 ./wait
liubaoli  8634  0.0  0.0      0     0 pts/23   Z    15:04   0:00 [wait] <defunct>

在./wait命令后面加个&表示后台运行,Shell不等待这个进程终止就立刻打印提示符并等待用户输命令。现在Shell是位于前台的,用户在终端的输入会被Shell读取,后台进程是读不到终端输入的。第二条命令ps u是在前台运行的,在此期间Shell进程和./a.out进程都在后台运行,等到ps u命令结束时Shell进程又重新回到前台。

父进程的pid是8633,子进程是僵尸进程,pid是8634,ps命令显示僵尸进程的状态为z,在命令行一栏还显示<defunct>。

如果一个父进程终止,而它的子进程还存在(这些子进程或者仍在运行,或者已经是僵尸进程了),则这些子进程的父进程改为init进程。init是系统中的一个特殊进程,通常程序文件是/sbin/init,进程id是1,在系统启动时负责启动各种系统服务,之后就负责清理子进程,只要有子进程终止,init就会调用wait函数清理它。

僵尸进程是不能用kill命令清除掉的,因为kill命令只是用来终止进程的,而僵尸进程已经终止了。

waitwaitpid函数的原型是:

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

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

若调用成功则返回清理掉的子进程id,若调用出错则返回-1。父进程调用waitwaitpid时可能会:

  • 阻塞(如果它的所有子进程都还在运行)。

  • 带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。

  • 出错立即返回(如果它没有任何子进程)。

这两个函数的区别是:

  • 如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。

  • wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。

可见,调用waitwaitpid不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进程终止,起到进程间同步的作用。如果参数status不是空指针,则子进程的终止信息通过这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将status参数指定为NULL

实例:

waitpid.c

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

int main(void)
{
	pid_t pid;
	pid = fork();
	if (pid < 0) {
		perror("fork failed");
		exit(1);
	}
	if (pid == 0) {
		int i;
		for (i = 3; i > 0; i--) {
			printf("This is the child\n");
			sleep(1);
		}
		exit(3);
	} else {
		int stat_val;
		waitpid(pid, &stat_val, 0);
		if (WIFEXITED(stat_val))
			printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
		else if (WIFSIGNALED(stat_val))
			printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val));
	}
	return 0;
}

~/SourceCode/learn/C_learn$ ./waitpid 
This is the child
This is the child
This is the child
Child exited with code 3

子进程的终止信息在一个int中包含了多个字段,用宏定义可以取出其中的每个字段:如果子进程是正常终止的,WIFEXITED取出的字段值非零,WEXITSTATUS取出的字段值就是子进程的退出状态;如果子进程是收到信号而异常终止的,WIFSIGNALED取出的字段值非零,WTERMSIG取出的字段值就是信号的编号。

猜你喜欢

转载自blog.csdn.net/EMH1899/article/details/84973138
今日推荐