第 23 章 定时器与休眠 (Timers and Sleeping)

      +

      核心结论

      • 三类 Interval Timersetitimer() 设置 ITIMER_REAL(wall-clock,SIGALRM)、ITIMER_VIRTUAL(用户态 CPU,SIGVTALRM)、ITIMER_PROF(用户+内核 CPU,SIGPROF);每类每进程一个。

      • Alarm 简易 APIalarm(seconds) 单次实时 timer;返回旧 timer 剩余秒数;alarm(0) 取消;与 setitimer 共享 ITIMER_REAL timer。

      • 阻塞系统调用的超时:标准技巧——alarm() + 不带 SA_RESTART 的 SIGALRM handler + read/write;handler 触发后阻塞调用返回 EINTR。

      • POSIX Clocksclock_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 Timerstimer_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

      Whatsleep(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);

      nanosleepsleep 的关系:

      • 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

      Whatclock_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_RAWCLOCK_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

      Whattimer_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_valuesigev_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 对照
      Timer 类型 计数维度 到期信号 用途

      ITIMER_REAL

      wall-clock 时间

      SIGALRM

      通用定时;超时强杀

      ITIMER_VIRTUAL

      进程用户态 CPU

      SIGVTALRM

      测用户 CPU

      ITIMER_PROF

      进程用户+内核 CPU

      SIGPROF

      profiling(GPROF)

      POSIX Clock 选择
      Clock 是否可跳变 用途

      CLOCK_REALTIME

      可(settimeofday/NTP step)

      日志时间戳;墙钟显示

      CLOCK_MONOTONIC

      否(但 NTP slew 会调整)

      测量时间间隔

      CLOCK_MONOTONIC_RAW

      否(不受 slew)

      高精度测量

      CLOCK_BOOTTIME

      否(含休眠)

      跨休眠的总时间

      CLOCK_PROCESS_CPUTIME_ID

      -

      进程 CPU profiling

      CLOCK_THREAD_CPUTIME_ID

      -

      线程 CPU profiling

      四、思维导图

      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 检测

      五、重点与易错点

      1. 三类 Interval Timer——ITIMER_REAL(wall-clock/SIGALRM)、ITIMER_VIRTUAL(用户 CPU/SIGVTALRM)、ITIMER_PROF(用户+内核 CPU/SIGPROF);每类每进程一个。

      2. alarm 与 setitimer(ITIMER_REAL) 共享 timer——互相覆盖;SUSv3 留作未指定;跨平台代码只用其中一个。

      3. timers 跨 exec 保留、不跨 fork 继承——子进程不会继承父进程的 timer。

      4. 阻塞调用超时模式:alarm + 不带 SA_RESTART 的 handler → 阻塞系统调用 → 检查 EINTR;Linux 2.6.25+ 更优用 SO_RCVTIMEO/socket 或 timerfd。

      5. sleep 与 alarm 交互未标准化——不要混用;sleep 可能被信号打断后写 EINTR 或留下剩余时间(nanosleep)。

      6. CLOCK_REALTIME 可被 settimeofday/NTP step 跳变——测量时间间隔用 CLOCK_MONOTONIC;高精度用 CLOCK_MONOTONIC_RAW。

      7. CLOCK_MONOTONIC 受 NTP slew(频率调整)影响——需要完全不受影响用 CLOCK_MONOTONIC_RAW。

      8. POSIX timers 可同时存在多个(区别于 setitimer);可投递到线程(SIGEV_THREAD / SIGEV_THREAD_ID);通过 sigval 携带 timer ID。

      9. timerfd 与 epoll 整合——单线程事件循环统一处理 I/O + 定时器;read 返回 uint64_t 表示到期次数;取消设 it_value/it_interval = 0

      10. 高分辨率 timer 自 2.6.21——CONFIG_HIGH_RES_TIMERS=y;clock_getres 检测;微秒精度可期。

      11. SIGEV_THREAD 启动新线程——回调在 glibc 管理的线程中;注意线程创建开销;不适用高频 timer。

      12. POSIX timer 投递默认 SIGRTMIN+1——可改用 sigev_signo 指定其他信号;与实时信号配合 SA_SIGINFO handler。

      13. itimerspec 含纳秒——比 itimerval 的微秒精度更高;适合配合高分辨率 timer。

      14. 阻塞 I/O 超时的现代方案SO_RCVTIMEO/SO_SNDTIMEO(socket)、O_NONBLOCK + poll/epoll、timerfd + epoll;尽量避免 alarm + SIGALRM 传统模式。

      15. 跨章衔接:第 22 章 SIGALRM 是 setitimer 的投递机制;第 14 章 /proc/sys 与高精度 timer;第 63 章 epoll 与 timerfd 整合;第 49 章 mmap 与 CLOCK_MONOTONIC 时间戳;第 53 章 POSIX 信号量与超时。