主页 > 互联网  > 

linux--per-CPU变量


per-CPU变量

per-CPU变量是一种存在与每个CPU本地的变量,对于每一种per-CPU变量,每个CPU在本地都有一份它的副本。

per-CPU变量的优点 多处理器系统(smp)中无需考虑与其他处理器的竞争问题(并非绝对的)可以利用处理器本地的cache硬件,提高访问速度 per-CPU变量的分类

按照分配内存空间的类型来看,有两种:

静态per-CPU变量动态per-CPU变量 静态的per-CPU变量在编译期就静态分配,动态per-CPU变量在程序运行时动态分配。 per-CPU变量的实现机制 静态per-CPU变量

相关宏

90 #ifndef PER_CPU_BASE_SECTION 91 #ifdef CONFIG_SMP 92 #define PER_CPU_BASE_SECTION ".data..percpu" 93 #else 94 #define PER_CPU_BASE_SECTION ".data" 95 #endif 96 #endif

定义和声明

4/* 5 * Base implementations of per-CPU variable declarations and definitions, where 6 * the section in which the variable is to be placed is provided by the 7 * 'sec' argument. This may be used to affect the parameters governing the 8 * variable's storage. 9 * /* per-CPU变量声明和定义的基础实现,变量被放置的section由sec参数提供,这被用于影响管理参数变量的存储方式 注意!用于声明和定义的sections必须匹配,以免由于编译器生成错误的代码来访问该节而发生链接错误。 */ 10 * NOTE! The sections for the DECLARE and for the DEFINE must match, lest 11 * linkage errors occur due the compiler generating the wrong code to access 12 * that section. 13 */ 14#define __PCPU_ATTRS(sec) \ 15 __percpu __attribute__((section(PER_CPU_BASE_SECTION sec))) \ 16 PER_CPU_ATTRIBUTES 17 18#define __PCPU_DUMMY_ATTRS \ 19 __attribute__((section(".discard"), unused)) 20#ifdef HAVE_MODEL_SMALL_ATTRIBUTE 21# define PER_CPU_ATTRIBUTES __attribute__((__model__ (__small__))) 22#endif 73 * Normal declaration and definition macros. 74 */ 75#define DECLARE_PER_CPU_SECTION(type, name, sec) \ 76 extern __PCPU_ATTRS(sec) __typeof__(type) name 77 78#define DEFINE_PER_CPU_SECTION(type, name, sec) \ 79 __PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES \ 80 __typeof__(type) name 81#endif 83/* 84 * Variant on the per-CPU variable declaration/definition theme used for 85 * ordinary per-CPU variables. 86 */ 87#define DECLARE_PER_CPU(type, name) \ 88 DECLARE_PER_CPU_SECTION(type, name, "") 90#define DEFINE_PER_CPU(type, name) \ 91 DEFINE_PER_CPU_SECTION(type, name, "")

这么多宏,最后其实就是DEFINE_PER_CPU和DECLARE_PER_CPU最重要。 举个例子:

DECLARE_PER_CPU(int, python); DEFINE_PER_CPU(int, python);

被展开为:

extern __percpu __attribute__((section("data..percou"))) int python; __percpu __attribute__((section("data..percou"))) int python;

这里的定义实际把python变量放到.data…percpu段里。

看链接脚本:kernel/vmlinux.lds

定义了percpu区域的输出段,简单版本,需要对齐 所有percpu变量都会放到这个段里,__per_cpu_start和__per_cpu_end分别用于标识该区域的起始地址与终止地址。 在C代码中使用外部变量:extern char __per_cpu_load[], __per_cpu_start[], __per_cpu_end[];来引用他们。

