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

【操作系统百科】per-CPU 变量

文章导航

分类入口
os
标签入口
#percpu#this-cpu#percpu-counter#preempt

目录

多核上最大的扩展瓶颈是共享数据的锁争抢。per-CPU 变量让每个 CPU 有一份独立副本——读写不需要锁、不需要原子操作、不需要 cache 行 bouncing。

一、先看图

flowchart LR
    CPU0[CPU 0] --> V0[per-CPU 副本 0<br/>count=42]
    CPU1[CPU 1] --> V1[per-CPU 副本 1<br/>count=37]
    CPU2[CPU 2] --> V2[per-CPU 副本 2<br/>count=51]
    CPU3[CPU 3] --> V3[per-CPU 副本 3<br/>count=28]
    SUM[需要全局值时<br/>sum_over_cpus] --> CPU0
    SUM --> CPU1
    SUM --> CPU2
    SUM --> CPU3
    classDef cpu fill:#388bfd22,stroke:#388bfd,color:#adbac7;
    class CPU0,CPU1,CPU2,CPU3,V0,V1,V2,V3 cpu

二、静态 per-CPU

编译时声明:

DEFINE_PER_CPU(int, my_counter);

// 使用
this_cpu_inc(my_counter);          // 当前 CPU 的副本 +1
int val = this_cpu_read(my_counter);
this_cpu_write(my_counter, 0);

// 读其他 CPU
int remote = per_cpu(my_counter, cpu_id);

// 全局汇总
int total = 0;
for_each_possible_cpu(cpu)
    total += per_cpu(my_counter, cpu);

布局

链接器把所有 per-CPU 变量放到 .data..percpu section。启动时为每个 CPU 复制一份。

三、动态 per-CPU

运行时分配:

int __percpu *p = alloc_percpu(int);
this_cpu_inc(*p);
free_percpu(p);

底层用 pcpu_alloc:从 vmalloc 区域的 per-CPU chunk 分配。chunk 按 CPU 偏移排布。

四、this_cpu_* 操作

这组宏利用 x86 %gs 段前缀(arm64 用 TPIDR_EL1)直接偏移到当前 CPU 区域:

this_cpu_read(var)
this_cpu_write(var, val)
this_cpu_add(var, delta)
this_cpu_inc(var)
this_cpu_dec(var)
this_cpu_cmpxchg(var, old, new)
this_cpu_xchg(var, new)

单条指令完成(如 addl %gs:offset, $1)——比 get_cpu() + per_cpu() + put_cpu() 快且不禁抢占。

raw_cpu_* vs this_cpu_*

五、preempt 与 per-CPU

关键问题:如果你在读 per-CPU 变量的途中被抢占到另一个 CPU 怎么办?

preempt_disable();
val = __this_cpu_read(counter);
val += delta;
__this_cpu_write(counter, val);
preempt_enable();

或直接用 this_cpu_add 一步到位。

六、percpu_counter

通用的”近似全局计数器”——热路径 per-CPU 累积,冷路径汇总:

struct percpu_counter fbc;
percpu_counter_init(&fbc, 0, GFP_KERNEL);

percpu_counter_add(&fbc, 1);          // 热路径
s64 val = percpu_counter_sum(&fbc);   // 冷路径(精确)
s64 approx = percpu_counter_read(&fbc); // 不精确但快

阈值机制:本地累积超过 batch(默认 32)时同步到全局 count。

用途:文件系统空闲块计数、nr_dentry、nr_unused。

七、per-CPU 的变种

7.1 per-CPU refcount

percpu_ref:热路径 per-CPU 计数 → 关闭时切换到 atomic_t 集中计数。

percpu_ref_init(&ref, release_fn, 0, GFP_KERNEL);
percpu_ref_get(&ref);       // per-CPU 模式
percpu_ref_put(&ref);
percpu_ref_kill(&ref);      // 切 atomic 模式

block I/O、cgroup 控制器大量使用。

7.2 local_t

per-CPU 整数但允许在中断/NMI 中原子更新:

DEFINE_PER_CPU(local_t, events);
local_inc(&this_cpu(events));

用于 perf events 计数。

八、per-CPU 陷阱

A:读其他 CPU 的 per-CPU 变量 per_cpu(var, remote_cpu) 可以读但不要写(除非有其他同步)。per-CPU 的语义是”只有 owner CPU 写”。

B:per-CPU 变量 + preempt_enable 之间写 多步操作不用 this_cpu_* 且不禁抢占 → 可能看到半更新。

C:per-CPU 加 memcg 不感知 per-CPU 内存可能在某些 cgroup 统计之外。SLAB_ACCOUNT 只计 slab 对象。

九、观察

# 全系统 per-CPU 内存
cat /proc/meminfo | grep Percpu
# Percpu:  12345 kB

# per-CPU chunk 详情
cat /proc/percpu_alloc

# 看 percpu_counter
cat /proc/sys/fs/dentry-state   # nr_dentry 用 percpu_counter

十、小结


参考文献

工具


上一篇vmalloc/kmap/ioremap 下一篇内核内存调试

同主题继续阅读

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

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 .