我们上一章说了
实现内核同步的方法很多,如下表
技术 | 说明 | 适用范围 |
每CPU变量 | 在CPU之间复制数据结构 | 所有CPU |
原子操作 | 对一个计数器原子地“读-修改-写”的指令 | 所有CPU |
内存屏障 | 避免指令重新排序 | 本地CPU或所有CPU |
自旋锁 | 加锁时忙等 | 所有CPU |
信号量 | 加锁时阻塞等待 | 所有CPU |
顺序锁 | 基于访问计数器的锁 | 所有CPU |
本地中断的禁止 | 禁止单个CPU上的中断处理 | 本地CPU |
本地软中断的禁止 | 禁止单个CPU上的可延迟函数处理 | 本地CPU |
读-复制-更新(RCU) | 通过指针而不是锁来访问共享数据结构 | 所有CPU |
但是这个每CPU变量,让我看起来非常的拗口,什么是每CPU变量,我刚开始看的时候,以为别人写错了,后面又查了很多资料,确定是对的,才放心,原来是翻译的问题
优点
per_cpu的好处是访问它不需要加锁,而且这个变量存在CPU的cache里,访问速度会非常快
原英文是 per_cpu
/* * Add a offset to a pointer but keep the pointer as is. * * Only S390 provides its own means of moving the pointer. */ #ifndef SHIFT_PERCPU_PTR /* Weird cast keeps both GCC and sparse happy. */ #define SHIFT_PERCPU_PTR(__p, __offset) ({ \ __verify_pcpu_ptr((__p)); \ RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset)); \ }) #endif /* * A percpu variable may point to a discarded regions. The following are * established ways to produce a usable pointer from the percpu variable * offset. */ #define per_cpu(var, cpu) \ (*SHIFT_PERCPU_PTR(&(var), per_cpu_offset(cpu))) |
既然这么拗口,我觉得就没有必要翻译了,直接说明是per_cpu 就好了
怎么使用这个 per_cpu 呢?
在源码
drivers\cpufreq\cpufreq_userspace.c
下面有一个使用的历程
声明一个 per_cpu
static DEFINE_PER_CPU(unsigned int, cpu_is_managed); #define DEFINE_PER_CPU(type, name) \ DEFINE_PER_CPU_SECTION(type, name, "") |
使用一个per_cpu
get_cpu_var(var) 和 set_cpu_var(var) |
使用另一个CPU的per_cpu变量
per_cpu(cpu_cur_freq, freq->cpu) = freq->new; |
导出一个per_cpu 变量供其他模块使用
EXPORT_PER_CPU_SYMBOL(per_cpu_var); EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var); |
注意!!
在使用per_cpu的时候,记得禁止内核抢占
DECLARE_PER_CPU(int, mycounter); ● int cpu; ● preempt_disable(); //禁止抢占 ● cpu = smp_processor_id(); ● per_cpu(mycounter, cpu) += 1; ● preempt_enable(); //开启内核抢占 |
SMP系统chcae 命中
什么是smp 系统呢?就是有几个 CPU组成的芯片,就是我们通常说的几核几核的说法,大概的图像这样
per_cpu解决的是多个CPU上共享数据的问题,如果声明了一个变量A,这个变量A在每个CPU下都有自己的副本,使用的堆栈地址也不一样,但是如果改变了A的值,其他的CPU同样能获取这个改变的值。
这样的话每个CPU只要关心单个CPU的并发问题,不用考虑多CPU的互斥加锁并发问题。
声明和定义Per-CPU变量的API | 描述 |
DECLARE_PER_CPU(type, name) DEFINE_PER_CPU(type, name) | 普通的、没有特殊要求的per cpu变量定义接口函数。没有对齐的要求 |
DECLARE_PER_CPU_FIRST(type, name) DEFINE_PER_CPU_FIRST(type, name) | 通过该API定义的per cpu变量位于整个per cpu相关section的最前面。 |
DECLARE_PER_CPU_SHARED_ALIGNED(type, name) DEFINE_PER_CPU_SHARED_ALIGNED(type, name) | 通过该API定义的per cpu变量在SMP的情况下会对齐到L1 cache line ,对于UP,不需要对齐到cachine line |
DECLARE_PER_CPU_ALIGNED(type, name) DEFINE_PER_CPU_ALIGNED(type, name) | 无论SMP或者UP,都是需要对齐到L1 cache line |
DECLARE_PER_CPU_PAGE_ALIGNED(type, name) DEFINE_PER_CPU_PAGE_ALIGNED(type, name) | 为定义page aligned per cpu变量而设定的API接口 |
DECLARE_PER_CPU_READ_MOSTLY(type, name) DEFINE_PER_CPU_READ_MOSTLY(type, name) | 通过该API定义的per cpu变量是read mostly |
per_cpu 在内存中固定的section
参考资料:(公众号后台回复 per_cpu 获取)
推荐阅读:
第4章 第三节 内核同步
第4章 原子操作 第二节