Linux与定时器
# 10.Linux与定时器
时间是一切实时系统的基础——嵌入式应用中的动画帧率、传感器采集周期、看门狗喂狗、超时重传全部依赖时钟。本节覆盖从传统 alarm 到现代 timerfd 的全套定时器方案。
# 目录介绍
- 10.1 案例引入
- 10.2 Linux 时间体系
- 10.3 NTP 时间跳变与防护
- 10.4 传统定时器
- 10.5 timerfd:把定时器变成文件描述符
- 10.6 高精度定时器
- 10.7 嵌入式定时器最佳实践
- 10.8 关键结论与速查表
# 10.1 案例引入
# 10.1.1 用户调时间后仪表盘动画卡死
某车载仪表盘的转速表动画使用 QElapsedTimer(基于 CLOCK_MONOTONIC)实现平滑旋转。一切正常,直到某天用户把系统时间从 2025 年调成 2038 年——仪表盘卡死 5 秒不动。
排查发现 QML 中另一个计时器组件使用了 new Date().getTime()(基于 CLOCK_REALTIME)。时间跳变引发了绑定链条的级联失效——NTP 的 stepping 模式直接让壁钟倒退 13 年(从 2038 回到 NTP 服务器纠正的 2025),所有依赖壁钟的状态机集体崩溃。
正确做法:UI 动画一律用 CLOCK_MONOTONIC,只有需要显示给用户看的"日历时间"才用 CLOCK_REALTIME。
两种时钟在时间跳变时的表现对比:
CLOCK_REALTIME:
用户调时: 2025 → 2038 ──→ 值跳变 +13 年
NTP 纠正: 2038 → 2025 ──→ 值倒退 -13 年 ← 所有定时器/动画崩溃
CLOCK_MONOTONIC:
用户调时: 不影响 ──→ 继续单调递增
NTP 纠正: 不影响 ──→ 继续单调递增
只有系统重启时才从 0 开始
# 10.1.2 核心问题
- 五种时钟类型
REALTIME / MONOTONIC / MONOTONIC_RAW / BOOTTIME / PROCESS_CPUTIME应该怎么选? - timerfd 为什么是 epoll 事件循环的最佳拍档?相比
setitimer有什么优势? - 嵌入式设备首次上电时间是 1970 年——如何优雅处理?
- NTP 的 slewing 和 stepping 有什么区别?如何防止时间倒退引发的 bug?
# 10.2 Linux 时间体系
# 10.2.1 CLOCK_REALTIME 壁钟时间
CLOCK_REALTIME 是"人类世界的时间"——1970-01-01 至今的秒数。它的值可以被 settimeofday()、NTP、date 命令修改:
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
printf("Unix timestamp: %ld.%09ld\n", ts.tv_sec, ts.tv_nsec);
CLOCK_REALTIME 的行为特征:
| 操作 | 对 CLOCK_REALTIME 的影响 |
|---|---|
date -s 手动改时间 | 瞬间跳变 (stepping) |
NTP slewing(慢调) | 每秒加速/减速最多 500 ppm (~43ms/天) |
NTP stepping(快调) | 瞬间跳到正确值 |
| 闰秒 | 加/减 1 秒 |
| 系统挂起/休眠 | 恢复后跳到当前时间 |
适用场景:日志时间戳、文件 mtime、用户界面显示的时间、证书过期检查。
# 10.2.2 CLOCK_MONOTONIC 单调时间
CLOCK_MONOTONIC 保证"只往前走,绝不后退"——从系统启动到现在的单调秒数:
struct timespec t1, t2;
clock_gettime(CLOCK_MONOTONIC, &t1);
do_work();
clock_gettime(CLOCK_MONOTONIC, &t2);
uint64_t elapsed_ns = (t2.tv_sec - t1.tv_sec) * 1000000000ULL
+ (t2.tv_nsec - t1.tv_nsec);
printf("elapsed: %lu ns\n", elapsed_ns);
CLOCK_MONOTONIC 受 NTP 的 slewing 影响——NTP 慢调时会微调频率,但从不往回跳。如果你需要完全不受 NTP 影响的单调时钟,用 CLOCK_MONOTONIC_RAW。
适用场景:动画计时、性能测量、超时检测、定时器、游戏/动画引擎。
# 10.2.3 CLOCK_MONOTONIC_RAW 无 NTP 校正
CLOCK_MONOTONIC_RAW 是纯粹的硬件时钟计数——不受 NTP 的 adjtime() 影响,完全以硬件频率递增:
// 需要不受 NTP 干扰的精确测量时用 RAW
clock_gettime(CLOCK_MONOTONIC_RAW, &t1);
usleep(1000);
clock_gettime(CLOCK_MONOTONIC_RAW, &t2);
MONOTONIC vs MONOTONIC_RAW 的差异:
| 特性 | MONOTONIC | MONOTONIC_RAW |
|---|---|---|
| 单调递增 | ✅ | ✅ |
| 受 NTP slewing 影响 | ✅(频率微调) | ❌ |
| 跨系统同步 | 接近(经 NTP 校准) | 独立(可能漂移) |
| 适用场景 | 超时、动画、定时器 | 精确性能测量 |
# 10.2.4 CLOCK_BOOTTIME 休眠统计
CLOCK_BOOTTIME 与 MONOTONIC 相同,但包含系统休眠期间的时长:
// 想知道设备实际运行了多少秒(含休眠)
clock_gettime(CLOCK_BOOTTIME, &ts);
适用场景:判断设备总运行时长(含待机)、电池寿命统计、定期"自设备启动以来"的维护任务。
# 10.2.5 gettimeofday 的废弃与 clock_gettime
gettimeofday 已过时——三个致命缺陷:
| 缺陷 | 说明 |
|---|---|
| 精度低 | 毫秒级(struct timeval),无纳秒支持 |
| 不是单调 | 返回壁钟时间,会跳变 |
| Y2038 问题 | 32 位 time_t 在 2038-01-19 溢出(32 位系统) |
始终用 clock_gettime 替代:
// ❌ 旧式
struct timeval tv;
gettimeofday(&tv, NULL);
// ✅ 现代
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
时钟选择的黄金法则:
| 用途 | 时钟 |
|---|---|
| 显示给用户的时间 | CLOCK_REALTIME |
| 动画/定时器/超时 | CLOCK_MONOTONIC |
| 精确性能测量 | CLOCK_MONOTONIC_RAW |
| 总运行时长(含休眠) | CLOCK_BOOTTIME |
| 进程 CPU 使用时间 | CLOCK_PROCESS_CPUTIME_ID |
# 10.3 NTP 时间跳变与防护
# 10.3.1 NTP slewing vs stepping
NTP 有两种纠正本地时钟的方式:
| 方式 | 行为 | 持续时间 | 对 timer/动画的影响 |
|---|---|---|---|
| slewing | 每秒加速/减速 ≤ 500 ppm (~43ms/天) | 数小时(平滑) | 无感知 |
| stepping | 瞬间跳到正确值 | 瞬间 | ❌ 所有依赖壁钟的组件崩溃 |
ntpd 的默认策略:偏差 < 128ms → slewing;偏差 ≥ 128ms → stepping。
嵌入式设备禁用 stepping:
# chrony 配置
echo "maxslewrate 1000" >> /etc/chrony.conf # 允许更快的 slewing
echo "makestep 1 -1" >> /etc/chrony.conf # 只在启动时 stepping
# ntpd 配置
echo "tinker panic 0" >> /etc/ntp.conf # 任何偏差都不 stepping
# 10.3.2 时间倒退的防御策略
防御规则——只有 CLOCK_REALTIME 会倒退:
// 1. 测量时间差:永远用 MONOTONIC
uint64_t start = get_monotonic_us();
do_work();
uint64_t elapsed = get_monotonic_us() - start; // 绝不为负
// 2. 需要日历时间的场合:检查倒退
time_t last = time(NULL);
while (1) {
time_t now = time(NULL);
if (now < last) {
fprintf(stderr, "WARN: time went backwards! %ld → %ld\n", last, now);
// 恢复正常操作——不依赖 now
}
last = now;
}
// 3. 定时任务:用 MONOTONIC 驱动,REALTIME 只做显示
uint64_t next_fire = get_monotonic_ms() + 1000; // 1 秒后触发
// 即使 REALTIME 跳变,next_fire 依然准确
# 10.3.3 嵌入式设备首次上电 1970 年问题
嵌入式设备无 RTC 电池——首次上电时间是 1970-01-01:
time_t now = time(NULL);
if (now < 946684800) { // 2000-01-01 之前 = 显然不对
printf("RTC not set, using fallback time\n");
// 策略 A:使用文件系统最后一次修改时间
struct stat st;
if (stat("/etc/config", &st) == 0) {
now = st.st_mtime; // 至少不会比上次配置更早
}
// 策略 B:使用编译时间(__DATE__ __TIME__)
// 见下面的例子
}
编译时间回退——嵌入式"至少不倒退"的技巧:
// 链接时嵌入编译时间戳
const char build_time[] = __DATE__ " " __TIME__;
time_t get_min_valid_time() {
struct tm tm = {0};
strptime(build_time, "%b %d %Y %H:%M:%S", &tm);
time_t build_ts = mktime(&tm); // 编译时的 Unix 时间戳
time_t now = time(NULL);
return (now < build_ts) ? build_ts : now;
// 保证至少不早于编译日期
}
# 10.4 传统定时器
# 10.4.1 alarm 与 SIGALRM 的局限性
alarm 是最古老的定时器——秒级精度,只能设一个:
void handle_timeout(int sig) {
printf("timeout\n");
}
signal(SIGALRM, handle_timeout);
alarm(5); // 5 秒后触发 SIGALRM
// 只有 1 个定时器:再次调 alarm() 会取消前一个
alarm 的四大局限:
| 局限 | 说明 |
|---|---|
| 秒级精度 | 不能设毫秒/纳秒 |
| 只有一个 | 不能同时设多个定时器 |
| 信号上下文 | 只能在信号处理器中处理(async-signal-safe 受限) |
| 与 sleep 冲突 | sleep() 内部使用 SIGALRM,混用导致未定义行为 |
# 10.4.2 setitimer 三种定时器
setitimer 是 alarm 的升级版——微秒精度,支持三种类型:
struct itimerval timer = {
.it_value = {.tv_sec = 0, .tv_usec = 500000}, // 首次 500ms
.it_interval = {.tv_sec = 0, .tv_usec = 100000} // 之后每 100ms
};
setitimer(ITIMER_REAL, &timer, NULL); // 真实时间定时器
三种定时器类型:
| 类型 | 时钟基准 | 信号 | 适用 |
|---|---|---|---|
ITIMER_REAL | 真实时间(壁钟) | SIGALRM | 通用定时 |
ITIMER_VIRTUAL | 进程用户态 CPU 时间 | SIGVTALRM | CPU 性能分析 |
ITIMER_PROF | 进程总 CPU 时间(用户+内核) | SIGPROF | 代码性能 profiling |
setitimer 仍有限制:每个进程每种类型只有一个定时器;仍依赖信号机制;微秒精度而非纳秒。
# 10.4.3 usleep/nanosleep/clock_nanosleep 精度对比
三种精确睡眠——timerfd 尚未出现时的选择:
// usleep —— 已废弃,微秒精度
usleep(100000); // 100 ms
// nanosleep —— POSIX,纳秒精度,但只能相对时间
struct timespec ts = {.tv_sec = 0, .tv_nsec = 100000000};
nanosleep(&ts, NULL); // 100 ms
// clock_nanosleep —— POSIX,纳秒精度,支持绝对时间
struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline);
deadline.tv_nsec += 100000000; // +100 ms
clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL);
三者的精度对比(ARM Cortex-A7, Linux 5.10):
| 函数 | 名义精度 | 实际抖动量 | Y2038 安全 |
|---|---|---|---|
usleep | 1 μs | ±50-200 μs | ❌(32位) |
nanosleep | 1 ns | ±30-100 μs | ✅(64位 timespec) |
clock_nanosleep(ABSTIME) | 1 ns | ±20-80 μs | ✅ |
clock_nanosleep(TIMER_ABSTIME) 的优势——不受被信号中断唤醒的时间累积影响。如果你用相对睡眠被信号打断 3 次,每次重试都有累计误差;绝对时间的 clock_nanosleep 说"在 wall-clock ts X 唤醒我"——无论被提前唤醒多少次。
# 10.5 timerfd:把定时器变成文件描述符
# 10.5.1 timerfd_create/settime/gettime
timerfd 是 Linux 特有的定时器接口——定时器是一个文件描述符:
#include <sys/timerfd.h>
// 创建基于 MONOTONIC 的 timerfd
int tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
// 设置:1 秒后首次触发,之后每 500ms 触发一次
struct itimerspec its = {
.it_value = {.tv_sec = 1, .tv_nsec = 0}, // 首次:1s
.it_interval = {.tv_sec = 0, .tv_nsec = 500000000} // 周期:500ms
};
timerfd_settime(tfd, 0, &its, NULL);
// 消费定时器事件(读 tfd)
uint64_t expirations;
ssize_t n = read(tfd, &expirations, sizeof(expirations));
// expirations = 触发次数(如果读取不及时,可能 > 1)
timerfd_gettime 查询剩余时间:
struct itimerspec remaining;
timerfd_gettime(tfd, &remaining);
printf("next fire in: %ld.%09ld s\n",
remaining.it_value.tv_sec, remaining.it_value.tv_nsec);
# 10.5.2 timerfd + epoll 事件驱动
timerfd 与 epoll 天然融合——定时器和 I/O 统一处理:
int epfd = epoll_create1(0);
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_EVENTS, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == tfd) {
uint64_t exp;
read(tfd, &exp, sizeof(exp)); // 消费事件
periodic_task();
} else {
handle_io(events[i].data.fd);
}
}
}
timerfd 相比传统定时器的四大优势:
| 优势 | 说明 |
|---|---|
| 事件驱动 | 与 epoll/select 统一,不依赖信号 |
| 无信号上下文限制 | 处理器可调任意函数(printf/malloc 都行) |
| 多实例 | 每个 timerfd_create() 创建一个独立定时器 |
| 可读可查 | read 消费、timerfd_gettime 查询剩余时间 |
| 支持 fork/exec 继承 | 可传给子进程 |
# 10.5.3 单次 vs 周期性定时器
单次(one-shot)——it_interval = {0, 0}:
struct itimerspec once = {
.it_value = {.tv_sec = 5, .tv_nsec = 0},
.it_interval = {.tv_sec = 0, .tv_nsec = 0} // ← 不重复
};
timerfd_settime(tfd, 0, &once, NULL);
// 5 秒后触发一次,之后 tfd 不再可读
周期性——it_interval != {0, 0}:
struct itimerspec periodic = {
.it_value = {.tv_sec = 0, .tv_nsec = 100000000}, // 首次 100ms
.it_interval = {.tv_sec = 0, .tv_nsec = 100000000} // 每 100ms
};
timerfd_settime(tfd, 0, &periodic, NULL);
单次定时器的典型场景——超时管理:
// 每个网络请求配一个单次 timerfd
int timer_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
struct itimerspec timeout = {
.it_value = {.tv_sec = 5, .tv_nsec = 0}, // 5 秒超时
.it_interval = {0, 0} // 不重复
};
timerfd_settime(timer_fd, 0, &timeout, NULL);
// epoll 中同时监听 socket fd 和 timer fd
// 哪个先就绪就处理哪个——完美实现超时机制
# 10.5.4 timerfd 在 Qt 事件循环中的集成
Qt 5 起,QSocketNotifier 可以把任何 fd 集成到 Qt 事件循环:
#include <QSocketNotifier>
#include <sys/timerfd.h>
#include <unistd.h>
class TimerFdNotifier : public QObject {
Q_OBJECT
public:
TimerFdNotifier(int intervalMs, QObject *parent = nullptr)
: QObject(parent), m_tfd(timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK))
{
struct itimerspec its = {
.it_value = {.tv_sec = intervalMs / 1000,
.tv_nsec = (intervalMs % 1000) * 1000000},
.it_interval = {.tv_sec = intervalMs / 1000,
.tv_nsec = (intervalMs % 1000) * 1000000}
};
timerfd_settime(m_tfd, 0, &its, NULL);
m_notifier = new QSocketNotifier(m_tfd, QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated, this, [this]() {
uint64_t exp;
read(m_tfd, &exp, sizeof(exp));
emit timeout();
});
}
~TimerFdNotifier() { close(m_tfd); }
signals:
void timeout();
private:
int m_tfd;
QSocketNotifier *m_notifier;
};
// 用法:TimerFdNotifier *t = new TimerFdNotifier(100, this);
// connect(t, &TimerFdNotifier::timeout, this, &MyObject::onTick);
# 10.6 高精度定时器
# 10.6.1 hrtimer 内核支持
hrtimer(High Resolution Timer)是内核 2.6.16 引入的纳秒级定时器子系统。它是 timerfd、nanosleep、clock_nanosleep 的底层实现。
检查 hrtimer 是否激活:
cat /proc/timer_list | head -20
# 如果看到 .resolution: 1 nsecs → hrtimer 已激活
# 如果看到 .resolution: 10000000 nsecs → 只有 tick (10ms)
# 确认高精度模式
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
# tsc 或 arch_sys_counter (ARM)
hrtimer 在 ARM 上的特殊配置——必须启用内核 CONFIG_HIGH_RES_TIMERS=y。嵌入式内核默认通常已开启。
# 10.6.2 POSIX 定时器 timer_create
POSIX 定时器——比 timerfd 更灵活(跨平台),但接口更复杂:
#include <signal.h>
#include <time.h>
// 创建定时器
struct sigevent sev = {
.sigev_notify = SIGEV_SIGNAL,
.sigev_signo = SIGRTMIN, // 用实时信号通知
.sigev_value.sival_ptr = &timer_id
};
timer_t timerid;
timer_create(CLOCK_MONOTONIC, &sev, &timerid);
// 设置定时器
struct itimerspec its = {
.it_value = {.tv_sec = 0, .tv_nsec = 500000000},
.it_interval = {.tv_sec = 0, .tv_nsec = 500000000}
};
timer_settime(timerid, 0, &its, NULL);
// 删除
timer_delete(timerid);
timerfd vs POSIX timer 选择:
| 特性 | timerfd | POSIX timer (timer_create) |
|---|---|---|
| 可移植性 | Linux 独有 | POSIX(Linux/macOS/QNX) |
| 通知方式 | fd 可读(epoll 原生支持) | 信号 / 线程 / 无 |
| API 复杂度 | 极简 | 复杂 |
| 多实例 | 每个 timerfd_create 一个 | 每个 timer_create 一个 |
| 嵌入式推荐 | ✅ Linux 平台首选 | ⚠️ 跨平台才用 |
# 10.6.3 嵌入式实时性要求与 PREEMPT_RT
标准 Linux 内核的定时器精度受限于 tick:
- 默认
CONFIG_HZ=250(tick 间隔 4ms) - 即使
timerfd设了 100μs 的定时器,实际触发抖动 ±2ms
PREEMPT_RT 补丁——使 Linux 成为硬实时 OS:
# 检查是否打了 RT 补丁
uname -v | grep PREEMPT_RT
# 或
cat /sys/kernel/realtime
# RT 内核下:
# - 定时器抖动:±50 μs → ±5 μs
# - 中断线程化:所有中断处理变为内核线程(可抢占)
# - spinlock 变 rt_mutex
嵌入式 RT 选型:
| 场景 | 内核 | 典型抖动 |
|---|---|---|
| 仪表盘动画(60fps) | 标准 PREEMPT | ±2-4 ms |
| CAN 总线(1ms 周期) | PREEMPT_RT | ±50-100 μs |
| 电机控制(50μs 周期) | PREEMPT_RT + 独立核 | ±5-10 μs |
| 硬实时(< 10μs) | Xenomai / FreeRTOS | ±1-5 μs |
# 10.7 嵌入式定时器最佳实践
# 10.7.1 看门狗喂狗的定时器设计
看门狗需要一个独立于主事件循环的定时器——主事件循环卡住时依然能喂狗:
// 方案:独立线程 + timerfd + 喂狗
void *watchdog_thread(void *arg) {
int wdt_fd = open("/dev/watchdog", O_RDWR);
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
struct itimerspec its = {
.it_value = {.tv_sec = 5, .tv_nsec = 0}, // 5 秒喂一次
.it_interval = {.tv_sec = 5, .tv_nsec = 0}
};
timerfd_settime(tfd, 0, &its, NULL);
while (1) {
uint64_t exp;
read(tfd, &exp, sizeof(exp));
ioctl(wdt_fd, WDIOC_KEEPALIVE, 0); // 喂狗
}
}
// 主线程卡死 → 喂狗线程依然运行 → 5 秒后继续喂狗 → 不重启
// 真正全部卡死(调度器崩溃/中断关闭)→ 看门狗 30s 超时 → 硬件重启
# 10.7.2 动画帧调度与 vsync 对齐
QML 动画的理想定时器——DRM vblank 事件(vsync):
60 Hz 显示屏 → vsync 每 16.67ms 发生一次
QML 渲染循环 → 应在 vsync 后立刻开始渲染,在下一个 vsync 前完成
嵌入式优化:用 DRM/KMS 的 vblank fd + epoll
嵌入式 vsync 对齐的 timerfd 技巧:
// 用 timerfd 模拟 vsync 节拍(如果硬件不支持 DRM vblank fd)
int vsync_tfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
struct itimerspec vsync = {
.it_value = {.tv_sec = 0, .tv_nsec = 16666667}, // 60 Hz = 16.67ms
.it_interval = {.tv_sec = 0, .tv_nsec = 16666667}
};
timerfd_settime(vsync_tfd, 0, &vsync, NULL);
# 10.7.3 低功耗设备上的定时器唤醒策略
嵌入式低功耗设备的矛盾——需要休眠省电,又需要定时器唤醒采集数据。
timerfd + CLOCK_BOOTTIME_ALARM(Linux 3.11+)——允许定时器在系统休眠时唤醒设备:
// 使用支持唤醒的时钟
int tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK);
struct itimerspec its = {
.it_value = {.tv_sec = 60, .tv_nsec = 0} // 60 秒后唤醒
};
timerfd_settime(tfd, TFD_TIMER_ABSTIME, &its, NULL);
// 现在可以安全调 suspend:
// system("echo mem > /sys/power/state");
// 60 秒后 timerfd 会触发 CPU 中断 → 唤醒设备 → epoll_wait 返回
唤醒策略总结:
| 设备类型 | 定时器周期 | 方案 |
|---|---|---|
| 持续供电(车载) | 10-100ms | timerfd + epoll |
| 电池供电(传感器) | 1-60 分钟 | CLOCK_BOOTTIME_ALARM + suspend |
| 太阳能的功耗极度受限 | 10-60 分钟 | MCU 外部 RTC 芯片唤醒 Linux 主机 |
| 看门狗 | 1-10s | 独立线程 timerfd |
# 10.8 关键结论与速查表
核心结论五条:
- 动画/定时器/超时一律用
CLOCK_MONOTONIC——绝不倒退,不受用户调时和 NTP stepping 影响 - timerfd + epoll 是嵌入式定时器的最佳方案——事件驱动、无信号限制、与 I/O 统一处理
clock_nanosleep(TIMER_ABSTIME)比相对睡眠更准确——指定唤醒时刻而非睡眠时长,避免累积误差- 嵌入式 RTC 缺失 = 1970 年问题——用编译时间或文件系统时间设置"最低有效时间"兜底
- 看门狗喂狗用独立线程 timerfd——主事件循环卡死不影响硬件看门狗喂狗
五种时钟速查表:
| 时钟 | 会倒退 | 受NTP | 含休眠 | 适用 |
|---|---|---|---|---|
REALTIME | ✅ | ✅ | ❌ | 显示时间 |
MONOTONIC | ❌ | 仅 slewing | ❌ | 定时/动画 |
MONOTONIC_RAW | ❌ | ❌ | ❌ | 性能测量 |
BOOTTIME | ❌ | 仅 slewing | ✅ | 运行总时长 |
PROCESS_CPUTIME | ❌ | ❌ | ❌ | CPU 占用分析 |
定时器方案速查表:
| 方案 | 精度 | 多实例 | epoll 友好 | 嵌入式推荐 |
|---|---|---|---|---|
alarm | 秒 | ❌ | ❌ | ❌ |
setitimer | μs | ❌(每种1个) | ❌ | ❌ |
nanosleep | 纳秒(nom) | ✅(阻塞) | ❌ | ⚠️ 仅简单阻塞等待 |
clock_nanosleep | 纳秒(nom) | ✅(阻塞) | ❌ | ⚠️ 需绝对时间 |
| timerfd | 纳秒 | ✅ | ✅ | ✅ 最佳 |
POSIX timer_create | 纳秒 | ✅ | ❌(用信号) | ⚠️ 跨平台 |
时间 API 版本速查:
| 废弃 | 替代 | 原因 |
|---|---|---|
gettimeofday | clock_gettime(REALTIME) | Y2038 + 非单调 |
usleep | nanosleep / clock_nanosleep | 精度低 + 废弃 |
alarm | timerfd_create | 独一份秒级 |
setitimer | timerfd_create | 信号驱动 + 单实例 |
sleep | nanosleep / clock_nanosleep | 精度低 |
下一步:把你的 Qt 应用中的所有 QTimer 在 Linux 嵌入式目标上的底层实现(默认就是 timerfd + epoll)做一次 strace 验证——strace -e timerfd_create,timerfd_settime,epoll_wait ./myapp,亲眼看到每个 QTimer 对应一个 timerfd。
延伸阅读:
man 2 clock_gettime—— 时间获取man 2 timerfd_create—— timerfd 接口man 2 clock_nanosleep—— 高精度睡眠man 7 time—— Linux 时间系统概览man 2 timer_create—— POSIX 定时器