linux信号(14)——SIGALRM:从“手机闹钟”看SIGALRM:进程的非阻塞定时神器

<摘要>

SIGALRM是Linux/Unix系统中编号为14的定时信号 ,核心功能是"内核在指定时间后向进程发送信号,提醒进程执行定时任务或处理超时逻辑"。其设计源于进程"时间管理"的需求:若程序需要"3秒后执行某个操作"或"限制函数运行不超过5秒",无需阻塞等待(如sleep),只需通过alarm/setitimer设置定时,内核会在时间到后发送SIGALRM,进程可通过信号处理函数响应。

默认情况下,进程收到SIGALRM会直接终止,这与SIGPIPE类似,但SIGALRM的核心价值在于"非阻塞定时"------就像你给手机设闹钟,不用一直盯着时间,闹钟响了再处理,期间可以做其他事。

本文通过"手机闹钟"的生活化类比,从背景、定时函数、处理方法三个维度解析SIGALRM:先讲清"为什么需要SIGALRM"(传统定时的局限),再拆解alarm/setitimer的用法差异,最后通过"函数超时控制""周期性日志打印"两个完整案例(含代码、Makefile、Mermaid时序图),展示SIGALRM如何实现非阻塞定时,帮助开发者掌握这一"进程内置闹钟"工具。


<解析>

从"手机闹钟"看SIGALRM:进程的非阻塞定时神器

想象一下:你需要3分钟后给朋友回消息,有两种方式可选:

  1. 盯着时钟等 :放下手头所有事,盯着手机倒计时,3分钟一到就回消息------这是传统sleep的"阻塞定时",效率低;
  2. 设个闹钟:打开手机闹钟,设3分钟后响,期间继续工作,闹钟响了再回消息------这是SIGALRM的"非阻塞定时",不浪费时间。

在Linux系统中,SIGALRM就是进程的"手机闹钟":进程通过alarmsetitimer给内核"设闹钟",内核在指定时间后发送SIGALRM信号,进程收到信号后执行预设操作(如超时处理、周期性任务),期间进程可正常处理其他逻辑(如I/O、计算),无需阻塞等待。

今天咱们就从"手机闹钟"的比喻入手,拆透SIGALRM:先搞懂它"解决什么问题"(传统定时的痛点),再学会"怎么设闹钟"(alarm/setitimer用法),最后用案例实战"闹钟响了怎么处理"(信号响应与定时逻辑)。

一、背景与核心概念:为什么需要SIGALRM?

要理解SIGALRM,得先明白传统定时方式的局限------就像知道"盯着时钟等"有多低效,才会认可"设闹钟"的价值。

1. 传统定时的痛点:阻塞 vs 轮询

进程需要定时功能时,传统方式有两种,但都存在明显缺陷:

(1)阻塞定时:sleep/usleep------"盯着时钟等"

调用sleep(3)会让进程阻塞3秒,期间无法做任何事(如处理客户端请求、读取文件),相当于"放下工作等闹钟",仅适用于简单场景(如程序启动前延迟)。

示例(低效)

c 复制代码
#include <stdio.h>
#include <unistd.h>

int main() {
    printf("开始工作...\n");
    sleep(3); // 阻塞3秒,期间无法处理其他逻辑
    printf("3秒到,回消息!\n"); // 3秒后才执行
    return 0;
}

问题:若进程是服务器,阻塞期间无法接收新连接,导致服务不可用。

(2)轮询定时:循环检查时间差------"频繁看时钟"

通过time()clock_gettime()获取当前时间,循环计算与目标时间的差值,直到达到定时长度。这种方式不阻塞,但会疯狂占用CPU(相当于"每秒看100次时钟"),效率极低。

示例(浪费CPU)

c 复制代码
#include <stdio.h>
#include <time.h>

int main() {
    printf("开始工作...\n");
    time_t start = time(NULL);
    // 轮询等待3秒,期间CPU占用100%
    while (time(NULL) - start < 3); 
    printf("3秒到,回消息!\n");
    return 0;
}

