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------这个非阻塞的定时神器,能让你的程序更高效、更健壮。

相关推荐
心灵宝贝5 小时前
libopenssl-1_0_0-devel-1.0.2p RPM 包安装教程(openSUSE/SLES x86_64)
linux·服务器·数据库
BullSmall6 小时前
linux zgrep命令介绍
linux·运维
emma羊羊7 小时前
【文件读写】图片木马
linux·运维·服务器·网络安全·靶场
wdfk_prog7 小时前
闹钟定时器(Alarm Timer)初始化:构建可挂起的定时器基础框架
java·linux·数据库
2301_818411557 小时前
Ubuntu之apt更新源
linux·运维·ubuntu
Damon小智8 小时前
玩转CodeX:CodeX安装教程(Windows+Linux+MacOS)
linux·windows·macos·ai·ai编程·codex·gpt-5
CS Beginner8 小时前
【Linux】Mysql的基本文件组成和配置
linux·运维·mysql
爱奥尼欧9 小时前
【Linux】网络部分——Socket编程 UDP实现网络云服务器与本地虚拟机的基本通信
linux·服务器·网络
Ching·9 小时前
RK3568入门之VScode远程连接开发板,直接开发板上面编程和实验
linux·ide·vscode·编辑器·rk3568