Linux编程入门(16)-进程(四)等待子进程

上一篇介绍了进程的创建和退出,以及相关的系统函数。

Linux编程入门(15)-进程(三)编程

这篇主要讲讲,子进程退出后,父进程如何获取其退出状态。

有的应用程序,需要父进程知道子进程何时终止或退出,以及其返回给父进程的状态值信息。

那么,父进程在创建完成子进程后,有没有办法获知子进程的退出状态?答案是肯定的。

Linux 提供了系统函数 wait(),用于检测子进程的终止情况。

系统函数 wait()

系统函数 wait() 主要做两件事:

  • 暂停调用它的进程,直到有子进程退出。
  • 获取子进程结束时传递给 exit() 的值。

Linux编程入门(16)-进程(四)等待子进程_第1张图片

wait() 等待调用进程的任意一个子进程终止,其函数原型为:

#include 
#include 

pid_t wait(int *wstatus);

参数 wstatus 指向的整型变量用于接收子进程的终止状态。

函数执行成功,返回终止的子进程 ID。 失败,则返回 -1。

wait() 详细的执行步骤为:

  1. 如果调用进程没有子进程,那么 wai() 会出错,返回 -1。
  2. 调用此函数的进程,如果有子进程终止,wait() 立即返回。否则,调用进程一直阻塞。
  3. 如果 wstatus 非空,则关于子进程终止的信息会通过 wstatus 指向的整型变量返回。
  4. 将终止进程的 PID 作为 wait() 返回值,并返回。

注意,如果调用者阻塞并且有多个子进程,那么任意一个子进程终止时,wait() 就立即返回

因为 wait() 的返回值是终止进程的 PID,所以父进程能够知晓是哪一个进程终止了。若某个子进程用来完成某项任务,当任务处理完毕后退出,父进程可以通过 wait() 了解到该任务完成了,可以继续其他处理了。

关于进程的结束状态

一个进程的结束,有三种方式:

  • 正常结束。成功调用 exit(0) 或者 return 0
  • 进程出错退出。例如,由于内存耗尽而提前退出程序。程序遇到问题要退出时,可以调用 exit() 函数时传递一个非零值。
  • 被一个信号 kill 掉。信号可能来自键盘、定时器、内核或者其他进程。

父进程如何知道子进程是以何种方式退出的呢?可以通过 wait() 返回的退出状态来进行判断。

在调用 wait() 函数时,给其传递一个整型变量的地址。Linux 内核会将子进程的退出状态存储在这个变量中:

  • 子进程调用 exit() 退出,内核会把 exit 的参数值存放到整数变量中。
  • 子进程被 kill 掉,内核将信号序号存放在这个变量中。

结束状态值的构成

进程退出返回的整型状态值,实际上仅用了最低的 16 位,由 3 部分构成:

  • 8 位记录退出的值(正常退出)。
  • 1 位指明是否发生错误并产生了 core 文件(core dump)
  • 7 位记录信号编号(被信号 kill 掉,异常返回)

Linux编程入门(16)-进程(四)等待子进程_第2张图片

终止状态可以用 中的宏来检测。其中,有 4 个宏可用来取得进程终止的原因:

  • WIFEXITED(status) , 进程正常结束,则为真。
  • WIFSIGNALED(status), 子进程被信号终止,则为真。
  • WIFSTOPPED (status) , 子进程因信号而停止,则为真。
  • WIFCONTINUED (status) , 进程收到 SIGCONT 而恢复执行 , 则为真。

编程示例

编程示例-1

编程实验,实际验证一下,子进程调用 exit() 是如何触发 wait() 返回的处理流程:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 子进程执行函数 */
void child_code(int delay)
{
	printf("child %d here. will sleep %d seconds\n", getpid(), delay);
	sleep(delay);
	printf("child done. exit\n");

	exit(17);
}
/* 父进程执行函数 */
void parent_code(int childpid)
{
	int wait_ret;

	wait_ret = wait(NULL);

	printf("done waiting for %d. Wait returned: %d\n", childpid, wait_ret);
}

int main()
{
	int newpid;

	printf("before: mypid is %d\n", getpid());

	/* 创建新进程,并判断返回值 */
	if((newpid = fork()) == -1)
	{
		perror("fork");
	}
	else if(newpid == 0)
	{
		/* 子进程执行感函数 */
		child_code(2);
	}
	else
	{
		/* 父进程执行感函数 */
		parent_code(newpid);
	}
}

在父进程中,控制流始于程序的开始,在 wait() 的地方阻塞。

