父进程Socket accept与waitpid管理问题及SIGCHLD日志异常排查
关于Socket Accept与子进程管理的两个问题
问题1:Poll超时期间的僵尸进程处理
我有一个父进程,先通过poll等待连接,再在while循环中调用waitpid。相关代码如下:
poll_list[0].fd = server_fd; poll_list[0].events = POLLIN; retval = poll(poll_list, 1, 3500); if (retval == -1) { my_error(stderr, "%s: Error setting pool %d", __func__, __LINE__); exit(EXIT_FAILURE); } if (retval > 0) { // There was an accept if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) { my_error(stderr, "%s: Accepted error of near line %d", __func__, __LINE__); exit(EXIT_FAILURE); } } ... // Forker pour créer un processus enfant while (waitpid(-1, NULL, WNOHANG) > 0);
但在3.5秒的poll超时期间,可能会产生僵尸进程。我可以缩短poll超时时间,但有没有更好的方案来管理Socket accept与子进程?
问题2:SIGCHLD信号处理中异常退出日志无法输出
此外,我使用SIGCHLD信号处理的代码如下:
void childsignal() { pid_t status; int pid; while ((pid=waitpid(-1, &status, WNOHANG))>0) { my_error(stderr, "Parent: The child process %d caught signal %d and exited", pid, status); if (!WIFEXITED(status)) // process exit abnormally, need to log. my_error(stderr, "Parent: The child process %d caught signal %d and exited", pid, status); //Never executed }; }
例如,若进程a正常退出,第一条日志会输出,但进程b被信号杀死时,异常日志却无法输出,请问该如何解决?
解决方案
一、解决Poll超时期间的僵尸进程问题
不要依赖缩短poll超时来回收子进程,正确的做法是用SIGCHLD信号异步处理子进程退出,配合poll的信号中断处理:
- 注册SIGCHLD信号处理函数:在进程启动时就注册你写的
childsignal函数,确保子进程退出时父进程能立即收到信号并回收,不会等到poll超时。 - 处理
poll被信号中断的情况:当poll被SIGCHLD信号打断时,会返回-1且errno设为EINTR,此时直接重新调用poll即可,无需退出。
修改后的poll调用逻辑示例:
while (1) { poll_list[0].fd = server_fd; poll_list[0].events = POLLIN; retval = poll(poll_list, 1, 3500); if (retval == -1) { if (errno == EINTR) { // 被SIGCHLD中断,重新进入poll等待 continue; } my_error(stderr, "%s: Error setting poll %d", __func__, __LINE__); exit(EXIT_FAILURE); } if (retval > 0) { // 处理accept及后续fork逻辑... } // 无需在此调用waitpid,信号处理函数已异步完成回收 }
这样子进程一退出就会触发SIGCHLD信号,信号处理函数里的waitpid会立即清理僵尸进程,不会等到poll超时,同时poll被中断后会重新等待连接,不影响正常业务逻辑。
二、修复SIGCHLD处理函数的异常日志问题
你的代码核心问题是错误解析waitpid返回的status状态码:
- 直接将
status作为信号值输出是错误的,status是复合状态码,必须用系统宏解析。 - 异常判断逻辑正确,但获取信号值的方式错误,需要用
WTERMSIG宏提取导致进程终止的信号编号。
修改后的信号处理函数:
void childsignal() { int status; pid_t pid; // 注意:确保my_error是异步信号安全的函数,避免在信号处理中调用非安全函数 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { if (WIFEXITED(status)) { // 正常退出,获取退出码 int exit_code = WEXITSTATUS(status); my_error(stderr, "Parent: Child process %d exited normally with code %d", pid, exit_code); } else if (WIFSIGNALED(status)) { // 被信号终止,获取信号编号 int sig = WTERMSIG(status); my_error(stderr, "Parent: Child process %d was killed by signal %d", pid, sig); } } }
关键修正点:
- 用
WIFEXITED判断正常退出,WEXITSTATUS提取退出码。 - 用
WIFSIGNALED判断信号终止,WTERMSIG提取信号编号。 - 分情况输出日志,避免重复且逻辑清晰。
另外注意:信号处理函数中必须使用异步信号安全的函数(如write),如果my_error内部调用了printf这类非安全函数,可能会引发未知问题,建议替换为异步安全的日志实现。
内容的提问来源于stack exchange,提问作者JoelCrypto
相关产品推荐
相关产品推荐

