定时器与睡眠 — 从 jiffy 到 POSIX timer
定时器与睡眠 — 从 jiffy 到 POSIX timer
这一章讲什么: 「睡到某个时刻再醒」和「干活干到一半被定时器 打断一下」是两类不同的问题。本章把支撑它们的地基全挖出来: 硬件时钟怎么 ticking(为什么老内核的软件定时器精度到一个 jiffy)、 sleep 家族暗藏的共享定时器冲突、nanosleep 重启动带来的 timer drift、以及正解——绝对时间的 clock_nanosleep 和 POSIX timer。 项目:两版终端进度条(alarm 版 → POSIX timer 版)和一个文件 监视器 watchfiles,把第 8 章的信号军规全部实战一遍。
1. 顶层全景:时间从哪来,定时器到哪去
硬件层:RTC(电池保持的 wall clock,开机校时用)
PIT(可编程间隔定时器,内核约 1000Hz 发 tick)
TSC/HPET(高精度计数器,内核 2.6 起的高分辨率定时器底座)
│ tick 的间隔 = 1 个 jiffy(值随内核配置,1–10ms)
▼
系统时钟:纯软件,开机用 RTC 或 NTP 校一次,之后靠数 PIT 的 tick 前进
│ 老内核上软件定时器精度 ≤ jiffy;新内核走 HPET 高精度定时器
▼
睡眠函数:sleep/usleep/nanosleep/clock_nanosleep
│ 被打断 → EINTR → 余量重睡 → timer drift(相对时间累积误差)
│ 绝对睡眠(TIMER_ABSTIME)→ 免疫 drift
▼
定时器:alarm(1 秒粒度、一次性)
│ → setitimer/getitimer(1970s–80s,2008 已废弃)
│ → POSIX timer:timer_create + timer_settime + timer_getoverrun
▼
通知路径:信号(SIGEV_SIGNAL)/ 线程回调(SIGEV_THREAD)/ 不通知图说:越往下分辨率越高、越能精确控制;代价是 API 越繁琐。 主线例子:同一 6.789 秒的睡眠,相对睡法漂移 0.42 毫秒、绝对睡法 漂移 0.09 毫秒——数字来自原书实测,这一章的机理全藏在这两个数里。
2. 地基:硬件时钟、jiffy 与系统时钟
计时的硬件家当1: RTC(real-time clock,电池保持 wall clock, 关机也走;Linux 只在开机时拿它初始化软件时间;还能编程 2–8192Hz 周期中断、当闹钟用);PIT(programmable interval timer,内核 通常编程成约 1000Hz 发中断,这个中断叫 timer interrupt,间隔 就是 1 个 tick;内核常量 HZ 是 tick 频率,jiffy 是一个 tick 的时长——100Hz 内核 jiffy=0.01s,250Hz 内核 0.004s);更精的 有 TSC(时间戳计数器,振荡频率可达 1GHz)和 HPET(高精度 事件定时器,内部计数 ≥100KHz,内核 2.6 起支持)。
系统时钟是纯软件时钟:开机时从 RTC 或 NTP 服务校时,此后每次 PIT 中断到来就累加——「靠数硬件定时器的 tick 过日子」2。 这事和编程的关系一句话:老内核上软件定时器的精度天花板就是 jiffy;新内核走 HPET 高精度定时器,不受这个限3。
3. 睡眠函数:共享定时器的暗雷与 nanosleep
sleep/usleep 的隐藏问题:它们背后共用一个软件定时器,还和对 alarm()
共用的那个是同一个——重叠调用互相干扰,还会和信号的产生/交付
打架。man 页的建议:改用 nanosleep()4。
nanosleep(&req, &rem) 不碰信号、纳秒分辨率;被打断时返 -1/EINTR,
rem 里是没睡完的余量,标准重睡循环是:
do {
retval = nanosleep(&initial_sleep, &remainder);
if ( retval == -1 && errno != EINTR ) fatal_error(errno, "nanosleep");
else initial_sleep = remainder; /* 接着睡剩下的 */
} while ( retval < 0 );主线例子:drift 实测
nanosleep_demo2 睡 6.789123456 秒、中途打了 4 个信号:
Sleep lasted 6.7895459130 seconds, 0.0004224570 seconds longer than requested.信号加到 14 个,漂移涨到 0.00118 秒;32 个,0.00358 秒。 机理:每次从 handler 返回再重新入睡,请求的时长是相对量,起点 每次往后挪一点——重启动的次数越多,欠账越多,这就是 timer drift5。
4. clock_nanosleep:绝对时间免疫 drift——以及一个著名陷阱
clock_nanosleep(clockid, flags, &request, &remain) 多两个参数:
选时钟(CLOCK_REALTIME / CLOCK_MONOTONIC),以及 TIMER_ABSTIME
——把 request 解释为「睡到时钟到达这个绝对时刻」,像设闹钟:
现在 11 点、设 15 点,就睡到 15 点,不是睡 15 小时6。要睡相对
时长,先 clock_gettime 拿到现在、加上间隔、再把目标时刻传进去。
绝对睡法被打断后,用同一个目标时刻重睡——起点永远不变,漂移
只剩调度噪声:同样的实验,绝对版漂移 0.0000911 秒,30 个信号
打断后仍稳定在 0.00012 秒7。
陷阱(书里加框强调):nanosleep 被信号打断返回 -1 并置 errno=EINTR;clock_nanosleep 被信号打断直接返回正数错误码 EINTR。判断写法完全不同:
retval = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &end, NULL);
if ( retval == EINTR ) /* 被打断,用同一 end 再睡 */
else if ( retval > 0 ) /* 真错误 */
else /* retval==0,睡到点了 */两套返回值约定混用,是这段 API 最著名的坑8。
5. alarm():最老的定时器
alarm(秒) 安排 seconds 秒后给自己发 SIGALRM;精度 1 秒;
一次性;再调一次就把前一个冲掉(返回上一个剩余秒数:
alarm(10) 后 4 秒再 alarm(20) → 返回 6);alarm(0) 取消9。
妙用:给长任务设「超时保险」——到点没干完就收到 SIGALRM 收工。
项目一:进度条 v1(alarm 版)
终端进度条的刷新机制,三件小事全是学问:
- 同行动画:行首写
\r(回车,光标挪回行首)再画整行,新行 盖旧行;行尾换行\n会把光标推到下一行。 - 行缓冲:printf 不带
\n不会立刻出——C 库对终端是行缓冲; 所以 handler 里printf(...); fflush(stdout);必须成对10。 - handler 里续钟:alarm 是一次性的,要在 SIGALRM handler 里
alarm(REFRESH_INTERVAL)才能周期刷新——每次 handler 开跑到 重新上钟之间的延迟会累积(「下次更晚一点」),这就是 alarm 当 周期定时器的先天不足11。
填充条用两个 memset(先全填 - 再把左端 finished_work 个改 #);
完成比例存在文件作用域 fraction_completed(handler 和主程序
都要看);主程序更新它时用 sigprocmask 挡 SIGALRM 防竞争。
CTRL-C/CTRL-\ 的 handler:擦掉条 → raise(SIGTERM) 自杀——又是
第 8 章那套「清理后按默认动作办」12。
6. POSIX timer:正规军的玩法
三件套(-lrt 链接,glibc 2.17 起并入 libc):
timer_create(clockid, &sigevent, &timerid); /* 造一个未上膛的定时器 */
timer_settime(timerid, flags, &newval, &old); /* 上膛/重设/关(it_value=0) */
timer_delete(timerid); /* 销掉 */struct itimerspec { it_value; it_interval; }——初值(首响时间)
与周期(0 = 单发,非 0 = 每周期自动重装,真正的周期定时器);
flags 同样支持 TIMER_ABSTIME(初值按绝对时刻解释)13。
sigevent 的四种通知方式(POSIX timer 比 alarm 强的地方):
| sigev_notify | 到期时 |
|---|---|
SIGEV_SIGNAL | 发 sigev_signo 指定的信号;handler 的 si_code==SI_TIMER,si_value 可带 timerid |
SIGEV_NONE | 不通知(用来「让时钟跑着」) |
SIGEV_THREAD | 开一个新线程跑 sigev_notify_function(sigev_value) |
SIGEV_THREAD_ID | 信号发给指定线程(Linux 特有) |
timer overrun:通知丢了几个
信号不排队的规则(第 8 章)对定时器同样成立:每个定时器在进程
里最多挂起 1 个通知,处理不过来就丢——丢了几个叫 overrun,
timer_getoverrun(timerid) 报数(handler 里也能调)。意义:就算
通知丢了,计数还在,「每次通知该 +1」的场景能补上欠账14。
项目二:进度条 v2(POSIX timer 版)
三处升级:通知改 SIGUSR1(免得和别人的 SIGALRM 抢);刷新间隔 用户可定(0.001 秒到 8 秒);模拟任务里的小睡改用 clock_nanosleep(TIMER_ABSTIME)——小节间隔是固定绝对时刻, 周期刷新不再被「越睡越短」扭曲,也没有 overrun 累积15。 收尾的清理顺序也值得抄:任务完 → 等一拍(给最后一次刷新机会)→ timer_settime 把 it_value 清零关定时器 → SIG_IGN 通知信号 → 擦条 → 打印 Done16。
7. 项目三:watchfiles——文件监视器
监视一组文件的大小,变了就刷新屏幕,稳定几拍后自动退出,q 键 提前退。新知识点:
- ANSI 转义序列:
\033[(CSI)开头,控制终端行为——\033[2J清屏、\033[1;1H光标回左上角、\033[<行>;1H移到指定行。所有终端模拟器都认,这就是 ncurses(第 19 章) 出现之前的原始做法17。 - handler 全 async-signal-safe:不碰 printf/strcat 之外的 stdio——先 strcpy/strcat 拼出整屏字符串,一次 write 打出去; 屏幕布局 = 「刷新区(每拍重建)+ 静态区(提示行)」18。
- 状态全靠文件作用域:size 数组、stable_cnt、stopflag (volatile sig_atomic_t);主循环 read 等 q,被信号打断返 EINTR 就 continue;退出前 tcflush 清掉残留的换行19。
8. 作者的判断与证据
有明证的:
- drift 两组对照数据(相对 0.42ms/4 信号 → 0.0036s/32 信号; 绝对恒 0.09–0.12ms)是同机实测57。
- clock_nanosleep 返回值不返 -1 的差异,加框强调并给了正确 判法8。
- overrun 的「每定时器最多挂起一个」语义出自 man 页14。
作者的判断:
- 「alarm 只适合教学,周期刷新上 POSIX timer」——由 drift 数据 支撑的工程结论15。
- watchfiles 里「每拍整屏重建字符串再一次 write」优于「逐字段 定位改」——实现简洁性优先,理直气壮的取舍18。
- 「先挡信号再更新共享变量」的用法在第 8 章是军规,本章是实际 需求——教学编排的痕迹,但理由真实12。
9. 边界与局限
- 实时信号(SIGRTMIN 起的带数据、可排队信号)只在 sigevent 一节捎带,没展开——那是信号当「队列」用的进阶,本书点到为止;
- setitimer/getitimer 只给了「已废弃,用 POSIX timer」的迁移路径, 没展开13;
- watchfiles 的屏幕布局假设文件数 < 行数(拿不到终端行数, 那是第 18 章的 ioctl);也没有用 ncurses;
- 精度讨论止步「HPET/HRT 之后不受 jiffy 限」,没讲内核时钟源 切换细节3。
10. 可带走的
主线例子,一行复述: 睡 6.789123456s 中途来 4 个信号——相对 重睡漂移 +0.42ms(次数越多欠越多),绝对睡眠(TIMER_ABSTIME 固定目标时刻)漂移稳定 +0.09ms。
- jiffy = PIT tick 的间隔(HZ 分之一),老内核软件定时器精度 的上限;HPET 高精度定时器之后不受此限。
- sleep/usleep/alarm 共用一个软件定时器,会互相干扰;认真 用 nanosleep。
- nanosleep 被打断返 -1/EINTR,rem 拿余量;重睡循环要 do-while。
- 相对重睡累积 timer drift;要稳用 clock_nanosleep 的 TIMER_ABSTIME(先 gettime 加间隔)。
- clock_nanosleep 打断返回正数 EINTR 不返 -1——判断写法 和 nanosleep 不同!
- alarm(秒)= N 秒后 SIGALRM;再调冲掉旧的(返回剩余), alarm(0) 取消;handler 里续钟可当周期用,但有累积延迟。
- 终端同行刷新 =
\r回行首重画;printf 要 fflush(行缓冲)。 - POSIX timer:it_value 初值、it_interval 周期(0=单发)、 it_value=0 关;SIGEV_SIGNAL/THREAD/NONE 通知可选。
- timer overrun = 丢的通知数,handler 里 timer_getoverrun 补账。
- handler 里做屏幕更新:只碰 async-signal-safe 函数,拼好 字符串一次 write;屏幕分刷新区+静态区。
11. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| RTC/PIT/TSC/HPET 与 jiffy | Chapter 9: Timers and Sleep Functions | text/15-ch09-chapter-9-timers-and-sleep-functions.txt:144(搜「real-time clock」) · :166(搜「metronome」) · :169(搜「jiffy」) |
| 系统时钟靠 tick 前进 | 同上 | :192(搜「every time it receives」) |
| sleep/usleep 共享定时器问题 | 同上 | :221(搜「share the same timer」) · :225(搜「nanosleep」) |
| nanosleep 与重睡循环 | 同上 | :232(搜「int nanosleep」) · :411(搜「do」) |
| drift 实测数据 | 同上 | :512(搜「0.0004224570」) · :519(搜「0.0035780350」) |
| clock_nanosleep 绝对睡眠 | 同上 | :561(搜「alarm clock」) · :589(搜「t2, not t1 + t2」) |
| 返回值陷阱(EINTR 非 -1) | 同上 | :616(搜「returns the error code EINTR」) |
| 绝对版 drift 对照 | 同上 | :674(搜「0.0000910600」) |
| alarm 语义与取消 | 同上 | :705(搜「SIGALRM」) · :707(搜「canceled」) |
| 进度条 v1 三机制 | 同上 | :1066(搜「return character」) · :1093(搜「fflush」) · :923(搜「race condition」) |
| 竞争与 sigprocmask 保护 | 同上 | :947(搜「sigprocmask(SIG_BLOCK」) |
| itimerspec 与 timer_settime | 同上 | :1369(搜「struct itimerspec」) · :1377(搜「disarmed」) |
| sigevent 四种通知 | 同上 | :1293(搜「SIGEV_NONE」) · :1298(搜「SIGEV_THREAD」) |
| timer overrun | 同上 | :1402(搜「at most one signal」) · :1406(搜「timer_getoverrun」) |
| 进度条 v2 的绝对睡眠改造 | 同上 | :1519(搜「clock_nanosleep(CLOCK_MONOTONIC」) · :1647(搜「timer_settime」) |
| ANSI 转义序列 | 同上 | :1910(搜「Control Sequence Introducer」) · :1913(搜「erases the entire」) |
| watchfiles 的 handler 布局 | 同上 | :2058(搜「update_stats」) · :2097(搜「write(STDOUT_FILENO」) |
Footnotes
-
出处:「Chapter 9: Timers and Sleep Functions」第 143–182 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:144,搜「real-time clock」) · :166(搜「metronome」) · :169(搜「jiffy」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 183–205 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:192,搜「every time it receives」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 200–205 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:201,搜「resolution of software timers」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 207–225 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:221,搜「share the same timer」) · :225(搜「nanosleep() instead」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 505–523 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:512,搜「0.0004224570」) · :519(搜「32 signals」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 558–570 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:561,搜「alarm clock」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 665–682 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:674,搜「0.0000910600」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 612–626 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:616,搜「returns the error code EINTR」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 690–736 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:705,搜「SIGALRM」) · :707(搜「canceled」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 1060–1075 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1066,搜「return character」)与第 1076–1096 段的行缓冲框(text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1093,搜「fflush」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 1203–1212 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1208,搜「cumulative delays」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 923–951 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:947,搜「sigprocmask(SIG_BLOCK」)与第 1148–1156 段的终止 handler。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 1214–1242 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1220,搜「obsolete」)与第 1367–1393 段(text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1369,搜「struct itimerspec」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 1395–1434 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1402,搜「at most one signal」) · :1406(搜「timer_getoverrun」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 1442–1533 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1519,搜「clock_nanosleep(CLOCK_MONOTONIC」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 1593–1662 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1647,搜「timer_settime」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 1900–1964 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:1910,搜「Control Sequence Introducer」) · :1913(搜「erases the entire」)。 ↩ -
出处:「Chapter 9: Timers and Sleep Functions」第 1968–2033 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:2005,搜「async-signal-safe」)与第 2058–2107 段的 handler(text/15-ch09-chapter-9-timers-and-sleep-functions.txt:2097,搜「write(STDOUT_FILENO」)。 ↩ ↩2 -
出处:「Chapter 9: Timers and Sleep Functions」第 2140–2183 段(
text/15-ch09-chapter-9-timers-and-sleep-functions.txt:2170,搜「while ( !stopflag )」) · :2177(搜「tcflush」)。 ↩