You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

wait(NULL)异常捕获未结束子进程PID问题排查

问题根因与修复方案

你遇到的异常本质是代码存在致命野指针bug+事件监听逻辑设计错误,和wait系统调用本身的行为无关。

第一类问题:直接导致wait提前返回的硬bug

你在fork出的子进程分支里写了未初始化指针的非法操作:

case 0:
#ifdef CHILD_NAME 
    char* test; // 野指针,没有指向任何合法内存空间
    *sh_value = *sh_value +1;
    sprintf(test,"%d",*sh_value); // 向随机内存地址写数据,直接触发段错误

这行代码执行时,子进程会立刻因为段错误崩溃退出,根本走不到后续execve加载user程序的逻辑,更不可能执行你写的“发消息后跑其他任务”的代码,所以父进程的wait()会立刻拿到崩溃子进程的PID,和你预期的“子进程跑任务时wait阻塞”完全不符。
除此之外你代码里还有几个低级错误会导致逻辑异常:

  • 共享内存的计数器*sh_value没有加同步保护,多进程同时自增会出现竞态,计数不准
  • msgrcv传入的接收长度120远大于结构体实际长度,会造成栈内存越界写
  • 父进程分支里调用getppid()拿到的是父进程的父进程(一般是shell)的PID,不是当前进程PID,打印逻辑错误

第二类问题:无法同时响应两类事件的设计错误

就算你修复了野指针问题,你现在写的阻塞调用串行逻辑也不可能实现“同时响应子进程退出、消息队列消息”的需求:

  • 如果你循环调用阻塞式wait(NULL),程序会完全卡在wait调用上,消息队列有新消息时根本没机会执行msgrcv接收
  • 如果你把wait和msgrcv顺序写在循环里,程序会先卡在wait上等子进程退出,收不到消息;就算wait返回,又会卡在msgrcv上等消息,没法及时处理后续退出的子进程。

正确实现方案

要同时监听两类事件,最稳妥的实现是SIGCHLD信号+非阻塞回收+阻塞收消息的模式,逻辑如下:

  1. 注册SIGCHLD信号处理函数:子进程退出时内核会自动给父进程发SIGCHLD信号,这个信号可以打断正在阻塞的msgrcv调用
  2. 信号触发后,用带WNOHANG参数的waitpid非阻塞回收所有已经退出的子进程,避免卡住消息接收
  3. 修复野指针、长度传参等低级错误

修复后的核心代码

1. 修复子进程fork分支的野指针问题

for (int i = 0; i < SO_USERS_NUM; i++)
{
    switch (fork())
    {
    case 0:
#ifdef CHILD_NAME 
        char test[16]; // 栈上分配合法内存,足够存储数字字符串
        // 直接用循环变量生成编号,避免共享内存竞态问题
        snprintf(test, sizeof(test), "%d", i+1);
        args[1] = test;
        args[2] = NULL;          
        execve(CHILD_NAME,args,NULL);
        // execve只有失败才会返回,打印错误
        perror("execve failed");
#endif
        exit(EXIT_FAILURE);
        break;
     case -1:
        perror("fork failed");
        break;
    default:
        break;
    }
}

2. 注册SIGCHLD信号处理

#include <signal.h>
// 信号标记,必须用sig_atomic_t保证跨信号上下文的原子性
volatile sig_atomic_t has_child_exit = 0;
void sigchld_handler(int sig) {
    has_child_exit = 1;
}

// 在fork之前注册信号
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
// 不要设置SA_RESTART,保证信号可以打断阻塞的msgrcv
sigaction(SIGCHLD, &sa, NULL);

3. 正确的事件循环

int exited_child_num = 0;
struct msgUser mt_msg;
while (exited_child_num < SO_USERS_NUM) {
    // 先非阻塞回收所有已退出的子进程
    if (has_child_exit) {
        pid_t pid;
        int status;
        while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
            printf("子进程退出,PID: %d\n", pid);
            exited_child_num++;
        }
        has_child_exit = 0;
    }
    // 阻塞收消息,被信号打断时会返回EINTR
    ssize_t num_bytes = msgrcv(q_id, &mt_msg, sizeof(mt_msg.numero), 3, 0);
    if (num_bytes >= 0) {
        printf("收到队列消息,内容: %d\n", mt_msg.numero);
    } else {
        if (errno == EINTR) {
            // 被SIGCHLD打断,回到循环开头处理子进程退出即可
            continue;
        }
        perror("msgrcv failed");
        break;
    }
}
// 所有子进程退出后删除消息队列
msgctl(q_id, IPC_RMID, NULL);

补充说明

你之前写的“user进程无限循环时wait正常阻塞”的版本,本质是刚好野指针写内存没触发崩溃(未定义行为的随机结果),子进程成功execve跑了死循环,所以wait一直阻塞,属于撞大运的正常,不是代码逻辑正确。

内容的提问来源于stack exchange,提问作者Matteo Pagliarello

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:27:23