如果子进程的退出状态没有被收集,那么这个结束的子进程就会变成僵尸(zombie)进程。
这是一个没有收集状态的demo:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = vfork();
if (pid > 0)
{
while(1)
{
printf("cnt: %d\n", cnt);
printf("This is parent print, pid: %d\n", getpid());
sleep(1);
}
}
else if (pid == 0)
{
while (1)
{
printf("This is child print, pid: %d\n", getpid());
sleep(1);
cnt++;
if (cnt == 3)
{
exit(0);
}
}
}
return 0;
}
编译运行:
这里的pid 3209就的一个僵尸进程(Z+)。
引入wait函数:
/* Wait for a child to die. When one does, put its status in *STAT_LOC
and return its process ID. For errors, return (pid_t) -1.
This function is a cancellation point and therefore not marked with
__THROW. */
extern __pid_t wait (int *__stat_loc);
浅翻译一下:(父进程)等待一个子进程的结束。当有一个结束后,把他的状态放到*STAT_LOC里,同时返回他的进程标识符。
对于这个函数的调用会等待子进程的结束,此时父进程是出于阻塞状态的。
如果一个子进程结束,正等待的父进程会获取其终止状态,则取得改子进程的终止状态立即返回。
如果没有子进程,那么函数就会立刻出错返回。
先不关心形参:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int cnt = 0;
pid = fork();
if (pid > 0)
{
wait(NULL);
while(1)
{
printf("This is parent print, pid: %d\n", getpid());
sleep(1);
}
}
else if (pid == 0)
{
while (1)
{
printf("This is child print, pid: %d\n", getpid());
printf("cnt: %d\n", cnt);
sleep(1);
cnt++;
if (cnt == 3)
{
exit(0);
}
}
}
return 0;
}
编译运行:
可以看到,使用wait函数之后,结束的子进程就们没有再变成僵尸进程了。
考虑形参和返回值:
返回值很好解决,但是这个形参有一些需要注意的问题。直接用一个整型指针承接返回状态后,把它输入出现了错误的结果:exit(3) 之后的打印值却是768。其原因是需要用宏WEXITSTATUS解析。
/* If WIFEXITED(STATUS), the low-order 8 bits of the status. */
#define __WEXITSTATUS(status) (((status) & 0xff00) >> 8)
demo6.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
pid_t pid_exit;
int cnt = 0;
int status = 10;
pid = fork();
if (pid > 0)
{
pid_exit = wait(&status);
printf("child(%d) quit, its status:%d\n", pid_exit, WEXITSTATUS(status));
while(1)
{
printf("This is parent print, pid: %d\n", getpid());
sleep(1);
}
}
else if (pid == 0)
{
while (1)
{
printf("This is child print, pid: %d\n", getpid());
printf("cnt: %d\n", cnt);
sleep(1);
cnt++;
if (cnt == 3)
{
exit(3);
}
}
}
return 0;
}
关于waitpid函数:
waitpid函数和wait函数的最重要的区别就是,waitpid函数有一个选项,可以让调用者不阻塞
/* Wait for a child matching PID to die.
If PID is greater than 0, match any process whose process ID is PID.
If PID is (pid_t) -1, match any process.
If PID is (pid_t) 0, match any process with the
same process group as the current process.
If PID is less than -1, match any process whose
process group is the absolute value of PID.
If the WNOHANG bit is set in OPTIONS, and that child
is not already dead, return (pid_t) 0. If successful,
return PID and store the dead child's status in STAT_LOC.
Return (pid_t) -1 for errors. If the WUNTRACED bit is
set in OPTIONS, return status for stopped children; otherwise don't.
This function is a cancellation point and therefore not marked with
__THROW. */
extern __pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options);
对于waitpid函数中pid参数的作用解释如下:
pid == -1 等待任一子进程。就这一方面而言,waitpid和wait等效
pid > 0 等待其进程ID与pid相等的子进程。
pid == 0 等待其组ID等于调用进程组ID的任一子进程。
pid < -1 等待其组ID等于pid绝对值的任一子进程。
waitpid的option常量
WCONTINUED 若实现支持作业控制,那么由PID指定的任意子进程在暂停后已经继续,但其状态尚未报告,则返回其状态
WNOHANG 若pid指定的子进程并不是立即可用的,则waitpid不阻塞此时返回值为0
WUNTRACED 若某实现支持作业控制,而由pid指定的任意子进程已处于暂停状态,并且其状态自暂停以来还未报告过,则返回其状态WIFSTOPPED宏确定返回值是否对于一个暂停子进程
以上摘自《UNIX环境高级编程》,加粗表示常用。
demo7.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
//pid_t pid_exit;
int cnt = 0;
int status = 10;
pid = fork();
if (pid > 0)
{
//pid_exit = wait(&status);
waitpid(pid, &status, WNOHANG);
printf("Waitting for child(%d)\n", pid);
while(1)
{
printf("This is parent print, pid: %d\n", getpid());
sleep(1);
}
}
else if (pid == 0)
{
while (1)
{
printf("This is child print, pid: %d\n", getpid());
printf("cnt: %d\n", cnt);
sleep(1);
cnt++;
if (cnt == 3)
{
exit(3);
}
}
}
return 0;
}
编译运行:
值得留意的是,这里子进程变成了僵尸进程。
目前了解的就这么多,日后用到再补充。
孤儿进程
就是父进程再子进程结束之前结束,这时的子进程就变成了孤儿进程。Linux系统为了避免存在过多的孤儿进程,会由init进程(pid = 1)收留孤儿进程,变成孤儿进程的父进程。