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

C语言信号处理程序陷入死循环,无法同时终止父子进程

问题分析与解决方案

核心问题点

  1. 父进程信号处理函数未注册:你的signal(SIGUSR1, sig_handler_parent);放在了while(true)死循环之后,这段代码永远不会被执行到,父进程根本没有设置SIGUSR1的自定义处理逻辑,后续信号触发后行为不可控。
  2. 子进程信号处理后未退出:子进程触发SIGUSR1信号后,仅给父进程发信号却未主动退出,导致父进程的waitpid一直阻塞,子进程可能继续执行后续逻辑,甚至重复触发信号引发死循环。
  3. 信号处理函数使用非安全函数:puts属于非异步信号安全函数,在信号处理函数中调用可能引发未定义行为,应该改用write这类安全函数。
  4. 信号发送逻辑冗余:子进程无需给自己发SIGUSR1来触发处理函数,直接在myfunc2中给父进程发信号后退出即可,绕圈触发信号反而增加复杂度。

修正后的代码示例

信号处理函数优化

#include <unistd.h>

// 子进程信号处理函数
void sig_handler_child(int __attribute__ ((unused)) signum)
{
    const char msg[] = "dead child\n";
    write(STDOUT_FILENO, msg, sizeof(msg)-1);
    kill(getppid(), SIGUSR1);
    _exit(0); // 必须主动退出子进程
}

// 父进程信号处理函数
void sig_handler_parent(int __attribute__ ((unused)) signum)
{
    const char msg[] = "killing parent\n";
    write(STDOUT_FILENO, msg, sizeof(msg)-1);
    // 处理空指针情况,避免atoi崩溃
    char *exit_val = getenv("EXIT_VAL");
    int ext_val = exit_val ? atoi(exit_val) : 0;
    exit(ext_val);
}

主逻辑修正

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>

// 函数声明
void sig_handler_child(int);
void sig_handler_parent(int);
void myfunc(char *env[]);
void myfunc2(char *argv[]);

int main(int argc, char *argv[], char *env[]) {
    // 保留你的初始化逻辑
    myfunc(env);
    return 0;
}

void myfunc(char *env[]) {
    pid_t session;
    int wstatus;

    // 父进程提前注册信号处理函数,必须放在循环之前
    signal(SIGUSR1, sig_handler_parent);

    while (1) {
        // 替换成你的用户输入获取逻辑
        char *test_argv[] = {"exit", "123", NULL};
        myfunc2(test_argv);

        session = fork();
        if (session == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        }
        if (session == 0) {
            signal(SIGUSR1, sig_handler_child);
            // 这里执行你的命令逻辑,比如exec系列函数
            _exit(0);
        }
        waitpid(session, &wstatus, 0);
    }
}

void myfunc2(char *argv[]) {
    char *exitc = "exit";
    if (strcmp(argv[0], exitc) == 0) {
        // 设置退出值
        if (argv[1] != NULL)
            setenv("EXIT_VAL", argv[1], 1);
        else
            setenv("EXIT_VAL", "0", 1);
        
        // 直接给父进程发信号,然后子进程退出
        kill(getppid(), SIGUSR1);
        _exit(0);
    }
}

更简洁的替代方案(无需信号)

如果担心退出码判断冲突,可以约定一个专属退出码(比如255,避开常规命令的退出码范围),子进程遇到exit命令时用该码退出,父进程通过waitpid结果判断:

// myfunc2修改
void myfunc2(char *argv[]) {
    char *exitc = "exit";
    if (strcmp(argv[0], exitc) == 0) {
        if (argv[1] != NULL)
            setenv("EXIT_VAL", argv[1], 1);
        else
            setenv("EXIT_VAL", "0", 1);
        _exit(255); // 约定专属退出码
    }
}

// myfunc循环内修改
waitpid(session, &wstatus, 0);
if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 255) {
    char *exit_val = getenv("EXIT_VAL");
    int ext_val = exit_val ? atoi(exit_val) : 0;
    exit(ext_val);
}

这种方式比信号处理更简单可靠,避免了信号相关的各种潜在问题。

额外注意事项

  • 信号处理函数中只能调用异步信号安全的函数,比如write、_exit、kill等,禁止使用printf、puts这类非安全函数。
  • signal函数存在兼容性问题,建议改用更可靠的sigaction注册信号。
  • 如果需要让父进程响应Ctrl+C,需单独注册SIGINT信号处理函数,否则进程可能因信号逻辑混乱陷入无法终止的状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 07:54:22