土法炼钢兴趣小组的算法知识备份

【操作系统百科】seqlock 与 seqcount

文章导航

分类入口
os
标签入口
#seqlock#seqcount#retry-read#timekeeper#lockless

目录

xtime / ktime_get() 返回当前时间——这是内核最高频的读路径之一。不能用锁,RCU 也不合适(数据在原地更新)。seqlock 是答案。

一、先看图

sequenceDiagram
    participant W as Writer
    participant R as Reader

    W->>W: write_seqcount_begin<br/>seq++(偶→奇)
    W->>W: 更新数据
    W->>W: write_seqcount_end<br/>seq++(奇→偶)

    R->>R: seq1 = read_seqcount_begin
    R->>R: 读数据(可能不一致)
    R->>R: read_seqcount_retry(seq1)?
    alt seq 变了
        R->>R: 重试
    else seq 没变
        R->>R: 数据有效
    end

二、核心思想

三、seqcount vs seqlock

3.1 seqcount_t

只有序列号,不保护写者之间的互斥:

seqcount_t seq;

// 写者(外部需自己互斥)
write_seqcount_begin(&seq);
data = new_value;
write_seqcount_end(&seq);

// 读者
unsigned s;
do {
    s = read_seqcount_begin(&seq);
    val = data;
} while (read_seqcount_retry(&seq, s));

3.2 seqlock_t

seqcount + spinlock(写者互斥):

seqlock_t lock;

// 写者
write_seqlock(&lock);
data = new_value;
write_sequnlock(&lock);

// 读者
unsigned s;
do {
    s = read_seqbegin(&lock);
    val = data;
} while (read_seqretry(&lock, s));

四、Timekeeper 用法

// kernel/time/timekeeping.c
struct timekeeper {
    seqcount_raw_spinlock_t seq;
    // 时间数据
};

ktime_get() 路径:

do {
    s = read_seqcount_begin(&tk->seq);
    // 读 timekeeper 数据
} while (read_seqcount_retry(&tk->seq, s));

时间更新(tick 中断)只有一个写者 → seqcount 足够。

五、与 RCU 对比

特性 seqlock RCU
数据更新 原地修改 创建新版本
读者失败 重试 无失败
适用场景 小数据、写少读多 指针/链表
内存开销 无额外 旧版本延迟释放

seqlock 适合:小的、可以原地更新的数据(如时间戳、统计计数器)。

六、load tearing 风险

读者可能读到写者写了一半的数据:

// 64 位数据在 32 位架构上
// 读者可能看到 high 32 bit = new, low 32 bit = old

解决:用 READ_ONCE 读每个字段,或保证数据对齐 + 自然宽度。

七、seqcount latch

struct latch_tree {
    seqcount_latch_t seq;
    struct rb_root tree[2];  // 两棵树交替
};

写者更新一棵树 → 切换 seq → 更新另一棵树。读者根据 seq 选择哪棵。

用途:频繁更新的查找结构(如模块地址查找)。

八、typed seqcount(5.10+)

seqcount_spinlock_t     // 关联 spinlock
seqcount_raw_spinlock_t // 关联 raw_spinlock
seqcount_rwlock_t       // 关联 rwlock
seqcount_mutex_t        // 关联 mutex

lockdep 可以跟踪 seqcount 与其保护锁的关系。

九、观察

# seqlock 竞争
bpftrace -e 'kprobe:read_seqcount_begin { @[kstack(3)] = count(); }'
# timekeeper 读重试率
perf stat -e 'sched:sched_stat_runtime' -a sleep 1

十、小结


参考文献

工具


上一篇RCU 下一篇percpu_refcount

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-06-29 · linux / os

EEVDF 调度器:Linux 6.6 为什么换掉了 CFS

Linux 6.6 用 EEVDF 取代了 CFS 的 SCHED_NORMAL 选取逻辑。从 1995 年原始论文的 lag、eligibility、virtual deadline,到 commit 147f3ef 只重写 placement/pick/preempt,再到本机内核 6.6 上读 sched/debug 把每个任务的 vruntime、eligible 标志、deadline 一一对上 vd=ve+r/w,外加 nice 带宽与 base_slice 抢占两组实测,讲清换的是哪一块、延迟敏感任务凭什么先跑。

2026-07-05 · os

【操作系统百科】实时 OS 巡礼

硬实时与软实时的可调度性边界、VxWorks/QNX/Zephyr 机制对照、Linux PREEMPT_RT 与主线合入、DO-178C/ISO 26262 认证语境,以及选型决策树。

2026-07-06 · os

【操作系统百科】Unikernel

Unikernel 在云上为什么没成主流?MirageOS、IncludeOS、Unikraft、OSv 的设计取舍——库操作系统、启动时间、工具链、调试困难、POSIX 兼容。


By .