问题:循环期间CPU资源被空耗,影响其他进程运行。

(3)SIGALRM的优势:"设闹钟后自由工作"

SIGALRM解决了上述痛点:

  • 非阻塞:设置定时后进程可正常执行其他逻辑,无需等待;
  • 低资源消耗:内核负责计时,时间到了才通知进程,无需轮询;
  • 灵活:支持单次定时(如3秒后执行一次)和周期性定时(如每隔1秒执行一次)。

用"手机闹钟"类比三种定时方式的差异:

定时方式 对应行为 效率 适用场景
阻塞定时(sleep) 盯着时钟等闹钟 简单延迟(无其他任务)
轮询定时(循环查时间) 频繁看时钟 极低 无(几乎不用)
SIGALRM(信号定时) 设闹钟后正常工作 服务器超时控制、周期性任务

2. SIGALRM的本质:内核的"定时通知"

SIGALRM是POSIX标准定义的信号,编号14,官方描述为:Timer signal from alarm(2) and setitimer(2) (由alarmsetitimer触发的定时信号)。

其核心逻辑是:

  1. 进程调用alarmsetitimer,向内核"提交定时任务"(如"3秒后提醒我");
  2. 内核启动定时器,开始倒计时;
  3. 时间到后,内核向进程发送SIGALRM信号;
  4. 进程根据预设的信号处理逻辑响应(如执行函数、记录日志)。

默认情况下,进程收到SIGALRM的行为是"终止进程并产生核心转储"(部分系统默认不产生core dump,但进程一定会终止)。因此,实际开发中必须自定义SIGALRM的处理逻辑,否则会导致程序意外退出。

3. 触发SIGALRM的两个核心函数

SIGALRM的触发依赖两个系统调用:alarm(简单秒级定时)和setitimer(高精度周期性定时),两者的定位类似"手机闹钟的简易模式和专业模式"。

(1)alarm:秒级单次定时(简易模式)

alarm是最简单的定时函数,仅支持"秒级精度"和"单次定时",函数原型如下:

c 复制代码
#include <unistd.h>

unsigned int alarm(unsigned int seconds);
  • 参数seconds------定时时间(秒),0表示取消当前定时器;
  • 返回值:若之前已设置定时器,返回剩余时间(秒);若之前无定时器,返回0;
  • 核心特性
    1. 精度低:仅支持秒级,无法精确到毫秒/微秒;
    2. 单次触发:时间到后发送一次SIGALRM,定时器自动取消;
    3. 覆盖性:多次调用alarm,仅最后一次生效(覆盖之前的定时)。

示例(alarm基本用法)

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

// SIGALRM处理函数
void handle_alarm(int sig) {
    printf("alarm响了!3秒到了~\n");
}

int main() {
    // 设置SIGALRM处理函数
    signal(SIGALRM, handle_alarm);

    // 设3秒后触发SIGALRM
    unsigned int remaining = alarm(3);
    printf("之前的定时器剩余时间:%u秒(首次调用为0)\n", remaining);

    // 模拟其他工作(非阻塞,期间可处理其他逻辑)
    printf("设完闹钟,继续工作...\n");
    for (int i = 0; i < 5; i++) {
        sleep(1); // 每秒打印一次,证明进程未阻塞
        printf("工作中...%d秒\n", i+1);
    }

    return 0;
}

运行结果

复制代码
之前的定时器剩余时间:0秒(首次调用为0)
设完闹钟,继续工作...
工作中...1秒
工作中...2秒
alarm响了!3秒到了~
工作中...3秒
工作中...4秒
工作中...5秒

解读 :3秒后触发SIGALRM,执行handle_alarm,主程序的循环未被阻塞,继续执行。

(2)setitimer:高精度周期性定时(专业模式)

setitimeralarm更灵活,支持"微秒级精度"和"周期性定时",可满足复杂场景(如每隔500毫秒执行一次任务),函数原型如下:

