在应用程序设计中,有时候父进程需要知道某个子进程的状态(一般都是获取子进程的退出状态,避免其成为僵尸进程)
监控子进程的手段:
-
wait()函数
及相关系统调用 -
信号
SIGCHLD
和SIGHLD
(两个信号意义是一样的)**
wait()及相关系统调用
wait()系统调用
//头文件
#include <sys/wait.h>
//调用该函数的进程没有子进程终止,调用将一直阻塞,直至某个子进程终止
//有子进程终止,立即返回
pid_t wait(int *wstatus);
//wstatus —— 保存进程终止信息
RETURN VALUE
返回子进程的进程ID,错误返回-1
waitpid()系统调用
//头文件
#include <sys/wait.h>
pid_t waitpid (pid_t pid, int *wstatus, int options);
//pid —— 等待指定的子进程
//pid > 0, pid为等待的子进程的进程号
//pid = 0, 等待调用父进程同一进程组的所有子进程
//pid < -1, 等待进程组标识符与pid绝对值相等的所有子进程
//pid = -1, 等待任意子进程,waitpid(-1, &status, 0)与wait(&status)等价
//wstatus —— 子进程退出状态
//options —— 等待操作选项
// WNOHANG —— 指定的子进程状态没有改变立即返回,不阻塞
// WUNTRACED —— 除了返回终止的子进程信息外,还返回由于信号而终止的子进程信息
// WCONTINUED (since Linux 2.6.10)——返回因受到SIGCONT信号而恢复执行的已终止的子进程的信息
RETURN VALUE
成功时,返回状态已更改的子进程的进程ID;如果指定options为WNOHANG,以及pid指定一个或多个进程,但是没有子进程状态改变,则返回0。当出现错误时,返回-1。
使用以下宏来检查判断子进程退出的原因
WIFEXITED(wstatus) :
正常结束返回true,换句话就是, 调用exit(3) or exit(2)这些,或者在main()中return返回
WEXITSTATUS(wstatus) :
返回子程序的退出状态。它由状态参数中最不重要的8位组成,子元素在exit(3)或exit(2)调用中指定该参数,或者在main()中作为返回语句的参数。只有当WIFEXITED返回true时,才应该使用这个宏。
WIFSIGNALED(wstatus) :
如果子进程被一个信号终止,则返回true。.
WTERMSIG(wstatus) :
返回导致子进程终止的信号编号。只有当WIFSIGNALED返回true时,才应该使用这个宏。
WIFSTOPPED(wstatus) :
如果子进程接收信号终止,返回true;只有当使用WUNTRACED这个options选项进行系统调用,
或者当子进程在调试被跟踪时(参见ptrace(2)),才有可能这样做。
WSTOPSIG(wstatus) :
返回导致孩子停止的信号编号。只有当WIFSTOPPED返回true时,才应该使用这个宏。
WIFCONTINUED(wstatus)(since Linux 2.6.10) :
如果子进程受到SIGCONT信号被恢复执行返回true
//通过idtype and id 参数选择等待的子进程
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
//idtype —— id类型
//idtype == P_PID 等待进程组ID为id进程的子进程
//idtype == P_PGID 等待进程组ID为id所有进程的所有子进程
//idtype == P_ALL 等待任意进程,忽略id参数
//id —— 指定的进程组id
//options —— 操作选项
// WEXITED 等待已经终止的子进程,无论是否正常退出.
// WSTOPPED 等待通过信号终止的子进程
// WCONTINUED 等待先前是终止但被信号SIGCONT唤醒恢复执行的子进程
//options附加的选项:
// WNOHANG 与waitpid()的一样.
// WNOWAIT 返回子进程状态,依然让子进程处于等待状态;稍后的系统调用可用于再次获取子进程状态信息。
//infop —— 子进程信息
//该系统调用执行成功后infop被填充的结构体字段:
//si_pid 子进程ID.
//si_uid 子进程真实用户ID.
//si_signo 总是被设置为SIGCHLD.
//si_status 要么是子程序的退出状态(如exit(2)(或exit(3)),要么是导致子程序终止、停止或继续的信号。si_code字段可用于确定如何解释该字段。
//si_code字段取值:
// CLD_EXITED(子进程调用exit(2)退出);
// CLD_KILLED (子进程被信号杀死)
// CLD_DUMPED (子进程被信号杀死,及脱离内核)
// CLD_STOPPED (子进程被信号终止)
// CLD_TRAPPED(子进程被调试跟踪)
// CLD_CONTINUED(信号SIGCONT恢复子进程继续执行)。
RETURN VALUE
成功或者如果WNOHANG被指定进程组ID为id的子进程状态没有更改返回0,执行成子进程信息被填充到结构体指针infop中;当出现错误时,返回-1
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count;
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
long si_band; /* Band event (was int in
glibc 2.3.2 and earlier) */
int si_fd; /* File descriptor */
short si_addr_lsb; /* Least significant bit of address
(since Linux 2.6.32) */
void *si_lower; /* Lower bound when address violation
occurred (since Linux 3.19) */
void *si_upper; /* Upper bound when address violation
occurred (since Linux 3.19) */
int si_pkey; /* Protection key on PTE that caused
fault (since Linux 4.6) */
void *si_call_addr; /* Address of system call instruction
(since Linux 3.5) */
int si_syscall; /* Number of attempted system call
(since Linux 3.5) */
unsigned int si_arch; /* Architecture of attempted system call
(since Linux 3.5) */
}
等待所有子进程退出:
while( (childPid = wait(NULL)) != -1 )
continue;
wait():
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
pid_t pid = fork();
if (pid == 0)
{
static int cnt = 0;
while(1)
{
cnt++;
printf("child is running %d\r\n", cnt);
if (cnt >= 10)
{
cnt = 0;
exit(0);
}
sleep(1);
}
}
if (pid > 0)
{
int status;
pid_t child_pid;
child_pid = wait(&status); //一直阻塞直到子进程退出
printf("child pid = %d, exit value = %d\r\n", child_pid, status);
}
return 0;
}
waitpid():
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
pid_t pid = fork();
if (pid == 0)
{
static int cnt = 0;
while(1)
{
cnt++;
printf("child is running %d\r\n", cnt);
if (cnt >= 10)
{
cnt = 0;
exit(0);
}
sleep(1);
}
}
if (pid > 0)
{
int status;
int f_cnt = 0;
pid_t child_pid;
while (1)
{
child_pid = waitpid(pid, &status, WNOHANG);
if (child_pid == 0)
{
printf("father is runing %d\r\n", ++f_cnt);
}
else if (child_pid == -1)
{
printf("waitpid error\r\n");
break;
}
else
{
printf("child pid = %d, exit value = %d\r\n", child_pid, status);
break;
}
sleep(1);
}
}
return 0;
}
waitid():
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
int main(int argc, char **argv)
{
pid_t pid = fork();
if (pid == 0)
{
static int cnt = 0;
while(1)
{
cnt++;
printf("child is running %d\r\n", cnt);
if (cnt >= 10)
{
cnt = 0;
exit(0);
}
sleep(1);
}
}
if (pid > 0)
{
siginfo_t info;
memset(&info, 0, sizeof(siginfo_t));
int f_cnt = 0;
int ret;
while (1)
{
if (ret = waitid(P_PGID, getpgid(getpid()), &info, WEXITED | WNOHANG) == -1) //执行失败
{
perror("waitid\r\n");
break;
}
else if (ret == 0) //执行成功
{
if (info.si_pid == 0) //没有子进程状态发生改变
{
printf("no children changed state %d\r\n", f_cnt++);
}
else
{
printf("child pid = %d status = %d\r\n", pid, info.si_status );
}
}
sleep(1);
}
}
return 0;
}
SIGCHLD信号
子进程终止属于异步事件,父进程无法预知子进程何时终止,即使父进程向子进程发送SIGKILL信号。无论子进程何时终止,系统都会向父进程发送SIGCHLD
信号
是信号就默认处理,或忽略,或设置处理程序捕获处理它
问题:
相继多个子进程终止,即使产生多次SIGCHLD
信号,父进程也只能捕获一个,那么就会导致一些子进程成为僵尸进程
解决办法:
在SIGCHLD信号
的处理函数内部以WNOHANG
标志调用waitpid(
)
while(waitpid(-1, NULL, WNOHAND) > 0)
continue;
waitpid()
返回0表明无僵尸子进程存在。返回-1表示发生有错误(可能是ECHILD即没有子进程)