进程进程之间是有关系的,例如,父子进程、兄弟进程等。对于子进程而言,其退出时的状态可以由父进程得到。
一、等待进程退出
Linux内核为每个终止的子进程保存一定量的信息,这些信息包括进程ID、进程终止状态以及改进程的一些统计信息等。这些信息可以由父进程得到并做一些处理。
Linux下使用wait()函数得到子进程的结束信息,其原型如下:
#include <sys/wait.h>
pid_t wait(int *statloc);
调用wait()函数的进程会阻塞,直到该进程的任意一个子进程结束,wait()函数会得到结束的子进程的信息并返回该子进程的进程ID,结束信息保存在参数statloc所指向的内存空间中。如果改进程没有子进程,则立即出错返回,返回值为-1;如果在调用wait()函数时已经有若干个子进程结束运行了,则wait()函数立即返回,但是具体得到的是哪个子进程的信息则是不确定的,需要根据子进程的ID来判断。
wait()函数的参数用来保存子进程的返回信息,内核会将取得的子进程结束信息保存在该指针所指向的空间。如果该指针为NULL,则表示用户堆返回信息不关心。
返回信息是一个整数,不同的位代表不同的信息,它们是进程正常结束状态、终止进程的信号编号和暂停进程的信号编号。Linux提供了专门的宏,来判断哪些状态有效并且取得相应的状态值:
状态 | 判断宏 | 取值宏 |
进程正常结束 | WIFEXITED(status) | WEXITSTATUS(status) |
进程异常结束 | WIFSIGNALED(status) | WTERMSIG(status) |
进程暂停 | WIFSTOPPED(status) | WSTOPSIG(status) |
例如,当一个进程正常退出时,改进程的父进程得到其结束信息,需判断:如果WIFEXITED(status)值为真,那么说明该进程是正常退出的,WEXITSTATUS(status)使用返回信息中进程结束状态即可。
下面例子演示了如何使用wait函数取得子进程的结束状态:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
int num, status;
pid = fork();
if(pid < 0){
perror("fail to fork\n");
exit(1);
}else if(pid == 0){ //第一个子进程
printf("the first, exit normally\n");
exit(0);
}else {
if(wait(&status) == -1){ //父进程等待子进程的退出
perror("fail to wait\n");
exit(1);
}
if(WIFEXITED(status) == 1) { //得到正常退出的退出状态,exit()函数的参数
printf("the status of first is : %d\n", WEXITSTATUS(status));
}
}
pid = fork();
if(pid < 0){
perror("fail to fork\n");
exit(1);
}else if(pid == 0){ //第二个子进程
printf("the second, exit abnormally\n");
num = 1/0; //除以0,会产生SIGFPE异常信号
}else{
if(wait(&status) == -1){ //父进程等待子进程退出
perror("fail to wait\n");
exit(1);
}
if(WIFSIGNALED(status) == 1){ //得到终止子进程的信号的值
printf("the terminated signal is : %d\n", WTERMSIG(status));
}
}
return 0;
}
运行结果:
二、等待指定的进程
wait()函数可以等待子进程的退出,并且获得其退出状态信息。但是wait()函数只能等待第一个结束的子进程,如果需要指定等待一个子进程,则需要使用如下代码实现:
int status; //保存进程的状态信息
//pid中保存的是需要的得到的结束信息的进程ID
//如果得到结束状态信息的进程不是所需要的进程,则循环取子进程的结束状态
while(pid != wait(&status));
Linux下提供waitpid()函数,用以等待一个指定的子进程:
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);
waitpid()函数的第一个参数指定要等待的子进程的进程ID,pid参数的作用如下:
pid值 | 等待的作用 |
-1 | 等待任意子进程 |
>0 | 等待进程ID和pid相等的子进程 |
0 | 等待组ID和pid相等的子进程 |
<-1 | 等待组ID等于pid绝对值的组内任意子进程 |
waitpid()函数的第二个参数的意义和wait()函数相同,第三个是控制选项,该选项有3中情况,其中2种和作业控制有关:
选项 | 选项说明 |
WCONTINUED | 当子进程在暂停后继续执行,且其状态尚未报告,则返回其状态 |
WNOHANG | 当所等待进程尚未结束运行时不阻塞,waitpid()函数直接返回 |
WUNTRACED | 当子进程暂停时,并且其状态自暂停以来还未报告过,则返回其状态 |
该参数可以是0,也可以由上面所述的3种选项“或”得到。
下面的实例演示了使用WNOHANG选项非阻塞等待一个子进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid;
pid = fork();
if(pid < 0){
printf("fail to fork\n");
exit(1);
} else if(pid == 0) { //子进程
printf("the child\n");
sleep(3);
exit(0);
} else {
printf("the parent\n");
if(waitpid(pid, NULL, WNOHANG) == 0) //非阻塞等待子进程
printf("the child is not available now\n");
}
printf("no waiting, parent done\n");
return 0;
}
运行结果:
从上面的程序中可以看出父进程并没有阻塞在waitpid()函数上。
waitpid()函数和wait()函数的区别有一下3点:
- waitpid()函数可以指定一个子进程
- waitpid()函数可以不阻塞等待一个子进程
- waitpid()函数支持作业控制
三、输出进程统计信息
wait3()函数和wait4()函数基本等同于wait()函数和waitpid()函数,不同的是wait3()和wait4()函数除了wait()和waitpid()函数的功能外,还能够得到更详细的信息。这些信息CPU使用时间等,其保存在内核中,病通过参数rusage结构返回给用户,函数原型如下:
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/source.h>
pid_t wait3(int *statloc, int options, struct rusage *rusage);
pid_t wait4(pid_t pid, int *statloc, int options, struct rusage *rusage);
参数rusage是一个统计资源结构的指针,其结构声明如下:
struct rusage
{
struct timeval ru_utime; //用户CPU时间
struct timeval ru_stime; //系统CPU时间
long int ru_maxrss; //最大rss数量
long int ru_ixrss; //与其他进程共用代码段数量
long int ru_idrss; //数据段大小
long int ru_isrss; //栈大小
long int ru_minflt; //软页面错误
long int ru_majflt; //硬页面错误
long int ru_nswap; //换页次数
long int ru_inblock; //从文件系统读次数
long int ru_oublock; //向文件系统写次数
long int ru_msgsnd; //发送消息数
long int ru_msgrcv; //接受消息数
long int ru_nsignals; //收到信号数
long int ru_nvcsw; //主动换页次数
long int ru_nivcsw; //被动换页次数
};
读者使用上面结构得出需要的进程信息,例如用户CPU时间加上系统CPU时间可以得到程序占用CPU的时间,再用程序运行总时间减去该时间,就可以得到程序运行时的I/O时间。这样就可以知道程序的性能瓶颈在本身的计算流程上还是在I/O上,由此可以优化程序。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/resource.h>
int main(int argc, char *argv[])
{
pid_t pid;
struct rusage rusage;
pid = fork();
if(pid < 0){
printf("fail to fork\n");
exit(0);
} else if(pid == 0){
printf("the child\n");
exit(0);
} else {
printf("the parent\n");
}
if(wait3(NULL, 0, &rusage) == -1) { //得到改进程的详细信息
perror("fail to wait");
exit(1);
}
printf("utime is %d\n", rusage.ru_utime); //打印用户CPU时间
printf("stime is %d\n", rusage.ru_stime); //打印系统CPU时间
printf("maxrss is %ld\n", rusage.ru_maxrss); //打印最大rss数量
printf("ixrss is %ld\n", rusage.ru_ixrss); //打印与其他进程共用代码段的数量
return 0;
}
运行结果: