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

C语言实现Shell时,向execvp创建的子进程发送信号无效问题求助

问题:自定义Shell中SIGINT/SIGTSTP无法传递给子进程的原因及解决方法

我正在用C语言实现一个Shell,需要处理SIGINT和SIGTSTP信号,确保Shell自身不终止,而是将信号传递给子进程。尝试过两种方案:一是在信号处理函数中调用kill(child_pid, SIGINT)(child_pid为fork()返回的子进程ID);二是在子进程调用execvp()前设置新的组ID,再使用killpg()发送信号。但两种方案都无效:信号处理函数能通过write()正常输出提示信息,却无法终止子进程。

// global variable
__pid_t child_pid = 0;

int main(){
// init variables
pid_t childpid;
char *commandp;

 while(1){
   ignore();

   //do stuff that read input
   
   // do stuff that end up using fork to make child process

   if ( ( childpid = fork() ) == -1 ){
            perror ( " Fork fail " );
            exit (2); // exit value 2 means fork fail inside the shell
    }
    if (childpid == 0){
        setpg(getpid(), getpid());
        child_pid = getpgrp();

        commandp = argv[0];

        execvp(commandp, argv);
        
    }
    else {
        ignore_child();
        waitpid(childpid, &status, WUNTRACED);
    }
 }
}

//signal handling functions
void ignore(){
    struct sigaction act;

    act.sa_handler=SIG_IGN;

    // sigaction()
    sigaction(SIGINT, &act, NULL);
    sigaction(SIGTSTP, &act, NULL);
}

void ignore_child(){
    
    struct sigaction act;
    act.sa_handler = &handle_signal;
    sigaction(SIGINT, &act, NULL);
    sigaction(SIGTSTP, &act, NULL);
}

void handle_signal(int sig){
    
    if (child_pid != 0)
        if (sig == SIGINT){
            write(STDERR_FILENO, "Received SIGINT (CTRL-C) signal. Exiting...\n", 46);
            killpg(child_pid, SIGINT);
        }
        else if (sig == SIGTSTP){
            write(STDERR_FILENO, "\nReceived SIGSTP (CTRL-Z) signal. Stopping...\n", 47);
            killpg(child_pid, SIGTSTP);
        } 
}

核心问题分析

  1. 全局变量child_pid赋值完全无效
    fork后父子进程拥有独立的地址空间,子进程里的child_pid = getpgrp();只会修改子进程自己的全局变量,父进程的child_pid始终是初始值0。这直接导致信号处理函数里的killpg逻辑根本不会执行。

  2. 信号结构体未正确初始化
    你的sigaction结构体没有用memset初始化,也没设置信号掩码,可能存在脏数据干扰信号注册,或者在处理信号时出现意外的信号阻塞。

  3. 子进程未成为前台进程组
    终端发送的SIGINT/SIGTSTP默认只会传递给前台进程组。如果Shell本身还是前台进程组,信号会先被Shell捕获,而子进程在后台进程组无法收到终端信号。


修复步骤

  1. 修正child_pid的赋值逻辑
    直接在父进程中把fork得到的childpid赋值给全局变量,子进程里的赋值代码可以完全删除:

    else {
        child_pid = childpid; // 父进程中赋值全局变量
        ignore_child();
        waitpid(childpid, &status, WUNTRACED);
        child_pid = 0; // 子进程结束后重置,避免后续误操作
    }
    
  2. 规范初始化sigaction结构体
    每次注册信号前清空结构体和信号掩码,避免脏数据:

    #include <string.h> // 需包含memset的头文件
    
    void ignore(){
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_handler = SIG_IGN;
        sigemptyset(&act.sa_mask);
        sigaction(SIGINT, &act, NULL);
        sigaction(SIGTSTP, &act, NULL);
    }
    
    void ignore_child(){
        struct sigaction act;
        memset(&act, 0, sizeof(act));
        act.sa_handler = handle_signal;
        sigemptyset(&act.sa_mask);
        // 处理信号时阻塞同类信号,避免并发问题
        sigaddset(&act.sa_mask, SIGINT);
        sigaddset(&act.sa_mask, SIGTSTP);
        sigaction(SIGINT, &act, NULL);
        sigaction(SIGTSTP, &act, NULL);
    }
    
  3. 将子进程设为前台进程组
    父进程fork后,调用tcsetpgrp把终端的前台进程组切换为子进程组,确保终端信号能直接发送给子进程:

    #include <termios.h> // 需包含tcsetpgrp的头文件
    
    else {
        child_pid = childpid;
        // 将子进程组设为终端前台进程组
        tcsetpgrp(STDIN_FILENO, childpid);
        ignore_child();
        waitpid(childpid, &status, WUNTRACED);
        // 子进程结束后,Shell重新成为前台进程组
        tcsetpgrp(STDIN_FILENO, getpid());
        child_pid = 0;
    }
    
  4. 简化信号发送逻辑
    因为你已经给子进程单独设置了进程组(组ID等于子进程PID),直接用kill(child_pid, sig)即可,效果和killpg一致,代码更简洁。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 06:24:58