<摘要>
SIGALRM是Linux/Unix系统中编号为14的定时信号 ,核心功能是"内核在指定时间后向进程发送信号,提醒进程执行定时任务或处理超时逻辑"。其设计源于进程"时间管理"的需求:若程序需要"3秒后执行某个操作"或"限制函数运行不超过5秒",无需阻塞等待(如sleep
),只需通过alarm
/setitimer
设置定时,内核会在时间到后发送SIGALRM,进程可通过信号处理函数响应。
默认情况下,进程收到SIGALRM会直接终止,这与SIGPIPE类似,但SIGALRM的核心价值在于"非阻塞定时"------就像你给手机设闹钟,不用一直盯着时间,闹钟响了再处理,期间可以做其他事。
本文通过"手机闹钟"的生活化类比,从背景、定时函数、处理方法三个维度解析SIGALRM:先讲清"为什么需要SIGALRM"(传统定时的局限),再拆解alarm
/setitimer
的用法差异,最后通过"函数超时控制""周期性日志打印"两个完整案例(含代码、Makefile、Mermaid时序图),展示SIGALRM如何实现非阻塞定时,帮助开发者掌握这一"进程内置闹钟"工具。
<解析>
从"手机闹钟"看SIGALRM:进程的非阻塞定时神器
想象一下:你需要3分钟后给朋友回消息,有两种方式可选:
- 盯着时钟等 :放下手头所有事,盯着手机倒计时,3分钟一到就回消息------这是传统
sleep
的"阻塞定时",效率低; - 设个闹钟:打开手机闹钟,设3分钟后响,期间继续工作,闹钟响了再回消息------这是SIGALRM的"非阻塞定时",不浪费时间。
在Linux系统中,SIGALRM就是进程的"手机闹钟":进程通过alarm
或setitimer
给内核"设闹钟",内核在指定时间后发送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) (由alarm
或setitimer
触发的定时信号)。
其核心逻辑是:
- 进程调用
alarm
或setitimer
,向内核"提交定时任务"(如"3秒后提醒我"); - 内核启动定时器,开始倒计时;
- 时间到后,内核向进程发送SIGALRM信号;
- 进程根据预设的信号处理逻辑响应(如执行函数、记录日志)。
默认情况下,进程收到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;
- 核心特性 :
- 精度低:仅支持秒级,无法精确到毫秒/微秒;
- 单次触发:时间到后发送一次SIGALRM,定时器自动取消;
- 覆盖性:多次调用
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
:高精度周期性定时(专业模式)
setitimer
比alarm
更灵活,支持"微秒级精度"和"周期性定时",可满足复杂场景(如每隔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
类型,定义如下:cstruct 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
)。
核心特性:
- 高精度:支持微秒级,比
alarm
的秒级精度更高; - 周期性:可设置循环触发(如每隔1秒一次);
- 灵活计时:支持真实时间、用户态时间等多维度。
示例(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:自定义信号处理函数(最常用)
通过signal
或sigaction
注册SIGALRM的处理函数,当信号触发时,内核会中断当前进程的执行,跳转到处理函数,执行完成后返回原逻辑继续执行(类似"闹钟响了,暂停工作处理闹钟,处理完继续工作")。
关键注意事项:处理函数需"可重入"
信号处理函数可能在进程执行任何代码时被调用(如进程正在执行malloc
时收到SIGALRM),因此必须使用可重入函数(Reentrant Function),避免使用非可重入函数导致数据不一致或死锁。
常见的可重入/非可重入函数对照表:
类别 | 可重入函数(推荐) | 非可重入函数(禁用) |
---|---|---|
输入输出 | write (写入固定描述符,如stderr) |
printf 、fprintf (使用全局缓冲区) |
内存操作 | 无(避免在处理函数中分配/释放内存) | malloc 、free (全局内存池) |
字符串操作 | memcpy 、memset (操作局部缓冲区) |
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:setitimer
的it_interval
与it_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
会覆盖缓冲区,导致输出乱码或程序崩溃。
避坑方法:
- 处理函数中仅使用可重入函数(如
write
); - 复杂逻辑(如格式化时间)放在主程序中,处理函数仅设置全局标志。
正确代码:
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秒,超时则退出"。
案例需求
- 定义
slow_function
:模拟耗时任务(如循环10秒); - 用SIGALRM设置3秒超时,超时后终止
slow_function
; - 程序输出超时日志,正常退出,不崩溃。
完整代码(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
操作步骤
-
编译运行 :
bashmake 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------这个非阻塞的定时神器,能让你的程序更高效、更健壮。