【Linux详解】进程等待 | 非阻塞轮询

[复制链接]
发表于 2026-10-3 15:41:22 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

Linux 进程等待:wait、waitpid 与 status 位图是怎么回事

父进程 fork 出子进程,如果不管不顾直接退出,子进程就变成僵尸进程——刀枪不入杀不死,还会占着内存。进程等待就是解决这个问题的:让父进程暂停自己,直到某个子进程结束。这篇把 wait / waitpid / status 一条线讲透。

一、为什么需要进程等待

  • 僵尸进程杀不掉:僵尸状态(Z)的进程占着 task_struct,只能由父进程回收,存在内存泄漏。
  • 想知道任务完成得怎么样:可选,但实战经常需要——子进程跑了什么、退出码是多少。

二、wait 函数

#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *status);

2.1 一个观察样例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

int main(void)
{
  pid_t id = fork();
  if (id == 0)
  {
    // 子进程:每秒打印一次
    while (1)
    {
      printf("我是子进程,我正在运行... Pid:%d\n", getpid());
      sleep(1);
    }
  }
  else
  {
    printf("我是父进程:pid:%d,我将耐心地等待子进程!\n", getpid());
    sleep(20);              // 便于观察,先休眠 20 秒

    pid_t ret = wait(NULL); // status 先置 NULL
    if (ret < 0)
      printf("等待失败!\n");
    else
      printf("等待成功!\n"); // 此时子进程从 Z 状态被回收

    sleep(20);              // 回收后再让父进程存在一会儿
  }
  return 0;
}

执行流程:父进程打印一句后休眠 20 秒;子进程每秒打印一次;用 kill 5678 之类方式停掉子进程;20 秒后父进程苏醒,调 wait 回收,打印"等待成功";之后再存在 20 秒才退出。想要观察得全,就让父进程"晚点死"。

三、waitpid 才是主力

wait 功能太简单,实战里更多用 waitpid。wait 其实是 waitpid 的一个子功能。

3.1 参数

pid_t waitpid(pid_t pid, int *status, int options);
  • pid:要等待的子进程 ID。
    • pid > 0:只等待这个指定 ID 的子进程。
    • pid == -1:等待任意子进程结束,等同 wait。
    • 此外还可指定同进程组、同会话的子进程。
  • status:整型指针,输出型参数,用来存储子进程的退出状态。
  • options:指定等待行为的附加选项,传 0 就是默认阻塞等待。

3.2 返回值

  • 成功:返回已终止子进程的进程 ID。
  • 出错:返回 -1,并设置 errno 指示具体原因。

3.3 底层发生了什么

僵尸状态本质上就是子进程把自己的 task_struct 维护起来(代码可以释放,结构体必须留着)。wait/waitpid 拿到的退出信息,实际就是从子进程的 task_struct 里取出退出码、拷回父进程。父进程在用户态不能直接读子进程的 pcb,必须走系统调用——操作系统不相信任何人。

四、status 位图怎么解析

status 是输出型参数,不是整体用的,取自整数的低 16 位,又分最低八位和次低八位。

4.1 次低八位:取子进程退出码

pid_t id = fork();
if (id == 0)
{
  int cnt = 5;
  while (1)
  {
    printf("我是子进程,我正在运行... Pid:%d\n", getpid());
    sleep(1);
    if (--cnt == 0) break;
  }
  exit(233); // 退出码故意设成 233,方便验证
}
else
{
  int status = 0;
  pid_t ret = waitpid(id, &status, 0);
  if (ret > 0)
    printf("等待成功,ret:%d, 子进程退出码:%d\n", ret, (status >> 8) & 0xFF);
}

status 右移八位再与 0xFF 按位与,就取出了次低八位的退出码。要用位操作解析,不能整体当数字看。

4.2 最低七位:取子进程退出信号

前面讲次低八位都是关于进程正常退出(exit)。如果是异常退出呢?情况要分开处理:进程被信号打掉时,status 里记录的是终止信号而非退出码。

4.3 初识 core dump(核心转储)

core dump 是操作系统在进程收到某些信号而终止运行时,把此时进程地址空间的内容以及进程状态相关信息写出到一个磁盘文件。它主要用于调试——进程为什么挂了,core 文件一看便知。

五、常见坑

  • 父进程不 wait 就是僵尸:子进程退出后父进程还在跑,子进程保持 Z 状态;父进程退出前一定要回收,否则长期运行的服务会累积僵尸。
  • 一次 wait 只回收一个:多个子进程要循环 waitpid 依次回收,别只调一次。
  • waitpid 传 0 阻塞:想非阻塞轮询要用 WNOHANG,配 while 循环,不然父进程会被子进程的节奏绑死。
  • 误把 status 整体当退出码:直接用 status 打印会得到 59391 之类莫名其妙的数,必须 (status >> 8) & 0xFF。

小结

进程等待就一句话:父进程 wait 或 waitpid 回收子进程,顺带把退出码取回来。waitpid 的 pid 参数决定等谁、status 拿退出信息(次低八位退出码、最低七位信号)、options 定阻塞还是轮询。回收僵尸是硬需求,不回收就是内存泄漏,长期跑的服务尤其要盯。

qidao123.com企服评测-应用市场 编辑提示

回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表