读取系统时间时避免拼接不同版本的字段
Linux 读取系统实时时钟时用序列计数校验多个字段,若期间发生写入就重读,避免把旧的秒值与新的时钟换算状态拼在一起。
工程问题#
一次取时间需要读取秒基准和时钟源的换算状态;写者可能正调整系统时间或更新计时状态。逐字段读取即使没有单次撕裂,也不自动组成一致快照。
实际实现#
ktime_get_real_ts64 先读取 tk_core.seq,再读取 xtime_sec 和由 tkr_mono 计算的纳秒增量;若末尾序列校验失败,则重新读取。写侧以 timekeeper_lock 串行化,用 write_seqcount_begin/end 标记更新区间。这里使用与 raw spinlock 关联的 seqcount,不是让读者获取该自旋锁。
处理流程#
- 取得一个允许开始读取的偶数序列值
- 读取秒值及时间换算所需数据
- 校验序列未变化;与更新重叠时重试
- 通过校验后规范化秒和纳秒,返回时间值
源码片段#
固定版本 · 原文摘录817void ktime_get_real_ts64(struct timespec64 *ts)818{819 struct timekeeper *tk = &tk_core.timekeeper;820 unsigned int seq;821 u64 nsecs;822823 WARN_ON(timekeeping_suspended);824825 do {826 seq = read_seqcount_begin(&tk_core.seq);827828 ts->tv_sec = tk->xtime_sec;829 nsecs = timekeeping_get_ns(&tk->tkr_mono);830831 } while (read_seqcount_retry(&tk_core.seq, seq));832833 ts->tv_nsec = 0;834 timespec64_add_ns(ts, nsecs);835}读循环把秒基准和纳秒增量置于同一次序列校验中;检测到并发更新后重读,而不是给每个字段单独加锁。
1451 raw_spin_lock_irqsave(&timekeeper_lock, flags);1452 write_seqcount_begin(&tk_core.seq);14531454 timekeeping_forward_now(tk);14551456 xt = tk_xtime(tk);1457 ts_delta = timespec64_sub(*ts, xt);14581459 if (timespec64_compare(&tk->wall_to_monotonic, &ts_delta) > 0) {1460 ret = -EINVAL;1461 goto out;1462 }14631464 tk_set_wall_to_mono(tk, timespec64_sub(tk->wall_to_monotonic, ts_delta));14651466 tk_set_xtime(tk, ts);1467out:1468 timekeeping_update(tk, TK_CLEAR_NTP | TK_MIRROR | TK_CLOCK_WAS_SET);14691470 write_seqcount_end(&tk_core.seq);1471 raw_spin_unlock_irqrestore(&timekeeper_lock, flags);实际写侧先获取 timekeeper_lock 并关闭本地中断,再用序列计数包围设置时间及相关状态更新;结束计数写区后才释放锁。
读写两端必须遵守同一序列协议。这里展示正常取时与显式设置时间,其他快时钟、latch seqcount 和 NMI 读取路径需单独分析。
收益与取舍#
工程分析带来的收益
- 读者无需争用 timekeeper_lock 来取得这组时间字段
- 通过重试获得参与该更新协议的字段的一致读取
付出的代价
- 写入频繁或写区过长时读者可能反复重试,不保证读者等待时间上界
- 写者仍需串行化并满足不可被读侧抢占等约束;序列计数本身不保护指针所指对象的生命周期
可比较的选择
读写都获取同一锁读取过程更容易串行推理,但高频取时读者需要参与锁竞争。
原子发布不可变快照可缩小读者重试需求,但需管理快照发布、分配和旧对象回收。
版本与适用范围#
对应 Linux v6.12 的 ktime_get_real_ts64 读取与 do_settimeofday64 写入示例,具体类型是 seqcount_raw_spinlock_t。实时时钟可被调整,这不是单调时钟保证;seqcount 也不能独立保障时钟源指针的生命周期。
参考来源#
源码、官方文档与原始论文kernel/time/timekeeping.c · ktime_get_real_ts64 / do_settimeofday64
Documentation/locking/seqlock.rst · seqcount_t / seqcount_LOCKNAME_t