菜单

kaoqi
kaoqi
发布于 2026-09-22 / 6 阅读
0
0

kernel的Per-CPU机制

随着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。


评论