108 INIT_TEXT_SECTION(PAGE_SIZE) 109 . = ALIGN(16); 110 INIT_DATA_SECTION(16) 111 PERCPU(4) 676/** 677 * PERCPU - define output section for percpu area, simple version 678 * @align: required alignment 679 * 680 * Align to @align and outputs output section for percpu area. This 681 * macro doesn't maniuplate @vaddr or @phdr and __per_cpu_load and 682 * __per_cpu_start will be identical. 683 * 684 * This macro is equivalent to ALIGN(align); PERCPU_VADDR( , ) except 685 * that __per_cpu_load is defined as a relative symbol against 686 * .data.percpu which is required for relocatable x86_32 687 * configuration. 688 */ 689#define PERCPU(align) \ 690 . = ALIGN(align); \ 691 .data.percpu : AT(ADDR(.data.percpu) - LOAD_OFFSET) { \ 692 VMLINUX_SYMBOL(__per_cpu_load) = .; \ 693 VMLINUX_SYMBOL(__per_cpu_start) = .; \ 694 *(.data.percpu.first) \ 695 *(.data.percpu.page_aligned) \ 696 *(.data.percpu) \ 697 *(.data.percpu.shared_aligned) \ 698 VMLINUX_SYMBOL(__per_cpu_end) = .; \ 699 } setup_per_cpu_areas函数

DEFINE_PER_CPU(int, python)只是将python变量放到了.data…percpu 的 section中,如何让系统中每个CPU都拥有一份该变量的副本? linux内核委托setup_per_cpu_areas函数完成变量副本生成,并且对per-CPU变量的动态分配机制进行初始化。

静态per-CPU变量副本的产生

setup_per_cpu_areas函数做了这些事:

计算.data…percpu section的空间大小 static_size= __per_cpu_end - __per_cpu_start,此为所有用DEFINE_PER_CPU及其变体说定义出的静态per-CPU变量所占用空间的总大小。对于模块使用的per-CPU变量,以及动态分配的per-CPU变量,内核在初始化时后会为他们预留空间,也即下图的reserved和dynamic部分

调用alloc_bootmem_nopanic来分配一段内存,用于保存per-CPU变量的副本。(系统内存管理组件还没建立,使用的是引导期内存分配器)。这块内存要依赖于系统中CPU的数量,因为要给每个CPU创建变量的副本。每个CPU变量副本所在内存空间被称为一个unit,代码中nr_unit是SoC上CPU的数量,每个unit的大小记为unit_size。unit_size = PFN_ALIGN(static_size + reserved_size + dyn_size)。 副本所在空间的大小就是nr_units* unit_size。pcpu_base_addr指向副本空间起始地址。

经过步骤2,已经为副本创建了容纳他们的内存空间。步骤3开始将.data…percpu section中变量数据赋值到pcpu_base_addr空间,这一步就是下图两条箭头所做的事。

setup_per_cpu_areas函数的实现:

3292void __init setup_per_cpu_areas(void) 3293{ 3294 unsigned long delta; 3295 unsigned int cpu; 3296 int rc; 3297 3298 /* 3299 * Always reserve area for module percpu variables. That's 3300 * what the legacy allocator did. 3301 */ 3302 rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE, 3303 PERCPU_DYNAMIC_RESERVE, PAGE_SIZE, NULL, 3304 pcpu_dfl_fc_alloc, pcpu_dfl_fc_free); 3305 if (rc < 0) 3306 panic("Failed to initialize percpu areas."); 3307 3308 delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start; 3309 for_each_possible_cpu(cpu) 3310 __per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu]; 3311} 动态 per-CPU变量副本的产生 per-CPU变量使用案例

一个非常典型的案例:计数器 per-CPU变量非常适合做统计计数,内核专门有一个给予per-CPU变量设计的计数器(lib/percpu-counter.c) 比如在网络子系统中,要计算系统接受到的各类网络包的数量,这些包更新的频率是极快的,这就需要percpu的支持,系统中每个处理器都有这么一个对网络包数量进行计量的副本,变量更新时无需考虑多处理器竞争问题,想算出总数是只需将所有处理器的同一percpu副本相加即可。

标签:

linux--per-CPU变量由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“linux--per-CPU变量