c 复制代码
#include <sys/time.h>

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
  • 参数1:which------定时器类型,决定计时的"时间维度",常用以下三种:

    类型常量 计时维度 触发信号 适用场景
    ITIMER_REAL 真实时间(墙上时钟) SIGALRM 普通定时(如超时控制)
    ITIMER_VIRTUAL 进程用户态运行时间 SIGVTALRM 监控进程自身计算耗时
    ITIMER_PROF 用户态+内核态运行时间 SIGPROF 监控进程总耗时(含系统调用)

    开发中最常用ITIMER_REAL,对应真实时间,触发SIGALRM。

  • 参数2:new_value ------定时器配置,是struct itimerval类型,定义如下:

    c 复制代码
    struct itimerval {
        struct timeval it_interval; // 周期时间(每隔多久触发一次)
        struct timeval it_value;    // 首次触发时间(多久后第一次触发)
    };
    
    struct timeval {
        long tv_sec;  // 秒
        long tv_usec; // 微秒(1秒=1e6微秒)
    };
    • it_interval为0(秒和微秒都为0):单次定时(仅触发一次);
    • it_interval非0:周期性定时(首次触发后,每隔it_interval时间再触发)。
  • 参数3:old_value------输出参数,若不为NULL,会存储之前定时器的配置(用于后续恢复),通常设为NULL。

  • 返回值 :成功返回0,失败返回-1(需检查errno)。

核心特性

  1. 高精度:支持微秒级,比alarm的秒级精度更高;
  2. 周期性:可设置循环触发(如每隔1秒一次);
  3. 灵活计时:支持真实时间、用户态时间等多维度。

示例(setitimer周期性定时)

c 复制代码
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

int count = 0; // 计数:记录触发次数

void handle_alarm(int sig) {
    count++;
    printf("setitimer触发:第%d次(每隔1秒一次)\n", count);
    // 触发5次后停止定时器
    if (count >= 5) {
        struct itimerval timer = {0};
        setitimer(ITIMER_REAL, &timer, NULL); // 取消定时器
        printf("定时器已停止\n");
    }
}

int main() {
    signal(SIGALRM, handle_alarm);

    // 配置定时器:首次1秒后触发,之后每隔1秒触发
    struct itimerval timer;
    // 首次触发时间:1秒0微秒
    timer.it_value.tv_sec = 1;
    timer.it_value.tv_usec = 0;
    // 周期时间:1秒0微秒(每隔1秒触发)
    timer.it_interval.tv_sec = 1;
    timer.it_interval.tv_usec = 0;

    // 启动定时器
    setitimer(ITIMER_REAL, &timer, NULL);

    // 主进程循环,证明非阻塞
    while (1) {
        sleep(1);
        if (count >= 5) break; // 触发5次后退出
    }

    return 0;
}

运行结果

复制代码
setitimer触发:第1次(每隔1秒一次)
setitimer触发:第2次(每隔1秒一次)
setitimer触发:第3次(每隔1秒一次)
setitimer触发:第4次(每隔1秒一次)
setitimer触发:第5次(每隔1秒一次)
定时器已停止

解读:1秒后首次触发,之后每隔1秒触发一次,共5次,触发后取消定时器,主程序正常循环未阻塞。

二、SIGALRM的处理方法:如何响应定时信号?

SIGALRM的默认行为是"终止进程",因此实际开发中必须自定义处理逻辑,核心目标是"让进程收到信号后执行预设任务,而非退出"。

常见的处理方法有三种,按"推荐程度"排序:

方法1:自定义信号处理函数(最常用)

通过signalsigaction注册SIGALRM的处理函数,当信号触发时,内核会中断当前进程的执行,跳转到处理函数,执行完成后返回原逻辑继续执行(类似"闹钟响了,暂停工作处理闹钟,处理完继续工作")。

关键注意事项:处理函数需"可重入"

信号处理函数可能在进程执行任何代码时被调用(如进程正在执行malloc时收到SIGALRM),因此必须使用可重入函数(Reentrant Function),避免使用非可重入函数导致数据不一致或死锁。

常见的可重入/非可重入函数对照表:

