Linux Kernel/Timekeeping / real-time clock

读取系统时间时避免拼接不同版本的字段

Linux 读取系统实时时钟时用序列计数校验多个字段,若期间发生写入就重读,避免把旧的秒值与新的时钟换算状态拼在一起。

v6.12并发与一致性核验 2026-10-09
相关概念

工程问题#

一次取时间需要读取秒基准和时钟源的换算状态;写者可能正调整系统时间或更新计时状态。逐字段读取即使没有单次撕裂,也不自动组成一致快照。

实际实现#

ktime_get_real_ts64 先读取 tk_core.seq,再读取 xtime_sec 和由 tkr_mono 计算的纳秒增量;若末尾序列校验失败,则重新读取。写侧以 timekeeper_lock 串行化,用 write_seqcount_begin/end 标记更新区间。这里使用与 raw spinlock 关联的 seqcount,不是让读者获取该自旋锁。

处理流程#

  1. 取得一个允许开始读取的偶数序列值
  2. 读取秒值及时间换算所需数据
  3. 校验序列未变化;与更新重叠时重试
  4. 通过校验后规范化秒和纳秒,返回时间值

源码片段#

固定版本 · 原文摘录
kernel/time/timekeeping.cv6.12 · L817–L835
查看原文件
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}
ktime_get_real_ts64

读循环把秒基准和纳秒增量置于同一次序列校验中;检测到并发更新后重读,而不是给每个字段单独加锁。

kernel/time/timekeeping.cv6.12 · L1451–L1471
查看原文件
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);
do_settimeofday64

实际写侧先获取 timekeeper_lock 并关闭本地中断,再用序列计数包围设置时间及相关状态更新;结束计数写区后才释放锁。

读写两端必须遵守同一序列协议。这里展示正常取时与显式设置时间,其他快时钟、latch seqcount 和 NMI 读取路径需单独分析。

收益与取舍#

工程分析

带来的收益

  • 读者无需争用 timekeeper_lock 来取得这组时间字段
  • 通过重试获得参与该更新协议的字段的一致读取

付出的代价

  • 写入频繁或写区过长时读者可能反复重试,不保证读者等待时间上界
  • 写者仍需串行化并满足不可被读侧抢占等约束;序列计数本身不保护指针所指对象的生命周期

可比较的选择

读写都获取同一锁读取过程更容易串行推理,但高频取时读者需要参与锁竞争。

原子发布不可变快照可缩小读者重试需求,但需管理快照发布、分配和旧对象回收。

版本与适用范围#

对应 Linux v6.12 的 ktime_get_real_ts64 读取与 do_settimeofday64 写入示例,具体类型是 seqcount_raw_spinlock_t。实时时钟可被调整,这不是单调时钟保证;seqcount 也不能独立保障时钟源指针的生命周期。

参考来源#

源码、官方文档与原始论文
01
Linux · ktime_get_real_ts64 / do_settimeofday64

kernel/time/timekeeping.c · ktime_get_real_ts64 / do_settimeofday64

源码v6.12
02
Linux · Sequence counters and sequential locks

Documentation/locking/seqlock.rst · seqcount_t / seqcount_LOCKNAME_t

官方文档v6.12
6 个匹配案例