C编写Bash环境子进程超时终止程序:waitpid异常结束的解决方法咨询
C编写Bash环境子进程超时终止程序:waitpid异常结束的解决方法咨询
嗨,Miguel,我仔细看了你这段用来给子进程设置5秒超时的C代码,遇到的问题确实是信号处理里的典型坑——当SIGALRM触发中断waitpid后,你的循环逻辑没处理到位,导致程序误判子进程“正常退出”。我来帮你把两种解决方法都讲透,尤其是你不太熟悉的SA_RESTART用法,顺便修正代码里的几个小问题:
先分析你现有代码的核心问题
你的do-while循环逻辑写反了:
do{ returnedPID =waitpid(pid, &status, WNOHANG); }while(errno != EINTR );
用WNOHANG会让waitpid立即返回,如果子进程还在运行,它会返回0而不是-1,errno也不会被设为EINTR。而且当waitpid被信号中断时,返回-1且errno=EINTR,这时候你的循环条件errno != EINTR会直接退出,导致你没等到子进程被SIGKILL后的状态,误以为它正常结束了。
另外你的arguments分配和赋值也有问题,会导致参数列表错误或者内存浪费,我后面会一起修正。
解决方法1:修正waitpid循环,手动处理EINTR
我们需要去掉WNOHANG(让waitpid阻塞等待子进程状态),然后循环调用waitpid,直到它成功获取子进程状态,或者遇到非EINTR的错误。修改后的父进程代码如下:
else if(pid > 0){ signal(SIGALRM, sig_handler); alarm(5); int status; int returnedPID; // 循环调用waitpid,处理信号中断 do { returnedPID = waitpid(pid, &status, 0); // 阻塞等待,不用WNOHANG if (returnedPID == -1) { if (errno != EINTR) { // 非中断类错误,打印并退出循环 perror("waitpid error"); break; } // 如果是EINTR,继续循环重新调用waitpid } } while (returnedPID == -1); if (returnedPID > 0) { if (WIFEXITED(status)) printf("child exited with status of %d\n", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("child was terminated by signal %d\n",WTERMSIG(status)); else printf("reason unknown for child termination"); } }
解决方法2:用sigaction设置SA_RESTART,让系统调用自动重启
signal()函数是简化版的信号注册,不支持设置高级标志,我们需要用sigaction()来替代,通过SA_RESTART标志让被信号中断的系统调用(比如waitpid)自动重启,这样就不用手动写循环处理EINTR了。
具体步骤:
- 替换
signal()为sigaction(),设置SA_RESTART标志 - 修正
arguments的内存分配和参数赋值 - 给子进程的
execvp添加错误处理
完整修正后的代码如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <errno.h> #include <sys/wait.h> /*programa que temporiza la ejecución de un proceso hijo */ int pid; void sig_handler(int signum){ kill(pid, SIGKILL); } int main(int argc, char **argv) { if(argc < 2){ fprintf(stderr, "No hay suficientes argumentos\n"); exit(EXIT_FAILURE); } char* exeProgram = argv[1]; char** arguments; // 修正参数列表的分配和赋值 if(argc > 2){ // 所需空间:程序名 + 所有参数 + NULL,共(argc-1)个元素 arguments = malloc(sizeof(char*) * (argc - 1 + 1)); // +1是为了放NULL if (!arguments) { perror("malloc error"); exit(EXIT_FAILURE); } arguments[0] = exeProgram; for(int i = 1; i < argc - 1; ++i){ arguments[i] = argv[i+1]; } arguments[argc - 1] = NULL; // 末尾必须是NULL } else { // 只有程序名,没有参数 arguments = malloc(sizeof(char*) * 2); arguments[0] = exeProgram; arguments[1] = NULL; } pid = fork(); if(pid > 0){ struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sig_handler; sa.sa_flags = SA_RESTART; // 关键:让被信号中断的系统调用自动重启 sigemptyset(&sa.sa_mask); // 处理SIGALRM时不阻塞其他信号 if (sigaction(SIGALRM, &sa, NULL) == -1) { perror("sigaction error"); exit(EXIT_FAILURE); } alarm(5); int status; int returnedPID = waitpid(pid, &status, 0); // 阻塞等待,无需循环 if (returnedPID == -1) { perror("waitpid error"); } else { if (WIFEXITED(status)) printf("child exited with status of %d\n", WEXITSTATUS(status)); else if (WIFSIGNALED(status)) printf("child was terminated by signal %d\n",WTERMSIG(status)); else printf("reason unknown for child termination"); } free(arguments); // 记得释放内存 } else if(pid == 0) { execvp(exeProgram, arguments); // 如果execvp返回,说明执行失败 perror("execvp error"); exit(EXIT_FAILURE); } else { perror("fork error"); exit(EXIT_FAILURE); } return 0; }
额外提醒
- 记得在父进程里释放
arguments的内存,避免内存泄漏 - 子进程里
execvp如果返回,说明执行失败,一定要调用exit()退出,否则子进程会继续执行父进程的代码逻辑,导致异常 SA_RESTART并不是对所有系统调用都生效,但waitpid属于支持的范畴,所以这个方法完全适用
备注:内容来源于stack exchange,提问作者Miguel
相关产品推荐
相关产品推荐