类别 可重入函数(推荐) 非可重入函数(禁用)
输入输出 write(写入固定描述符,如stderr) printffprintf(使用全局缓冲区)
内存操作 无(避免在处理函数中分配/释放内存) mallocfree(全局内存池)
字符串操作 memcpymemset(操作局部缓冲区) strtok(修改输入字符串)
信号操作 sigaction(带信号掩码保护) signal(部分实现非可重入)

推荐实践:处理函数仅做"简单操作"(如设置全局标志、记录日志),复杂逻辑放在主程序中通过检查标志执行。

示例(安全的处理函数)

c 复制代码
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>
#include <unistd.h>

volatile sig_atomic_t timeout_flag = 0; // 全局标志:0=未超时,1=超时
// volatile:防止编译器优化,确保每次从内存读取
// sig_atomic_t:保证赋值操作是原子的,避免信号中断导致数据不一致

// 可重入的处理函数:仅设置标志
void handle_alarm(int sig) {
    timeout_flag = 1;
    // 用write代替printf,避免非可重入问题
    const char *msg = "定时到,设置超时标志\n";
    write(STDERR_FILENO, msg, sizeof(msg)-1);
}

int main() {
    // 用sigaction注册信号(比signal更安全,支持信号掩码)
    struct sigaction sa;
    sa.sa_handler = handle_alarm;
    sigemptyset(&sa.sa_mask); // 信号处理期间不屏蔽其他信号
    sa.sa_flags = 0;
    sigaction(SIGALRM, &sa, NULL);

    // 设置3秒后触发SIGALRM
    alarm(3);

    // 主程序逻辑:循环检查超时标志
    printf("开始执行任务,3秒内未完成则超时...\n");
    while (1) {
        if (timeout_flag) {
            printf("任务超时,退出程序!\n");
            break;
        }
        // 模拟任务执行(如处理数据、等待I/O)
        printf("任务执行中...\n");
        sleep(1);
    }

    return 0;
}

运行结果

复制代码
开始执行任务,3秒内未完成则超时...
任务执行中...
任务执行中...
任务执行中...
定时到,设置超时标志
任务超时,退出程序!

解读 :处理函数仅设置timeout_flag,主程序通过循环检查标志响应超时,避免在处理函数中执行复杂操作,保证安全性。

方法2:忽略SIGALRM(极少用)

通过signal(SIGALRM, SIG_IGN)忽略SIGALRM,此时内核发送信号后进程无任何响应,定时器触发后自动取消。这种方式仅适用于"临时取消定时"(如先设alarm(5),后决定不需要,用alarm(0)或忽略信号),但极少使用------因为忽略信号会丢失定时通知,失去SIGALRM的价值。

示例

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

int main() {
    // 忽略SIGALRM,信号触发后无响应
    signal(SIGALRM, SIG_IGN);

    // 设3秒后触发SIGALRM,但被忽略
    alarm(3);

    printf("忽略SIGALRM,3秒后无响应...\n");
    sleep(5); // 等待5秒,验证无信号响应
    printf("5秒后程序退出\n");

    return 0;
}

运行结果:3秒后无任何响应,程序继续执行到5秒后退出。

方法3:使用sigwaitinfo等待信号(同步处理)

通过sigwaitinfo将"异步信号"转为"同步等待"------进程主动阻塞等待SIGALRM,信号触发后sigwaitinfo返回,进程执行处理逻辑。这种方式适用于"单线程程序需同步等待定时"(如主线程等待子线程完成,同时设超时),避免异步信号中断主程序逻辑。

示例

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

int main() {
    sigset_t sigset;
    sigemptyset(&sigset);
    sigaddset(&sigset, SIGALRM); // 将SIGALRM加入信号集

    // 阻塞SIGALRM(防止信号在等待前触发)
    sigprocmask(SIG_BLOCK, &sigset, NULL);

    // 设置3秒后触发SIGALRM
    alarm(3);

    printf("同步等待SIGALRM...\n");
    struct siginfo_t info;
    // 阻塞等待SIGALRM,信号触发后返回
    int ret = sigwaitinfo(&sigset, &info);
    if (ret == SIGALRM) {
        printf("同步收到SIGALRM,定时到!\n");
    }

    return 0;
}

