马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
僵尸进程是 Linux 里很讨厌的一类问题:不及时回收,僵尸会越堆越多,占着 PID 和内核数据结构。更麻烦的是,进程一旦变成僵尸态就"刀枪不入",连 kill -9 也无能为力——谁也没办法杀掉一个已经死掉的进程。解决办法只有一个:让父进程等它、把它回收掉。
一、为什么会僵
子进程运行结束后,父进程没有等待并接收它的退出码和退出状态,操作系统就无法释放对应的内核数据结构与代码数据,于是子进程变成僵尸。
为了杜绝这种情况,父进程可以用函数等待子进程结束,此时父进程处于阻塞状态等待。
注意两点:
- 进程的退出状态是必要的(要不要回收就得看它)。
- 进程的执行结果(算得对不对)是父进程非必要的——结果准不准得自己判断,但父进程必须对子进程负责,别让它连累操作系统。
二、wait()
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
- 返回值:成功返回被等待进程的 pid,失败返回 -1。
- 参数:输出型参数,用来获取子进程退出状态;不关心的话可以写成 NULL。
示例:父进程先 fork 一个子进程跑 5 秒,自己 sleep 6 秒后调用 wait(NULL) 回收,最后父进程退出。
注意:wait 等待的是任意一个进程,不是特指某一个子进程。
三、waitpid()
pid_t waitpid(pid_t pid, int *status, int options);
1. 参数 pid
- pid = -1:等待任一个子进程,与 wait 等效。
- pid > 0:等待进程 ID 与 pid 相等的那个子进程。
- pid = 0:等待与调用进程同进程组的任意子进程。
- pid < -1:等待进程组 ID 等于 pid 绝对值的任意子进程。
2. 返回值
- 正常返回:返回收集到的子进程的进程 ID。
- 设置了
WNOHANG 而调用时并没有已退出的子进程可收集:返回 0。
- 出错:返回 -1,errno 会指示错误原因。
四、怎么取出退出码
(status >> 8) & 0xFF 取退出码,(status & 0x7F) 取终止信号,这两串位运算很难记,系统提供了两个宏来简化:
- WIFEXITED(status):判断进程退出状态,宏为真表示进程正常退出。
- WEXITSTATUS(status):相当于
(status >> 8) & 0xFF,直接拿到退出码。
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid > 0) {
if (WIFEXITED(status))
printf("child exit code: %d
", WEXITSTATUS(status));
else
printf("child quit unusual!
");
}
五、阻塞等待与非阻塞等待
1. 阻塞等待
上面写的都是阻塞等待:子进程没退出,父进程就一直卡在 wait/waitpid 调用处。wait 函数本身只能阻塞等待。
2. 非阻塞等待
非阻塞等待的意思是:父进程执行到 waitpid 时,如果子进程还在跑,父进程不等,直接往下继续执行,通过 while 循环轮询检查。这样一来等待期间父进程还能干自己的事。
只有 waitpid 能通过 options 参数实现非阻塞等待,传 WNOHANG:
waitpid(id, &status, WNOHANG);
返回值含义:
- ret > 0:等待成功,子进程已退出并被回收。
- ret = 0:检测成功,但子进程还没退出,需要下一轮再等。
- ret < 0:等待失败(比如进程已经不存在)。
while (1) {
ret = waitpid(id, &status, WNOHANG);
if (ret == -1) {
printf("wait failed, process not exist
");
break;
} else if (ret == 0) {
printf("child still running, do other work
");
sleep(3);
} else {
printf("wait success, child recycled
");
if (WIFEXITED(status))
printf("exit code: %d
", WEXITSTATUS(status));
break;
}
}
六、实践建议
- 服务型程序里,子进程退出后父进程要能回收,否则长期运行必然堆积僵尸。
- 不确定子进程编号时,用
wait(-1, ...) 或 waitpid(-1, ...) 兜底回收。
- 既要不阻塞主流程又要回收,就用 WNOHANG 轮询,别用 sleep 硬等。
- 发现僵尸已堆积,先查父进程是不是还活着;父进程挂了子进程会被 init 收养并由 init 回收,这之后僵尸通常会自动消失。
qidao123.com企服评测-应用市场 编辑提示 |