随着kernel的展开,发现不讲清楚kernel在不同CPU上的逻辑结构不行,所以需要再次专门讨论Per-CPU机制。
Per-CPU的核心目的是为每个逻辑CPU提供一份独立的数据副本,从而避免多核竞争带来的锁开销,并提高缓存局部性。简单的讲,就是同样的一份C代码,被不同的CPU加载运行时,可以由Per-CPU机制直接进行区分。
在之前讲述kernel的smp_setup_processor_id函数的时候,提及过俩个核心函数:
set_cpu_logic_map:这个函数用来提供一个cpuid到MPIDR_EL1寄存器的数组
set_my_cpu_offset:用来将tpidr_el1、tpidr_el2寄存器的值设置为off的值(这里的off在kernel中就是当前CPU的ID值)
更具体的说,set_my_cpu_offset是给smp_processor_id函数打前哨的一种函数。
smp_processor_id函数
这个函数在/include/linux/smp.h中被展开为raw_smp_setup_processor_id,当CONFIG_SMP参数为真时,会配合makefile指定gcc的-I检索参数,导入对应arch架构下的/arch/<architecture>/include/asm/smp.h文件,并在其中完成raw_smp_setup_processor_id的定义。
对于arm64来说,定义就是:(/asm/smp.h)
#define smp_processor_id() __smp_processor_id()
#define __smp_processor_id(x) raw_smp_processor_id(x)
#define raw_smp_processor_id() (*raw_cpu_ptr(&cpu_number))这里的raw_cpu_ptr在/include/linux/percpu-def.h中展开:
#define raw_cpu_ptr(ptr) \
({ \
__verify_pcpu_ptr(ptr); \
arch_raw_cpu_ptr(ptr); \
})
#define __verify_pcpu_ptr(ptr) \
do { \
const void __percpu *__vpp_verify = (typeof((ptr) + 0))NULL; \
(void)__vpp_verify; \
} while (0)
#define SHIFT_PERCPU_PTR(__p, __offset) \
RELOC_HIDE((typeof(*(__p)) __kernel __force *)(__p), (__offset))arch_raw_cpu_ptr在/include/asm-generic/percpu.h中展开:
#define arch_raw_cpu_ptr(ptr) SHIFT_PERCPU_PTR(ptr, __my_cpu_offset)并使用了/include/linux/compiler.h文件:
#ifndef RELOC_HIDE
# define RELOC_HIDE(ptr, off) \
({ unsigned long __ptr; \
__ptr = (unsigned long) (ptr); \
(typeof(ptr)) (__ptr + (off)); })
#endif所以,对于arch_raw_cpu_ptr来说,就是ptr偏移了。具体来说,在RELOC_HIDE中,将ptr指针强制转换为整数unsigned long,然后加上偏移量off,最终转换回原来的指针。
因此,可以说,只要使用了smp_processor_id函数,就可以根据cpu_number这个变量的基地址+cpuid偏移量,获取到cpu_number变量基地址+偏移地址的内存地址的值,就是processor的id了。
DEFINE_PER_CPU宏展开
从上面的smp processor id函数的展开可以看出,具体的CPU ID的值实际上存储于&cpu_number之中。这个cpu number变量是由DEFINE_PER_CPU进行宏展开并定义的。
在/include/linux/percpu-defs.h中:
#define __PCPU_DUMMY_ATTRS \
__section(".discard") __attribute__((unused))
#define DEFINE_PER_CPU_SECTION(type, name, sec) \
__PCPU_DUMMY_ATTRS char __pcpu_scope_##name; \
extern __PCPU_DUMMY_ATTRS char __pcpu_unique_##name; \
__PCPU_DUMMY_ATTRS char __pcpu_unique_##name; \
extern __PCPU_ATTRS(sec) __typeof__(type) name; \
__PCPU_ATTRS(sec) __weak __typeof__(type) name
#else
#define DEFINE_PER_CPU(type, name) \
DEFINE_PER_CPU_SECTION(type, name, "")其中,展开里面的__pcpu_scope_用于作用域标记、__pcpu_unique_用于重复性检查,只有最终的__PCPU_ATTRS(sec) __weak __typeof__(type) name用于真正定义这样的一个变量。
返回到/asm/smp.h这个函数中,实际上使用的是下面这个宏展开:
DECLARE_PER_CPU_READ_MOSTLY(int, cpu_number);它对应的展开是:(同样在percpu-defs.h中)
#define DECLARE_PER_CPU_SECTION(type, name, sec) \
extern __PCPU_DUMMY_ATTRS char __pcpu_scope_##name; \
extern __PCPU_ATTRS(sec) __typeof__(type) name
#define DECLARE_PER_CPU_READ_MOSTLY(type, name) \
DECLARE_PER_CPU_SECTION(type, name, "..read_mostly")这里是声明,在/arch/arm64/kernel/smp.c文件中,有具体的定义:
DEFINE_PER_CPU_READ_MOSTLY(int, cpu_number);
EXPORT_PER_CPU_SYMBOL(cpu_number);也就是说,在此处声明了cpu_number作为一个int。