运行结果

复制代码
同步等待SIGALRM...
同步收到SIGALRM,定时到!

解读sigwaitinfo阻塞进程,直到收到SIGALRM后返回,适用于需要"同步等待定时"的场景。

三、SIGALRM的常见误区与避坑指南

SIGALRM的使用中,新手常因忽略定时逻辑细节导致bug,以下是高频误区及避坑方法:

误区1:多次调用alarm未理解"覆盖性"

错误代码

c 复制代码
// 错误:认为两次alarm会分别触发
alarm(3); // 设3秒后触发
sleep(1);
alarm(2); // 覆盖之前的定时,3秒的定时失效

问题原因alarm是"覆盖式"定时,多次调用仅最后一次生效,之前的定时会被取消,返回值为之前定时的剩余时间(如上述代码中alarm(2)的返回值是2,即3秒定时已过去1秒,剩余2秒)。

避坑方法 :若需多个定时任务,使用setitimer或通过"全局标志+单次定时"模拟(如第一次定时3秒,触发后设下一次定时2秒)。

误区2:setitimerit_intervalit_value混淆

错误代码

c 复制代码
// 错误:希望1秒后首次触发,之后每隔2秒触发,但配置反了
struct itimerval timer;
timer.it_value.tv_sec = 2;    // 错误:首次触发设为2秒
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1; // 错误:周期设为1秒
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);

问题原因 :混淆了it_value(首次触发时间)和it_interval(周期时间),导致定时逻辑与预期不符。

避坑方法 :记住"先触发,后周期"------it_value是"多久后第一次响",it_interval是"响了之后每隔多久再响",配置时先设置it_value,再设置it_interval

误区3:处理函数中使用非可重入函数

错误代码

c 复制代码
void handle_alarm(int sig) {
    // 错误:printf是非可重入函数,可能导致缓冲区混乱
    printf("定时到!当前时间:%s\n", ctime(&time(NULL)));
}

问题原因printf使用全局输出缓冲区,若进程正在执行printf时收到SIGALRM,处理函数中的printf会覆盖缓冲区,导致输出乱码或程序崩溃。

避坑方法

  1. 处理函数中仅使用可重入函数(如write);
  2. 复杂逻辑(如格式化时间)放在主程序中,处理函数仅设置全局标志。

正确代码

c 复制代码
volatile sig_atomic_t alarm_flag = 0;

void handle_alarm(int sig) {
    alarm_flag = 1;
    // 用write输出固定字符串,避免非可重入函数
    const char *msg = "定时到!\n";
    write(STDERR_FILENO, msg, sizeof(msg)-1);
}

int main() {
    //... 注册信号...
    while (1) {
        if (alarm_flag) {
            // 主程序中使用printf,安全
            printf("当前时间:%s\n", ctime(&time(NULL)));
            alarm_flag = 0;
        }
        sleep(1);
    }
    return 0;
}

误区4:忽略alarm的精度问题

错误代码

c 复制代码
// 错误:期望alarm(0.5)实现500毫秒定时
alarm(0.5); // 编译错误:alarm参数是unsigned int(秒),0.5会被截断为0

问题原因alarm仅支持秒级精度,参数是unsigned int,小数会被截断为整数(如0.5→0,1.9→1),无法实现毫秒/微秒级定时。

避坑方法 :需高精度定时时,使用setitimer,通过tv_usec设置微秒(如500毫秒=500000微秒)。

正确代码

c 复制代码
struct itimerval timer;
// 500毫秒后触发(0秒+500000微秒)
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = 500000;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &timer, NULL);

误区5:信号未阻塞导致"竞态条件"

错误代码

c 复制代码
// 错误:设置定时后,信号可能在sigwaitinfo前触发,导致永久阻塞
alarm(1);
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGALRM);
sigwaitinfo(&sigset, NULL); // 可能永久阻塞:信号已触发,未被捕获

