第 23 章 定时器与休眠 (Timers and Sleeping)
核心结论
-
三类 Interval Timer:
setitimer()设置 ITIMER_REAL(wall-clock,SIGALRM)、ITIMER_VIRTUAL(用户态 CPU,SIGVTALRM)、ITIMER_PROF(用户+内核 CPU,SIGPROF);每类每进程一个。 -
Alarm 简易 API:
alarm(seconds)单次实时 timer;返回旧 timer 剩余秒数;alarm(0)取消;与 setitimer 共享 ITIMER_REAL timer。 -
阻塞系统调用的超时:标准技巧——
alarm()+ 不带 SA_RESTART 的 SIGALRM handler + read/write;handler 触发后阻塞调用返回 EINTR。 -
POSIX Clocks:
clock_gettime(CLOCK_REALTIME/CLOCK_MONOTONIC/CLOCK_PROCESS_CPUTIME_ID/CLOCK_THREAD_CPUTIME_ID/CLOCK_BOOTTIME/CLOCK_MONOTONIC_RAW)读取各种时间;CLOCK_REALTIME可被 settimeofday 跳变;CLOCK_MONOTONIC不受 NTP 调整。 -
POSIX Timers:
timer_create()创建定时器;timer_settime()设置;timer_delete()删除;可投递到进程(默认 SIGRTMIN+1)、线程、或通过 sigevent。 -
timerfd:Linux 2.6.25+——把定时器到期转为文件描述符事件;与 select/poll/epoll 整合;典型场景:单线程事件循环统一处理 I/O + 定时器。
|
本章主旨
本章介绍 Linux 的定时器与休眠机制——从经典 setitimer/alarm 到 POSIX clocks/timers,再到 Linux 特有的 timerfd。读者应掌握:三类 interval timer 的语义与信号、ITIMER_REAL 与 alarm 的关系、阻塞系统调用超时的标准技巧、POSIX 各种 clock 的区别(特别是 REALTIME vs MONOTONIC)、POSIX timer 的高级特性(线程投递、sigval 数据)、以及 timerfd 与事件循环的整合。这些是写「时间驱动代码」的必备知识。 |
一、核心概念
本章围绕 6 个核心概念展开:从经典 Interval Timer 入手,到 alarm 简易 API、阻塞调用超时技巧、POSIX clocks、POSIX timers,最后到 timerfd。
| 概念 | 定义 + 重要性 | 实现提示 |
|---|---|---|
Interval Timers(setitimer) |
setitimer 设置三类 timer:REAL(wall-clock/SIGALRM)、VIRTUAL(用户 CPU/SIGVTALRM)、PROF(用户+内核 CPU/SIGPROF);每类每进程一个;itimerval 含 value(首次到期)+ interval(重复间隔)。 |
§23.1;与 alarm 共享 ITIMER_REAL;getitimer 查询剩余时间;timers 跨 exec 保留但不跨 fork 继承。 |
alarm 简易 API |
alarm(seconds) 是单次 ITIMER_REAL 的简易封装;返回旧 timer 剩余秒数;alarm(0) 取消;与 setitimer 共享 ITIMER_REAL timer。 |
§23.1;典型用途:进程超时强杀(不设 handler 默认 SIGALRM 终止);与 sleep 的交互未标准化(不要混用)。 |
阻塞调用超时技巧 |
标准模式:设 SIGALRM handler(不 SA_RESTART)→ alarm(s) → 阻塞系统调用 → 检查 EINTR;典型用于 read/accept 超时。 |
§23.3;Linux 上 alarm + read 是终端读取超时的经典方案;Linux 2.6.25+ 更优用 timerfd + poll/select。 |
POSIX Clocks(clock_gettime) |
clock_gettime(clockid) 读取各种时间;CLOCK_REALTIME(wall-clock 可被 settimeofday 跳变)、CLOCK_MONOTONIC(单调不跳变)、CLOCK_PROCESS_CPUTIME_ID(进程 CPU)、CLOCK_THREAD_CPUTIME_ID(线程 CPU)、CLOCK_BOOTTIME(含休眠时间)、CLOCK_MONOTONIC_RAW(原始单调)。 |
§23.5;优先用 CLOCK_MONOTONIC 测量时间间隔——不受 NTP/adjust 跳变影响;CLOCK_REALTIME 适合作「墙钟时间戳」。 |
POSIX Timers(timer_create) |
timer_create 创建 POSIX timer;clockid 指定基于哪个 clock;sigevent 指定到期通知方式(默认投递 SIGRTMIN+1 给进程,可改为线程 + sigval)。 |
§23.6;timer_settime 设置启动/间隔;timer_gettime 查询;timer_delete 删除;支持 timer_t 数组可同时存在多个 timer。 |
timerfd |
timerfd_create/timefd_settime 把定时器到期转为可读 fd;与 select/poll/epoll 整合;Linux 2.6.25+。 |
§23.7;fd 读出 uint64_t 计数(到期次数);典型场景:epoll 主循环处理 I/O + 定时器;替代「timer + pipe」自建方案。 |
二、详细笔记
23.1 Interval Timers
What:setitimer() 是经典 interval timer API;每类 timer 每进程一个;到期产生信号;itimerval 含「首次到期时间」与「重复间隔」。
Why:让进程按固定时间节奏工作——周期任务、心跳、超时检测——是「时间驱动编程」的基础。
How:
#include <sys/time.h>
struct itimerval {
struct timeval it_interval; /* 重复间隔 */
struct timeval it_value; /* 首次到期 */
};
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
int getitimer(int which, struct itimerval *curr_value);
三类 timer(§23.1):
-
ITIMER_REAL——wall-clock;到期发 SIGALRM;最常用。 -
ITIMER_VIRTUAL——用户态 CPU;到期发 SIGVTALRM;用于测用户 CPU。 -
ITIMER_PROF——用户 + 内核 CPU;到期发 SIGPROF;用于 profiling(GPROF)。
要点:
-
it_value = 0禁用 timer。 -
it_interval = 0单次 timer;非 0 周期 timer。 -
与 alarm 共享 ITIMER_REAL timer——互相覆盖。
-
getitimer查询剩余时间。 -
timers 跨
exec()保留;不跨fork()继承。
When:
-
周期任务——
setitimer(ITIMER_REAL, {it_value=1s, it_interval=1s}),每秒触发一次。 -
心跳检测——handler 中设置
volatile sig_atomic_t flag。 -
超时强杀——不设 handler,SIGALRM 默认终止进程。
Example:第 23 章 Listing 23-1 real_timer.c——演示 REAL timer:
// 摘自《The Linux Programming Interface》第 23 章(Listing 23-1 简化)
struct itimerval itv;
itv.it_value.tv_sec = 1; itv.it_value.tv_usec = 800000;
itv.it_interval.tv_sec = 1; itv.it_interval.tv_usec = 0;
setitimer(ITIMER_REAL, &itv, NULL);
/* SIGALRM handler 设 gotAlarm = 1;主循环检查 gotAlarm */
23.2 Timer 精度与高分辨率 Timer
What:传统 timer 精度受限于「软件时钟频率」(jiffy,Linux 默认 4ms 或 10ms);2.6.21+ 内核可启用 CONFIG_HIGH_RES_TIMERS——精度可达微秒级。
Why:高频 timer(如 1ms 周期)需要高分辨率支持;高精度测量需要微秒甚至纳秒。
How:
-
Linux 2.6.21+:
CONFIG_HIGH_RES_TIMERS=y;现代发行版通常默认开启。 -
检测:
clock_getres(CLOCK_MONOTONIC, &ts)查看分辨率。 -
微秒精度:
timerfd_settime支持itimerspec(含纳秒);nanosleep支持纳秒。
When:高频控制(如 1kHz 控制循环)、性能测量——优先用 clock_gettime(CLOCK_MONOTONIC) + nanosleep。
23.3 阻塞系统调用的超时
What:标准技巧——用 alarm() + 不带 SA_RESTART 的 SIGALRM handler 让阻塞调用(如 read)按预期超时。
Why:避免永久阻塞在 I/O 上——网络应用常见需求。
How:5 步模式(§23.3):
/* 1. 设 SIGALRM handler(不 SA_RESTART) */
struct sigaction sa = {0};
sa.sa_handler = sigalrm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; /* 不自动重启 */
sigaction(SIGALRM, &sa, NULL);
/* 2. 设 timer */
alarm(seconds); /* 或 setitimer */
/* 3. 阻塞系统调用 */
n = read(fd, buf, sizeof(buf));
/* 4. 禁用 timer(如果调用提前返回) */
alarm(0);
/* 5. 检查 EINTR */
if (n == -1 && errno == EINTR) { /* 超时 */ }
else { /* 正常 */ }
When:
-
终端 read 超时——
alarm(5)+read(STDIN_FILENO, …)。 -
简单网络读取超时——Linux 2.6.25+ 更优用
SO_RCVTIMEO(socket 选项)。 -
多 fd 超时——用
poll/select的 timeout 参数。
Example:第 23 章 Listing 23-2 read_timeout.c——用 alarm 让 read 超时。
23.4 Sleep
What:sleep(seconds) 休眠整数秒;nanosleep(req, rem) 纳秒级休眠(POSIX);clock_nanosleep(CLOCK_REALTIME, 0, req, rem) 加 clock 选择。
Why:让出 CPU——主循环节流、轮询间隔、节流防 busy-loop。
How:
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);
int clock_nanosleep(clockid_t clockid, int flags,
const struct timespec *request,
struct timespec *remain);
nanosleep 与 sleep 的关系:
-
nanosleep是 POSIX 标准;sleep是历史接口。 -
Linux 上
sleep()用nanosleep()实现;alarm()实现的sleep()是另一回事(glibc)。 -
nanosleep被信号打断时把剩余时间写入rem——可重入循环。
When:
-
主循环节流——
nanosleep(&{0, 10*1000*1000}, NULL)100Hz 循环。 -
精确延迟——
clock_nanosleep(CLOCK_MONOTONIC, …)不受系统时钟跳变影响。
23.5 POSIX Clocks
What:clock_gettime(clockid, &ts) 读取各种时间;多种 clockid 表达不同时间维度。
Why:传统 time()/gettimeofday() 不够灵活——time_t 秒级;gettimeofday 微秒但只 wall-clock;POSIX clocks 提供多种时间源与分辨率。
How:
| clockid | 含义 | 用途 |
|---|---|---|
CLOCK_REALTIME |
wall-clock |
日志时间戳;可被 settimeofday 跳变 |
CLOCK_REALTIME_COARSE |
REALTIME 快速版(低精度) |
高频快速读取 |
CLOCK_MONOTONIC |
系统启动后的单调时间 |
测量间隔;不受 NTP 跳变 |
CLOCK_MONOTONIC_RAW |
单调原始(不受 NTP slew) |
高精度测量 |
CLOCK_BOOTTIME |
含休眠时间的单调时间 |
跨休眠的总时间 |
CLOCK_PROCESS_CPUTIME_ID |
进程用户+内核 CPU |
profiling |
CLOCK_THREAD_CPUTIME_ID |
线程 CPU |
线程 profiling |
clock_getres(clockid, &ts) 查询时钟分辨率——检查是否支持高分辨率。
clock_settime(CLOCK_REALTIME, &ts)——只有 REALTIME 可设置;需 CAP_SYS_TIME。
When:
-
测量时间间隔——
CLOCK_MONOTONIC(不受跳变)。 -
日志时间戳——
CLOCK_REALTIME+strftime转可读格式。 -
高频性能测量——
CLOCK_MONOTONIC_RAW或CLOCK_PROCESS_CPUTIME_ID。
Example:
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
do_work();
clock_gettime(CLOCK_MONOTONIC, &end);
double elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;
23.6 POSIX Timers
What:timer_create(clockid, sigevent, timerid) 创建定时器;timer_settime 启动/修改;timer_delete 删除;可同时存在多个 timer(区别于 setitimer 每类一个)。
Why:经典 setitimer 每类只有一个 timer;POSIX timers 支持多 timer 数组;可投递到线程而非整个进程。
How:
#include <signal.h>
#include <time.h>
timer_t timerid;
struct sigevent sev = {
.sigev_notify = SIGEV_SIGNAL, /* 投递信号 */
.sigev_signo = SIGUSR1, /* 或 SIGRTMIN+x */
.sigev_value.sival_ptr = &timerid /* sigval 携带数据 */
};
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
struct itimerspec its = {
.it_value = { .tv_sec = 5, .tv_nsec = 0 }, /* 首次到期 5 秒 */
.it_interval = { .tv_sec = 1, .tv_nsec = 0 } /* 之后每秒 */
};
timer_settime(timerid, 0, &its, NULL);
timer_delete(timerid);
投递方式(sigev_notify):
-
SIGEV_SIGNAL——投递信号(默认 SIGRTMIN+1 给进程);SA_SIGINFO handler 通过si_value取sigev_value。 -
SIGEV_THREAD——在新线程调用sigev_notify_function(sigev_value)。 -
SIGEV_THREAD_ID——投递信号给指定线程(sigev_notify_thread_id是 pthread_t)。
When:
-
多 timer 并存——比 setitimer 灵活。
-
多线程——线程独占 timer(SIGEV_THREAD_ID)。
-
一次性高分辨率 timer——
it_value非 0,it_interval全 0。
-
Example:用 timer_create + 实时信号 handler 取 si_value.sival_ptr 识别是哪个 timer 到期。
23.7 timerfd
What:Linux 2.6.25+——timerfd_create(clockid, flags) 创建定时器 fd;timerfd_settime(fd, flags, new, old) 设置;fd 可读时表示 timer 到期(read 返回 uint64_t 计数)。
Why:把定时器融入 epoll——单线程事件循环统一处理 I/O + 定时器;比「timer + pipe」自建方案简单。
How:
#include <sys/timerfd.h>
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
struct itimerspec its = {
.it_value = { .tv_sec = 5, .tv_nsec = 0 },
.it_interval = { .tv_sec = 1, .tv_nsec = 0 }
};
timerfd_settime(tfd, 0, &its, NULL);
/* tfd 加入 epoll */
struct epoll_event ev = { .events = EPOLLIN, .data.fd = tfd };
epoll_ctl(epfd, EPOLL_CTL_ADD, tfd, &ev);
/* 事件循环 */
while (1) {
int n = epoll_wait(epfd, events, MAX, -1);
for (i = 0; i < n; i++) {
if (events[i].data.fd == tfd) {
uint64_t cnt;
read(tfd, &cnt, sizeof(cnt));
/* cnt 是到期次数 */
}
}
}
flags:
-
TFD_CLOEXEC——exec 时关闭。 -
TFD_NONBLOCK——非阻塞 read(避免无数据时阻塞)。
到期语义:
-
单次 timer(
it_interval = 0)——read 一次后 fd 沉默。 -
周期 timer——read 一次得到 uint64_t 表示「上次 read 以来到期次数」;累加未读次数(默认可能溢出,超过 2^64 才停止)。
-
取消 timer:
timerfd_settime(tfd, 0, &(itimerspec){0}, NULL)。
-
When:
-
主循环 epoll + 定时器——单线程简洁。
-
替代
alarm+ SIGALRM handler 模式——更易整合到事件循环。
Example:第 23 章 demo_timerfd.c——timerfd_create + epoll 处理 5 秒后每秒一次的周期事件。
三、关键图表
(本章无独立编号图表)
|
三类 Interval Timer 对照
|
|
POSIX Clock 选择
|
四、思维导图
mindmap
root((第 23 章 定时器与休眠))
Interval Timer
ITIMER REAL VIRTUAL PROF
setitimer getitimer
SIGALRM SIGVTALRM SIGPROF
每类每进程一个
alarm
单次 timer
与 setitimer REAL 共享
alarm 0 取消
返回旧剩余秒数
阻塞调用超时
alarm handler 不 SA RESTART
EINTR 检查
5 步模式
sleep nanosleep
sleep 整数秒
nanosleep 纳秒
clock nanosleep clockid
信号打断 rem
POSIX Clocks
CLOCK REALTIME
CLOCK MONOTONIC
CLOCK BOOTTIME
CLOCK PROCESS CPU
clock gettime getres
POSIX Timers
timer create
CLOCK MONOTONIC
SIGEV SIGNAL THREAD
itimerspec
多 timer 数组
timerfd
fd 化定时器
epoll 整合
read uint64
TFD CLOEXEC NONBLOCK
高分辨率
CONFIG HIGH RES TIMERS
微秒精度
clock getres 检测
五、重点与易错点
-
三类 Interval Timer——ITIMER_REAL(wall-clock/SIGALRM)、ITIMER_VIRTUAL(用户 CPU/SIGVTALRM)、ITIMER_PROF(用户+内核 CPU/SIGPROF);每类每进程一个。
-
alarm 与 setitimer(ITIMER_REAL) 共享 timer——互相覆盖;SUSv3 留作未指定;跨平台代码只用其中一个。
-
timers 跨 exec 保留、不跨 fork 继承——子进程不会继承父进程的 timer。
-
阻塞调用超时模式:alarm + 不带 SA_RESTART 的 handler → 阻塞系统调用 → 检查 EINTR;Linux 2.6.25+ 更优用 SO_RCVTIMEO/socket 或 timerfd。
-
sleep 与 alarm 交互未标准化——不要混用;sleep 可能被信号打断后写 EINTR 或留下剩余时间(nanosleep)。
-
CLOCK_REALTIME 可被 settimeofday/NTP step 跳变——测量时间间隔用 CLOCK_MONOTONIC;高精度用 CLOCK_MONOTONIC_RAW。
-
CLOCK_MONOTONIC 受 NTP slew(频率调整)影响——需要完全不受影响用 CLOCK_MONOTONIC_RAW。
-
POSIX timers 可同时存在多个(区别于 setitimer);可投递到线程(SIGEV_THREAD / SIGEV_THREAD_ID);通过 sigval 携带 timer ID。
-
timerfd 与 epoll 整合——单线程事件循环统一处理 I/O + 定时器;read 返回 uint64_t 表示到期次数;取消设
it_value/it_interval = 0。 -
高分辨率 timer 自 2.6.21——CONFIG_HIGH_RES_TIMERS=y;
clock_getres检测;微秒精度可期。 -
SIGEV_THREAD 启动新线程——回调在 glibc 管理的线程中;注意线程创建开销;不适用高频 timer。
-
POSIX timer 投递默认 SIGRTMIN+1——可改用 sigev_signo 指定其他信号;与实时信号配合 SA_SIGINFO handler。
-
itimerspec 含纳秒——比 itimerval 的微秒精度更高;适合配合高分辨率 timer。
-
阻塞 I/O 超时的现代方案:
SO_RCVTIMEO/SO_SNDTIMEO(socket)、O_NONBLOCK+ poll/epoll、timerfd+ epoll;尽量避免 alarm + SIGALRM 传统模式。 -
跨章衔接:第 22 章 SIGALRM 是 setitimer 的投递机制;第 14 章 /proc/sys 与高精度 timer;第 63 章 epoll 与 timerfd 整合;第 49 章 mmap 与 CLOCK_MONOTONIC 时间戳;第 53 章 POSIX 信号量与超时。