在孤儿进程与僵尸进程中介绍了僵尸进程的产生,进程的僵尸态与死亡态区别在于是否回收资源,在Linux系统中应该避免僵尸进程的产生。产生僵尸进程的原因是子进程在退出时,其父进程没有退出,这时父进程并不会主动回收其资源,那么该进程则会成为僵尸进程。
同时,在笔记进程的创建中,创建子进程的示例代码中,可以看出当父子进程没有做任何延时或循环不退出时,则不会产生僵尸进程。这说明了两种可能性:
- 如果子进程先退出,父进程后退出,那么退出的父进程会将子进程的资源回收,那么不会产生僵尸进程
- 如果父进程先退出,子进程成为孤儿进程,孤儿进程退出,资源将会被
init/systemd
进程回收。
这个时候通常处理僵尸进程不能寄希望于将其父进程也退出,这可能会导致父进程不能拥有自由的生命周期。在 Linux
中,通常可以选择 wait()
函数及 waitpid()
函数用来完成对僵尸进程的资源的回收。
wait() 函数
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);点击复制复制失败已复制
wait()
函数被用来执行等待,直到其子进程终止。也就是说 wait()
函数可用于使父进程阻塞,等待子进程退出,一旦子进程退出,则 wait()
函数立即返回,并获得子进程的退出时的状态值,并回收子进程所使用的各种资源,以避免子进程称为僵尸进程。
以下示例展示 wait()
函数的使用:
#include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define errlog(errmsg) \ perror(errmsg); \ printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \ return -1; int main(int argc, const char *argv[]) { pid_t pid; int i = 3; pid = fork(); if (pid < 0) { errlog("fork error"); return -1; } else if (pid == 0) { /* child */ printf("The child precess, id = %d parent id = %d\n", getpid(), getppid()); while (i > 0) { sleep(1); printf("child...\n"); i--; } } else { /* parent */ int status; wait(&status); printf("The parent process, id = %d\n", getpid()); while (1) { } } return 0; }点击复制复制失败已复制
编译运行,结果如下:
$ gcc main.c && ./a.out The child precess, id = 14765 parent id = 14764 child... child... child... The parent process, id = 14764点击复制复制失败已复制
由运行结果可知,父进程使用 wait()
函数执行等到子进程执行 3
秒之后退出,此时 wait()
函数立刻返回,返回值为子进程的 ID
,变为非阻塞,并立即回收子进程的资源。
通过终端输入 $ ps axj
可以明显看到,僵尸进程并没有产生,同时父进程也并没有退出。
waitpid()函数
wait()
函数使用存在诸多限制,而设计 waitpid()
函数则可以突破这种限制。
如果父进程已经创建了多个子进程,使用 wait()
函数将无法等到某个特定的子进程的完成,只能按顺序等待下一个子进程的终止。
如果子进程没有退出,则 wait()
函数总是保持阻塞。故此,使用 wait()
函数只能发现那些已经终止的子进程,而 waitpid()
函数则突破了这种限制。
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);点击复制复制失败已复制
waitpid()
函数被用来关注子进程的状态是否发生变化。这些状态的变化包括子进程终止、子进程被一个信号停止、子进程被一个信号恢复。如果子进程的状态变化为子进程退出,那么 waitpid()
函数可以对子进程的资源进行回收,让子进程的资源得以释放。
waitpid()
函数的参数及返回值相对于 wait()
函数更加复杂,具体如下表所示:
参数 | 含义 |
pid | 占位符 |
<-1, 用于等待进程组中的任意一个子进程(该进程组ID等于pid的绝对值) | |
-1, 用于等待调用进程的任意一个子进程 | |
0, 用于等待进程组中的任意一个子进程(该进程组ID等于调用进程的ID),即调用进程是该进程组的组长 | |
>0, 用于等待子进程ID等于pid的子进程 | |
status | 同wait()函数功能一致,用于接收子进程退出时的状态值 |
options | 0,同wait()函数功能一致,使函数阻塞,等待子进程状态发生改变 |
WNOHANG,执行非阻塞,即如果子进程没有退出,函数本身不阻塞,直接获得返回值为0,此时子进程资源不会被回收。如果此时子进程已经退出,函数同样不阻塞,立刻返回,返回值为子进程的ID,并回收其资源 |
接下来展示 waitpid()
函数使用非阻塞的情况。代码的设计思路如下所示:
该程序的代码具体实现如下所示:
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <stdlib.h> #define errlog(errmsg) \ perror(errmsg); \ printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__); \ return -1; int main(int argc, const char *argv[]) { pid_t pid; pid = fork(); if (pid < 0) { errlog("fork error"); return -1; } else if (pid == 0) { /* child */ printf("The child precess, id = %d parent id = %d\n", getpid(), getppid()); sleep(5); exit(0); } else { /* parent */ int status; pid_t ret; while ((ret = waitpid(pid,&status,WNOHANG))==0) { sleep(1); printf("child has not been exited\n"); } if(ret == pid){ printf("child has been recycled\n"); } printf("The parent process, id = %d\n",getpid()); exit(0); } return 0; }点击复制复制失败已复制
编译并运行,结果如下:
$ gcc main.c && ./a.out The child precess, id = 18891 parent id = 18890 child has not been exited child has not been exited child has not been exited child has not been exited child has not been exited child has been recycled The parent process, id = 18890点击复制复制失败已复制
根据运行结果,当子进程未退出时, waitpid()
函数不阻塞立即返回,获得的返回值为 0
。 5
秒之后,子进程退出,此时执行 waitpid()
函数,捕获子进程退出,并获得其 ID
,回收子进程的资源。