问题原因alarm(1)设置后,1秒内若sigset未准备好或sigwaitinfo未调用,信号会提前触发,导致sigwaitinfo永远收不到信号,进程永久阻塞。

避坑方法:设置定时前,先阻塞目标信号,避免信号提前触发:

c 复制代码
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGALRM);
// 先阻塞SIGALRM,防止信号提前触发
sigprocmask(SIG_BLOCK, &sigset, NULL);

alarm(1); // 此时信号即使触发,也会被阻塞,等待sigwaitinfo
sigwaitinfo(&sigset, NULL); // 安全:信号会被唤醒

四、SIGALRM实战案例:函数超时控制

实际开发中,SIGALRM最常用的场景是"函数超时控制"------限制某个函数的运行时间(如网络请求不超过5秒,超时则终止)。以下是完整案例,实现"限制slow_function运行不超过3秒,超时则退出"。

案例需求

  1. 定义slow_function:模拟耗时任务(如循环10秒);
  2. 用SIGALRM设置3秒超时,超时后终止slow_function
  3. 程序输出超时日志,正常退出,不崩溃。

完整代码(timeout_control.c

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>

// 全局超时标志:volatile+sig_atomic_t确保安全
volatile sig_atomic_t timeout_occurred = 0;

/**
 * @brief SIGALRM处理函数:设置超时标志
 */
void handle_alarm(int sig) {
    timeout_occurred = 1;
    const char *msg = "[超时提醒] 函数运行已超过3秒,即将终止!\n";
    write(STDERR_FILENO, msg, sizeof(msg) - 1);
}

/**
 * @brief 模拟耗时函数:循环10秒,期间检查超时标志
 */
void slow_function() {
    printf("耗时函数开始执行(预计10秒)...\n");
    for (int i = 0; i < 10; i++) {
        // 检查超时标志,若已超时则退出函数
        if (timeout_occurred) {
            printf("耗时函数检测到超时,终止执行!\n");
            return;
        }
        printf("耗时函数运行中... 第%d秒\n", i + 1);
        sleep(1); // 模拟1秒耗时
    }
    printf("耗时函数正常完成(10秒)\n");
}

/**
 * @brief 初始化SIGALRM:设置3秒超时
 */
void init_alarm() {
    // 1. 注册SIGALRM处理函数(用sigaction更安全)
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = handle_alarm;
    // 信号处理期间屏蔽SIGALRM,避免嵌套触发
    sigaddset(&sa.sa_mask, SIGALRM);
    sa.sa_flags = 0; // 不使用特殊标志
    if (sigaction(SIGALRM, &sa, NULL) == -1) {
        perror("sigaction(SIGALRM) failed");
        exit(EXIT_FAILURE);
    }

    // 2. 设置3秒后触发SIGALRM(单次定时)
    if (alarm(3)!= 0) {
        printf("警告:之前存在未处理的定时器,已覆盖\n");
    }
}

int main() {
    printf("程序启动,开始设置3秒超时控制...\n");

    // 初始化SIGALRM定时
    init_alarm();

    // 执行耗时函数
    slow_function();

    // 取消定时器(若函数提前退出,避免后续触发)
    alarm(0);
    printf("程序正常退出\n");

    return 0;
}

Makefile

makefile 复制代码
# Makefile for SIGALRM Timeout Control
CC = gcc
CFLAGS = -Wall -Wextra -g -std=c99
TARGET = timeout_control

all: $(TARGET)

$(TARGET): timeout_control.c
	$(CC) $(CFLAGS) -o $@ $^

clean:
	rm -f $(TARGET)
	rm -f *.o

操作步骤

  1. 编译运行

    bash 复制代码
    make clean && make

./timeout_control

复制代码
2. **观察结果**:

程序启动,开始设置3秒超时控制...

耗时函数开始执行(预计10秒)...

耗时函数运行中... 第1秒

耗时函数运行中... 第2秒

超时提醒\] 函数运行已超过3秒,即将终止! 耗时函数检测到超时,终止执行! 程序正常退出 ### 案例解读 1. **定时初始化**:`init_alarm`注册处理函数,用`alarm(3)`设置3秒超时; 2. **耗时函数**:`slow_function`循环10秒,每次循环检查`timeout_occurred`,超时则退出; 3. **超时处理**:3秒后触发SIGALRM,处理函数设置`timeout_occurred`,耗时函数检测到后终止; 4. **资源清理**:主程序调用`alarm(0)`取消定时器,避免函数提前退出后信号仍触发。 ## 五、SIGALRM的总结:核心知识点梳理 用一张Mermaid图总结SIGALRM的核心逻辑,帮你快速回顾: ```mermaid graph TD A["SIGALRM核心总结(信号编号14)"] --> B["核心作用:内核定时通知,实现非阻塞定时/超时控制"] B --> C["触发方式(二选一)"] C --> C1["alarm(seconds):秒级单次定时,简单易用"] C --> C2["setitimer(ITIMER_REAL, ...):微秒级周期性定时,灵活高效"] B --> D["默认行为"] D --> D1["进程收到SIGALRM后默认终止(退出码14)"] D --> D2["定时触发后,定时器自动取消(单次)或循环(周期性)"] B --> E["处理方法(按推荐度)"] E --> E1["自定义处理函数:设置全局标志/记录日志,用可重入函数"] E --> E2["sigwaitinfo:同步等待信号,避免异步中断(适用于单线程)"] E --> E3["忽略信号(SIG_IGN):仅用于临时取消定时,极少用"] B --> F["避坑指南"] F --> F1["alarm多次调用会覆盖,需用setitimer实现多定时"] F --> F2["处理函数禁用非可重入函数(如printf、malloc)"] F --> F3["setitimer需区分it_value(首次触发)和it_interval(周期)"] F --> F4["高精度定时用setitimer,alarm仅支持秒级"] F --> F5["同步等待前先阻塞信号,避免竞态条件"] B --> G["适用场景"] G --> G1["服务器超时控制(如网络请求不超过5秒)"] G --> G2["周期性任务(如每隔1秒打印日志)"] G --> G3["函数运行时间限制(如耗时任务不超过3秒)"] G --> G4["非阻塞定时(不影响主程序逻辑)"] #### 一句话总结SIGALRM的价值 SIGALRM是进程的"内置闹钟"------无需阻塞等待或轮询,内核负责定时,时间到了通过信号通知进程,完美解决"定时与主逻辑并行"的需求,是服务器超时控制、周期性任务的核心工具。 下次需要"让程序3秒后做某事"或"限制函数不超过5秒"时,记得用SIGALRM------这个非阻塞的定时神器,能让你的程序更高效、更健壮。

