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

如何让信号处理程序仅执行一次并终止所有进程?

问题描述

我有一个包含1个父进程和N个子进程的程序,编写了信号处理函数来处理SIGINT和SIGTERM信号,用于清理信号量和共享内存。相关代码如下:

信号处理函数及初始化代码

void unlink_semaphores(){ 
    sem_unlink(SEM_NAME1) < 0; // perror("sem_unlink(3) failed"); 
    sem_unlink(SEM_NAME2) < 0; //perror("sem_unlink(3) failed"); 
    sem_unlink(SEM_NAME3) < 0; // perror("sem_unlink(3) failed"); 
    sem_unlink(SEM_NAME4) < 0; // perror("sem_unlink(3) failed"); 
    sem_unlink(SEM_NAME5) < 0; //perror("sem_unlink(3) failed"); 
    sem_unlink(SEM_NAME6) < 0; //perror("sem_unlink(3) failed"); 
    sem_unlink(SEM_NAME7) < 0; //perror("sem_unlink(3) failed"); 
    if (shm_unlink(SHMOBJ_PATH1) != 0) { 
        // perror("In shm_unlink() of buffer 1"); 
        exit(1); 
    } 
} 

void sinyal_handler(int signo) { 
    if (signo == SIGINT || signo == SIGTERM) { 
        if(signo == SIGINT) printf("received Ctrl+C\n"); 
        if(signo == SIGTERM) printf("received SIGTERM \n"); 
        printf("closing semaphores and giving shared maps...\n"); 
        unlink_semaphores(); 
        _exit(EXIT_FAILURE); 
    } 
}

在fork子进程前,我设置了信号处理:

signal(SIGINT,&sinyal_handler); 
signal(SIGTERM,&sinyal_handler);

子进程会一直运行在while(1)循环中,直到收到Ctrl+C信号后由信号处理程序终止并清理资源。

当前问题

按下Ctrl+C时,程序会多次调用信号处理函数,重复打印信息并尝试多次解除链接文件。请问该如何解决?


解决方案

你遇到的这个问题其实很常见——当按下Ctrl+C时,操作系统会把SIGINT信号发送给整个进程组里的所有进程,也就是你的父进程加上所有子进程,所以每个进程都会触发自己的信号处理函数,自然就会重复执行清理逻辑了。下面给你几个靠谱的解决思路:

方法1:父进程统一处理信号,子进程忽略信号

这是最推荐的方案,逻辑清晰且容易维护。让子进程忽略SIGINT和SIGTERM,只让父进程捕获信号,由父进程统一完成清理工作,同时父进程可以主动给所有子进程发送终止信号,确保它们正常退出。

修改后的fork代码示例:

// 先定义一个数组保存子进程PID,方便后续管理
pid_t child_pids[PROCESS_N];

for (i = 0; i < PROCESS_N; i++) { 
    pid_t pid = fork();
    if (pid == 0) { 
        // 子进程忽略SIGINT和SIGTERM,交给父进程处理
        signal(SIGINT, SIG_IGN);
        signal(SIGTERM, SIG_IGN);
        // 子进程的业务逻辑
        while(1){ 
            // ... 你的子进程业务代码
        }
    } else if (pid > 0) {
        // 父进程保存子进程PID
        child_pids[i] = pid;
    } else {
        // fork失败的错误处理
        perror("fork failed");
        exit(EXIT_FAILURE);
    }
}

然后修改父进程的信号处理函数,先终止所有子进程再执行清理:

void sinyal_handler(int signo) { 
    if (signo == SIGINT || signo == SIGTERM) { 
        if(signo == SIGINT) printf("received Ctrl+C\n"); 
        if(signo == SIGTERM) printf("received SIGTERM \n"); 

        // 先终止所有子进程
        for (int j = 0; j < PROCESS_N; j++) {
            if (child_pids[j] > 0) {
                // 给子进程发送终止信号
                kill(child_pids[j], SIGTERM);
                // 等待子进程退出,避免变成僵尸进程
                waitpid(child_pids[j], NULL, 0);
            }
        }

        printf("closing semaphores and giving shared maps...\n"); 
        unlink_semaphores(); 
        // 父进程用exit而不是_exit,确保标准IO等资源正确清理
        exit(EXIT_FAILURE); 
    } 
}

方法2:在信号处理中临时屏蔽信号(辅助方案)

如果担心单个进程可能被多次触发信号处理(比如信号还在处理时又收到同个信号),可以在进入信号处理函数后临时屏蔽SIGINT和SIGTERM,防止重复触发。不过这个方法不能解决多进程重复执行的问题,建议和方法1配合使用。

示例代码:

void sinyal_handler(int signo) { 
    // 屏蔽SIGINT和SIGTERM,防止处理过程中再次收到信号
    sigset_t mask;
    sigemptyset(&mask);
    sigaddset(&mask, SIGINT);
    sigaddset(&mask, SIGTERM);
    sigprocmask(SIG_BLOCK, &mask, NULL);

    if (signo == SIGINT || signo == SIGTERM) { 
        if(signo == SIGINT) printf("received Ctrl+C\n"); 
        if(signo == SIGTERM) printf("received SIGTERM \n"); 
        printf("closing semaphores and giving shared maps...\n"); 
        unlink_semaphores(); 
        _exit(EXIT_FAILURE); 
    } 

    // 恢复信号屏蔽(不过这里_exit已经退出,这行不会执行)
    sigprocmask(SIG_UNBLOCK, &mask, NULL);
}

方法3:用原子标记确保清理只执行一次

可以设置一个全局原子变量,标记是否已经执行过清理操作。每次进入信号处理函数时先检查这个标记,只有第一个进入的进程会执行清理逻辑,其他进程直接退出。

示例代码:

#include <stdatomic.h>

// 原子变量,初始为false,表示未执行清理
atomic_bool cleanup_done = ATOMIC_VAR_INIT(false);

void sinyal_handler(int signo) { 
    if (signo == SIGINT || signo == SIGTERM) { 
        // 原子交换操作:把cleanup_done设为true,返回之前的值
        if (!atomic_exchange(&cleanup_done, true)) {
            // 只有第一个进程会进入这里执行清理
            if(signo == SIGINT) printf("received Ctrl+C\n"); 
            if(signo == SIGTERM) printf("received SIGTERM \n"); 
            printf("closing semaphores and giving shared maps...\n"); 
            unlink_semaphores(); 
        }
        // 所有进程都退出
        _exit(EXIT_FAILURE); 
    } 
}

这个方法能避免重复执行unlink操作,但多个进程还是会触发信号处理函数,只是只有一个会执行清理。相比之下,方法1的逻辑更清晰,是更优的选择。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:42:10