You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

UNIX/C中设置两个alarm()并实现差异化信号处理的方法

好问题!咱们先把核心点说清楚:SIGALRM信号本身没有携带任何标识来区分它是来自哪一次alarm()调用——所有SIGALRM信号在操作系统层面都是完全一样的。不过我们可以通过两种思路来实现你的需求:一种是利用alarm()的「单闹钟特性」分阶段设置,另一种是用更灵活的多定时器API给信号附加区分信息。下面我给你详细拆解每种方案的实现方式:

方案一:用状态标记+分阶段设置alarm()(基于原生alarm()

这是最贴合你需求的方案,因为alarm()本身一次只能生效一个闹钟,刚好契合你“第一个闹钟先触发,处理完再启动第二个”的逻辑(毕竟第一个时长更短)。

具体步骤:

    1. 提前记录进程信息:主进程创建子进程时,把所有子进程的PID存在一个数组里,同时标记好要单独终止的那个目标进程PID。
    1. 维护一个全局状态变量:比如int alarm_stage = 0;,用来标记当前处于哪个阶段(0=未触发任何闹钟,1=第一个闹钟已处理,等待第二个)。
    1. 注册SIGALRM信号处理函数:根据alarm_stage的值执行不同逻辑:
    • alarm_stage为0时:这是第一个闹钟触发,调用kill(target_pid, SIGTERM)终止单个进程,然后立刻调用alarm(第二个时长)启动第二个闹钟,同时把alarm_stage设为1。
    • alarm_stage为1时:这是第二个闹钟触发,遍历所有子进程PID逐个发送SIGTERM,最后主进程自己退出。
    1. 初始启动第一个闹钟:在主进程初始化完成后,调用alarm(第一个时长)

示例代码片段:

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

#define MAX_CHILDREN 5
pid_t child_pids[MAX_CHILDREN];
int child_count = 0;
pid_t target_pid; // 要单独终止的进程PID
int alarm_stage = 0;
const int FIRST_ALARM_SEC = 3; // 第一个闹钟时长,短于第二个
const int SECOND_ALARM_SEC = 6;

// SIGTERM处理函数(子进程用)
void handle_sigterm(int sig) {
    write(STDOUT_FILENO, "Process exiting on SIGTERM\n", 27);
    exit(EXIT_SUCCESS);
}

// SIGALRM处理函数(主进程用)
void handle_alarm(int sig) {
    if (alarm_stage == 0) {
        // 第一次触发:终止单个进程
        kill(target_pid, SIGTERM);
        char msg[50];
        snprintf(msg, sizeof(msg), "Terminated single process %d\n", target_pid);
        write(STDOUT_FILENO, msg, sizeof(msg));
        // 启动第二个闹钟
        alarm(SECOND_ALARM_SEC);
        alarm_stage = 1;
    } else if (alarm_stage == 1) {
        // 第二次触发:终止所有进程
        write(STDOUT_FILENO, "Terminating all processes...\n", 29);
        for (int i = 0; i < child_count; i++) {
            if (child_pids[i] > 0) {
                kill(child_pids[i], SIGTERM);
            }
        }
        // 等待子进程退出,避免僵尸进程
        for (int i = 0; i < child_count; i++) {
            waitpid(child_pids[i], NULL, 0);
        }
        // 主进程退出
        exit(EXIT_SUCCESS);
    }
}

int main() {
    // 注册主进程的SIGALRM处理函数
    signal(SIGALRM, handle_alarm);

    // 创建子进程
    for (int i = 0; i < MAX_CHILDREN; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            // 子进程注册SIGTERM处理函数
            signal(SIGTERM, handle_sigterm);
            // 子进程进入循环,模拟工作
            while (1) {
                sleep(1);
            }
        } else if (pid > 0) {
            child_pids[i] = pid;
            child_count++;
            // 把第一个子进程设为目标进程
            if (i == 0) {
                target_pid = pid;
            }
        } else {
            perror("fork failed");
            exit(EXIT_FAILURE);
        }
    }

    // 启动第一个闹钟
    alarm(FIRST_ALARM_SEC);

    // 主进程等待(避免提前退出)
    while (1) {
        sleep(1);
    }

    return 0;
}
方案二:用多定时器API(附加区分信息,同时设置多个闹钟)

如果你必须要同时设置两个闹钟(而不是等第一个触发后再启动第二个),那alarm()就不够用了——因为它一次只能有一个生效。这时候可以用POSIX的timer_create() API,它允许创建多个定时器,每个定时器可以发送带自定义数据的信号,这样就能区分不同的触发源。

核心思路:

  • 给两个定时器分别设置不同的sival_int值(比如1代表第一个闹钟,2代表第二个)。
  • 注册信号处理函数时用sigaction(),并指定SA_SIGINFO标志,这样处理函数可以接收siginfo_t结构体,从中取出我们设置的sival_int值,从而区分是哪个定时器触发的。

这个方案更灵活,但代码复杂度稍高,适合需要同时管理多个定时任务的场景。

关于SIGTERM的处理

上面的示例里已经给子进程注册了SIGTERM的处理函数,目的是让子进程优雅退出(比如清理资源后再退出)。如果不需要优雅退出,子进程可以不用注册,因为默认SIGTERM会直接终止进程。

最后要注意的点:

  • 信号处理函数里尽量只调用异步信号安全的函数(比如kill()write()exit()),避免用printf()snprintf()这类非异步安全函数(虽然很多情况下能运行,但严格来说不符合POSIX标准)。
  • 主进程要避免提前退出,所以示例里用了while(1) sleep(1)来保持运行,直到第二个闹钟触发后自己退出。

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

火山引擎 最新活动