相关推荐
小白跃升坊15 分钟前
基于1Panel的AI运维
linux·运维·人工智能·ai大模型·教学·ai agent
跃渊Yuey34 分钟前
【Linux】线程同步与互斥
linux·笔记
舰长11536 分钟前
linux 实现文件共享的实现方式比较
linux·服务器·网络
zmjjdank1ng1 小时前
Linux 输出重定向
linux·运维
路由侠内网穿透.1 小时前
本地部署智能家居集成解决方案 ESPHome 并实现外部访问( Linux 版本)
linux·运维·服务器·网络协议·智能家居
VekiSon1 小时前
Linux内核驱动——基础概念与开发环境搭建
linux·运维·服务器·c语言·arm开发
zl_dfq2 小时前
Linux 之 【进程信号】(signal、kill、raise、abort、alarm、Core Dump核心转储机制)
linux
Ankie Wan2 小时前
cgroup(Control Group)是 Linux 内核提供的一种机制,用来“控制、限制、隔离、统计”进程对系统资源的使用。
linux·容器·cgroup·lxc
skywalk81632 小时前
尝试在openi启智社区的dcu环境安装ollama最新版0.15.2(失败)
linux·运维·服务器·ollama
zhengfei6113 小时前
AutoPentestX – Linux 自动化渗透测试和漏洞报告工具
linux·运维·自动化