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

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了。

具体步骤:

  1. 替换signal()为sigaction(),设置SA_RESTART标志
  2. 修正arguments的内存分配和参数赋值
  3. 给子进程的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 08:00:33