简介
- per-CPU变量是内核的一个重要机制,正如名称所示,per-CPU变量为每个cpu单独提供内存空间,每个cpu只访问修改各自的空间
- 一个per-CPU变量所需要的内存大小为:变量类型大小乘以cpu数量,即sizeof(type) x (number of cpus)
- 在NUMA系统中,计算所占内存大小的公式与上面类似,但是实际上cpu数量的计算更复杂
- 事实上,不管哪一种同步API都会带来或多或少的性能开销,但是一个per-CPU数据包含每个cpu自己的唯一私有数据,一个cpu不应该访问其他cpu对应的数据,所以按照这样逻辑不再需要使用同步API。注意:“只有这个cpu能访问这个数据“就是一种编程约定,我们需要确保当前cpu只会访问它自己的唯一数据。
- 很多内核子系统使用了这个机制:
- 内存管理的高速缓存机制
- Page分配器
- SLUB/SLAB分配器
- 经常需要更新的性能统计计数器,需要高效同步机制的例子
- vm统计
- 网络统计
- 基础设施RCU
- 性能剖析profiling、Ftrace
- VFS虚拟文件系统
- 内存管理的高速缓存机制
下图是一个简单的per-CPU计数器例子
特点
- 从2.6.19内核开始,为了方便创建和操作per-CPU数据,从而引入新的接口,函数定义单独放在allocpercpu.c文件,对于大型SMP计算机来说这些操作per-CPU数据接口简单且方便高效
- 2009年合并了动态percpu分配的补丁,引入了基于chunk实现的各种函数
- 将以前percpu变量所占内存都分别从每个cpu对应的内存区域里分配,也就变成chunk模式了。
- 可以通过相同的接口直接访问静态和动态percpu区域
- 参考:
Unit
- 每个cpu数据将放入每个cpu对应的unit内存空间
- unit不仅可能包含三种不同类型的内存区域,也有可能只包含一种内存区域
- Static内存区域
- 使用
DEFINE_PER_CPU()
宏声明per-CPU变量,其被编译进kernel映像中,在系统启动时为每个cpu定义这个变量 - 默认大小0x3ec0
- 使用
- Reserved内存区域
- 使用
DEDINE_PER_CPU()
宏声明per-CPU变量,其被编译进模块文件中,在加载模块时为每个cpu定义这个变量 - 默认大小0x2000(8KiB)
- 使用
- Dynamic 内存区域
- 借助
alloc_percpu
函数动态地分配每个cpu变量所占的内存空间 - 默认大小0x5000(20KiB)
- 在64位系统上,默认大小28KiB
- 借助
- Static内存区域
- unit大小等于
- pcpu_unit_pages
- 组成一个Unit的页帧数
- 通常大小为 16
- pcpu_unit_szie
- 组成一个Unit的字节大小(单位Byte)
- 通常大小为 0x10000(64KiB = 16 * 4KiB (PAGE_SIZE))
- pcpu_unit_pages
CPU -> Unit 映射
下图是一个在NUMA系统中cpu与unit映射的例子,(这个NUMA系统最大支持32CPU,实际有12CPU可用)