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信号+非阻塞回收+阻塞收消息的模式,逻辑如下:
- 注册SIGCHLD信号处理函数:子进程退出时内核会自动给父进程发SIGCHLD信号,这个信号可以打断正在阻塞的
msgrcv调用 - 信号触发后,用带
WNOHANG参数的waitpid非阻塞回收所有已经退出的子进程,避免卡住消息接收 - 修复野指针、长度传参等低级错误
修复后的核心代码
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
相关产品推荐
相关产品推荐

