wait函数
作用:父进程调用这个函数的功能有
一,主动获取子进程的”进程终止状态”
二,主动回收子进程终止后所占用的资源
进程的终止
正常终止
- main函数调用return
- 任意位置调用exit
- 任意位置调用_exit
不管那种方式来正常终止,最终都是通过_exec返回到OS内核。
异常终止:被某个信号终止
- 进程自己调用abort函数,自己给自己发送一个SIGABRT信号终止
- 由别人发的信号进行终止。
进程终止状态
退出状态与”进程终止状态”
严格来说:
return exit _exit 的返回状态称为“退出状态”
return(退出状态)、exit(退出状态)或_exit(退出状态)
当退出状态被_exit函数交给OS内核
OS对其进行加工之后得到的才是“进程终止状态”
父进程调用wait函数便可以得到这个“进程终止状态”
图示:
OS如何加工退出状态
正常终止构建“进程终止状态”
进程终止状态 = 终止原因(正常终止)【使用数字表示】 << 8
|退出状态【进程调用return,exit,_exit返回给内核数字只有低8位有效】的低8位
不管return,exit,_exit返回的返回值由多大,只有低8位有效,所以如果值太大只取低8位。
#include <stdio.h>
int main()
{
return 1000;
}
运行结果为:
我们可以看到返回值为232
1000对应二进制就是:0011 1110 1000
取低8位为:1110 1000
对应十进制:232
正常终止OS会调用正常终止上面的表达式变成”进程终止状态”。
异常终止构建“进程终止状态”
进程终止状态 = 是否产生core文件位|终止原因(异常终止)<< 8 |终止该进程的信号编号
父进程得到”进程终止状态”的用处
判断子进程终止的原因:
如果是正常终止,提取返回值,如果异常终止,提取出异常终止进程的信号编号。
当有OS时,进程return exit _exit 正常终止的时候,所返回的返回值(退出状态)最终通过“进程终止状态返回”返回给了父进程。
父进程可以根据子进程的终止状态来判断子进程的终止原因,返回值等,决定是否重新启动子进程或者做一些其他操作。
父进程如何从内核获得子进程的终止状态
父亲是否获得子进程的终止状态由父进程决定。
1父进程调用wait等子进程函数,如果子进程没有结束,父进程调用wait会一直休眠等待。
2子进程终止返回内核,内核构建“进程终止状态”
(1)如果子进程调用是return,exit,_exit正常终止的,将返回状态返回给内核并根据上面正常终止表达式构建正常终止的“进程终止状态”
(2)如果子进程是某个信号异常终止,内核也会根据返回状态返回给内核并根据上面异常终止表达式构建异常终止的“进程终止状态”
(3)内核向父进程发送SIGCHLD信号,通知父进程子进程结束了,可以获取子进程的“进程终止状态”
(4)如果父进程没有调用wait函数,就会忽略这个信号,表示不关心子进程的“进程终止状态”
一般情况下,父进程都不关心子进程的终止状态。
wait函数
wait函数原型
#include <sys/types.h>
#include <sys/wait.h>
pid_wait(int status);
功能
获得子进程的终止状态,主动释放进程所占用的资源
参数
用于存放“进程终止状态”的缓存
返回值
成功返回子进程的终止状态,失败返回-1,errno被设置
代码演示:
new_process.c文件
#include <stdio.h>
#include <stdlib.h>
//extern char ** environ;
int main(int argc,char **argv,char ** environ)
{
int i = 0;
for(i = 0;i<argc;i++)
{
printf("%s",argv[i]);
}
printf("\n---------------------------------\n");
return 0;
}
通过子进程打印命令行参数。
wait.c文件
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char ** argv)
{
pid_t ret = 0;
ret = fork();
if(ret >0)
{
int status = 0;
wait(&status);
printf("%d\n",status);
}
else if(ret == 0)
{
extern char** environ;
execve("./new_process",argv,environ);
}
return 0;
}
运行结果为:
status返回值为0
当子进程执行新程序之后,子进程的代码即使新程序的代码,新程序就会返回一个返回值,返回到内核之后,内核会构建内核终止状态,最终把进程终止状态返回给wait函数。
我们把子进程代码进程修改不要让子进程退出:
new_process.c文件
#include <stdio.h>
#include <stdlib.h>
//extern char ** environ;
int main(int argc,char **argv,char ** environ)
{
int i = 0;
for(i = 0;i<argc;i++)
{
printf("%s",argv[i]);
}
printf("\n---------------------------------\n");
while(1);
return 0;
}
wait.c文件
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc,char ** argv)
{
pid_t ret = 0;
ret = fork();
if(ret >0)
{
int status = 0;
wait(&status);
printf("%d\n",status);
}
else if(ret == 0)
{
extern char** environ;
execve("./new_process",argv,environ);
}
return 0;
}
运行结果为:
我们进行进程查看:
我们可以看到父子进程都存在,父进程处于休眠状态,子进程处于运行状态。
我们现在终止new_process这个进程:
通过信号把new_process进程异常终止。
终止子进程之后,父进程的wait函数获得一个唤醒信号,wait函数获取到子进程的进程终止状态,我们这里把进程终止状态放在了status。
如何提取“进程终止状态”里面的信息
正常终止:
进程终止状态 = 终止原因(正常终止)【使用数字表示】 << 8
|退出状态【进程调用return,exit,_exit返回给内核数字只有低8位有效】的低8位
异常终止:
进程终止状态 = 是否产生core文件位|终止原因(异常终止)<< 8 |终止该进程的信号编号
我们可以看到上面构建进程终止状态的时候里面就会包括很多信息。
系统提供了相应的带参宏,使用这个带参宏就可以从“进程终止状态”中提取出我们要的信息。
提取原理:
相应屏蔽字&进程终止状态,屏蔽掉不需要的内容,留下来就是需要的信息。
通过帮助手册查询wait函数就可以看到带参宏:
WIFEXITED(status):
提取出终止原因,判断是否是正常终止
(a)如果表达式为真:表示进程是正常终止的
此时使用WEXITSTATUS(status),就可以从里面提取出return/exit/_exit返回的“退出状态”。
(b)为假:不是正常终止的
WIFSIGNALED(status):
提取出终止原因,判断是否是被信号杀死的(异常终止)
(a)如果表达式为真:是异常终止的
此时使用WTERMSIG(status),就可以从里面提取出终止该进程的信号编号。
(b)为假:不是异常终止的。
代码演示:
new_process.c文件
#include <stdio.h>
#include <stdlib.h>
//extern char ** environ;
int main(int argc,char **argv,char ** environ)
{
int i = 0;
for(i = 0;i<argc;i++)
{
printf("%s",argv[i]);
}
printf("\n---------------------------------\n");
while(1);
return 0;
}
wait.c 文件
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char **argv)
{
pid_t ret = 0;
ret = fork();
if(ret > 0)
{
int status = 0;
wait(&status);
printf("status = %d\n", status);
if(WIFEXITED(status))
{
printf("exited:%d\n", WEXITSTATUS(status));
}
else if(WIFSIGNALED(status))
{
printf("signal killed:%d\n", WTERMSIG(status));
}
}
else if(ret == 0)
{
extern char **environ;
execve("./new_process", argv, environ);
}
return 0;
}
运行结果为:
我们可以看到父进程和子进程都开始运行:
父进程处于休眠状态,子进程处于运行状态。
我们让子进程异常退出:
就会显示异常终止并且打印status:
接下来我们演示一种正常终止的情况:
我们把new_process.c文件中的死循环去掉
#include <stdio.h>
#include <stdlib.h>
//extern char ** environ;
int main(int argc,char **argv,char ** environ)
{
int i = 0;
for(i = 0;i<argc;i++)
{
printf("%s",argv[i]);
}
printf("\n---------------------------------\n");
return 0;
}
wait.c文件内容不变
运行结果为:
我们可以看到进程为正常终止。
通过判断终止原因、返回值、信号编号,父进程可以决定是否重新运行子进程,不过大多数情况,父进程不关心子进程是怎么终止的,它的返回值是什么。
wait的缺点
如果父进程fork创建出了好多子进程,wait只能获取最先终止的那个子进程的“终止”状态,其它的将无法获取,如果你想获取所有子进程终止状态,或者只想获取指定子进程的进程终止状态,需要使用函waitpid函数。