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

【操作系统百科】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-04-27 · os

【操作系统百科】内存回收

Linux 内存回收是 VM 最复杂的子系统之一。本文讲 active/inactive LRU、kswapd 与 direct reclaim、watermark 三线、swappiness 的真实含义、MGLRU 改造、memcg 回收与 PSI。

2026-04-28 · os

【操作系统百科】交换

swap 还值得开吗?本文讲 swap area 基础、swap cache、zram 压缩内存、zswap 前端压缩池、swappiness 的真实含义、容器里的 swap 策略,以及为什么现代 Android 全靠 zram 不靠磁盘。

2026-05-03 · os

【操作系统百科】Slab/SLUB 分配器

buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。

2026-05-07 · os

【操作系统百科】用户态分配器

glibc malloc、tcmalloc、jemalloc、mimalloc 各有哲学。本文讲 arena、thread cache、size class、madvise 返还策略、碎片与 RSS 膨胀、如何根据负载选分配器。


By .