在子进程中,控制流始于 main 函数的中部(fork 之后),然后运行 child_code() 函数,最后调用 exit() 结束。

编译,运行结果如下:

$ gcc waitdemo.c -o waitdemo
$ ./waitdemo

before: mypid is 2844
child 2845 here. will sleep 2 seconds
child done. exit
done waiting for 2845. Wait returned: 2845

由程序的执行结果来看,这段程序验证了 wait() 的函数的两个特征:

  • wait() 函数会阻塞调用它的程序,直到子进程结束。
  • wait() 函数会返回结束进程的 PID。

编程示例-2

通过编程来显示子进程的退出状态,代码 waitdemo2.c 如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 子进程执行函数 */
void child_code(int delay)
{
	printf("child %d here. will sleep %d seconds\n", getpid(), delay);
	/* 子进程执行函数 */
	sleep(delay);
	printf("child done. exit\n");

	/* 子进程退出 */
	exit(17);
}

void parent_code(int childpid)
{
	int wait_ret;
	int child_status;
	int high_8, low_7, bit_7;

	wait_ret = wait(&child_status);

	printf("done waiting for %d. Wait returned: %d\n", childpid, wait_ret);

	/* 解析子进程退出状态信息 */
	high_8 = child_status >> 8;
	low_7 = child_status & 0x7F;
	bit_7 = child_status & 0x80;

	printf("status: exit = %d, sig = %d, core %d\n", high_8, low_7, bit_7);
}

int main()
{
	int newpid;

	printf("before: mypid is %d\n", getpid());

	if((newpid = fork()) == -1)
	{
		perror("fork");
	}
	else if(newpid == 0)
	{
		child_code(5);
}
	else
	{
		parent_code(newpid);
	}
}

编译、运行。先看看正常运行情况,退出状态从子进程中获取(exit() 的参数):

$ gcc waitdemo2.c -o waitdemo2
$ ./waitdemo2

before: mypid is 5506
child 5507 here. will sleep 5 seconds
child done. exit
done waiting for 5507. Wait returned: 5507
status: exit = 17, sig = 0, core 0

然后再看看异常结束情况。后台运行 waitdemo2,使用 kill 指令给子进程发送 SIGTERM 信号,内核将该信号编号填充到状态字中:

$ ./waitdemo2 &  /* 后台运行程序 */
before: mypid is 5592
child 5593 here. will sleep 5 seconds

kill 5593  /* 给子进程发送信号 */

done waiting for 5593. Wait returned: 5593
status: exit = 0, sig = 15, core 0        /* 显示状态信息 */

扩展

假如一个进程有多个子进程,只要有一个子进程退出,wait() 就返回。那么要等待一个指定的进程终止(需要直到该进程的 PID),该如何处理?

一种方式是,根据 wait() 返回的进程 ID 与期望的进程 ID 比较。如果终止进程不是所期望的,则再次调用 wait() 函数。

还有一种方式,使用专门的系统函数。Linux 系统提供了一个系统调用,可以用来等待特定的子进程。

该系统函数为 waitpid(), 其原型为:

#include 
#include 

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

参数 pid,表示需要等待的具体子进程,具体含义如下:

  • pid > 0,表示等待进程 ID 为 pid 的子进程。
  • pid = 0,等待与调用进程(父进程)同一个进程组的所有子进程。
  • pid = -1,等待任意进程。
  • pid < -1,等待进程组标识符与 pid 绝对值相等的所有子进程。

可以得知,wait(&status) 等价于 waitpid(-1, &status, 0)

参数 options,是一个位掩码,可以包含如下标志位:

  • WUNTRACED 返回终止子进程的信息和因信号终止的子进程信息。
  • WCONTINUED 返回因收到 SIGCONT 信号而恢复执行的子进程状态信息。
  • WNOHANG 无子进程退出,立即返回,不阻塞父进程。

小结

本文主要介绍了父进程如何等待子进程退出,以及获取进程退出状态相关的内容。

  • 介绍了系统函数 wait() 以及其内部执行的流程。
  • 进程退出状态的构成,以及进程退出状态如何检测。
  • 通过实例演示如何运用 wait() 函数。
  • 扩展介绍了一个可以等待指定进程的系统函数 waitpid()

下次文章,以一个示例来综合运用前面几篇学过的内容,欢迎订阅查看。


公众号【一起学嵌入式】,干货资料首先送达
Linux编程入门(16)-进程(四)等待子进程_第3张图片

你可能感兴趣的:(Linux编程,linux,运维,服务器)