__per_cpu_offset 数组是访问 per-CPU 变量的核心机制,它的作用相当于一张"地图",记录了每个 CPU 的 per-CPU 数据区域在整个内存中的位置偏移量。
核心概念:per-CPU 变量的存储模型
为了理解 __per_cpu_offset,首先要明白 per-CPU 变量在内存中是如何布局的。
在代码中,通过 DEFINE_PER_CPU(int, x) 这样的宏定义的变量,其实并不是一个真正的变量,而更像是一个"模板"或"蓝图"。内核在初始化时,会为系统中每一个可能的 CPU,都在内存中分配一块独立且大小相同的数据区域(称为 .data..percpu 段)。所有通过 DEFINE_PER_CPU 定义的变量,都会在这个区域内,为每个 CPU 各保留一份独立的副本。
__per_cpu_offset 的工作原理
__per_cpu_offset 数组就是用来定位这些副本的。
-
数组的定义与作用:__per_cpu_offset 是一个全局数组,定义在 include/asm-generic/percpu.h 中,其大小通常等于系统支持的最大 CPU 数量(NR_CPUS)。数组的每个元素 __per_cpu_offset[n] 存储的是第 n 号 CPU 的 per-CPU 数据区域相对于某个基准地址(通常是CPU0的数据区起始地址)的偏移量。
-
访问变量的核心公式:当内核代码(比如在 CPU 3 上运行)想要访问自己的 x 变量时,会使用 per_cpu_ptr(&x, cpu) 这样的宏。它的核心逻辑是:
变量在CPU 3上的实际地址 = &x (这个“模板”的地址) + __per_cpu_offset[3]。也就是说,通过将变量的"模板地址"加上当前 CPU 对应的偏移量,就得到了该变量在当前 CPU 上那份独立副本的真实内存地址。
底层优化:硬件级别的"加速通道"
__per_cpu_offset 数组提供了一种通用的、与架构无关的寻址方式。但在性能攸关的场景,尤其是访问当前 CPU 的 per-CPU 变量时,内核会利用硬件特性进行极致的优化,从而绕开这个数组。
-
x86 架构:使用 gs 段寄存器:在 x86 架构上,内核会将当前 CPU 的 per-CPU 数据区域的基地址直接加载到 gs 段寄存器中。这样一来,访问当前 CPU 的变量 x 就可以编译成一条简单的指令,例如 mov eax, gs:[x]。这里的 x 被视为一个相对于 gs 段基址的偏移量,无需再经过 __per_cpu_offset 数组的计算。这种方式不仅效率极高,还天然避免了进程在不同 CPU 间迁移导致的竞态问题。
-
ARM64 架构:使用 tpidr_el1 寄存器:类似地,在 ARM64 架构上,内核会使用 tpidr_el1 这个特殊的寄存器来保存当前 CPU 的 per-CPU 数据基址。访问时通过 mrs 指令读取该寄存器,并结合变量的偏移量来获得最终地址。
所以,__per_cpu_offset 数组是理解 per-CPU 变量设计哲学的基石,它清晰地展示了如何通过一个偏移量来实现"一份模板,多份副本"的机制。而在实际运行时,为了达到极致性能,硬件特性(如 x86 的 gs 段寄存器)会作为更快的"专线",直接、高效地完成地址转换。
网硕互联帮助中心






评论前必须登录!
注册