多核中percpu的数据分配

本文介绍了Linux 2.6内核中per-CPU数据管理机制,包括如何定义和操作每个CPU特有的数据变量,以及如何分配和释放这些数据。文章详细解释了关键宏和函数的用法,如DEFINE_PER_CPU、alloc_percpu和free_percpu等。

linux2.6 为了方便创建和操作每个CPU数据,引进了新的操作接口,percpu(),该接口简化了创建了操作每个CPU的数据。

定义于<linux/percpu.h> 和<mm/slab.c> 中

1. 定义和声明每个CPU变量:

DEFINE_PER_CPU(type, name);
这语句为系统的每个CPU都创建了一个类型为type,名字为name的变量。

如果需要在别处声明此变量,以防编译时的警告,可使用下面的宏:

DECLARE_PER_CPU(type, name);
2. 操作每个CPU的变量和指针:
get_cpu_var(name);  //返回当前处理器上的指定变量name的值, 同时将他禁止抢占;

put_cpu_var(name); //与get_cpu_var(name)相对应,重新激活抢占;

/*
 * Must be an lvalue. Since @var must be a simple identifier,
 * we force a syntax error here if it isn't.
 */
#define get_cpu_var(var) (*({				\
	preempt_disable();				\
	&__get_cpu_var(var); }))

/*
 * The weird & is necessary because sparse considers (void)(var) to be
 * a direct dereference of percpu variable (var).
 */
#define put_cpu_var(var) do {				\
	(void)&(var);					\
	preempt_enable();				\
} while (0)

通过指针来操作每个CPU的数据:

get_cpu_ptr(var); --- 返回一个void类型的指针,指向CPU ptr处的数据

put_cpu_ptr(var); --- 操作完成后,重新激活内核抢占。

#define get_cpu_ptr(var) ({				\
	preempt_disable();				\
	this_cpu_ptr(var); })

#define put_cpu_ptr(var) do {				\
	(void)(var);					\
	preempt_enable();				\
} while (0)
3. 获得别的处理器上的name变量的值

per_cpu(name, cpu) ;   //返回别的处理器cpu上变量name的值;

4. 给系统中每个处理器分配一个指定类型的对象: alloc_percpu();

#define alloc_percpu(type)	\
	(typeof(type) __percpu *)__alloc_percpu(sizeof(type), __alignof__(type))
/**
 * __alloc_percpu - allocate dynamic percpu area
 * @size: size of area to allocate in bytes
 * @align: alignment of area (max PAGE_SIZE)
 *
 * Allocate zero-filled percpu area of @size bytes aligned at @align.
 * Might sleep.  Might trigger writeouts.
 *
 * CONTEXT:
 * Does GFP_KERNEL allocation.
 *
 * RETURNS:
 * Percpu pointer to the allocated area on success, NULL on failure.
 */
void __percpu *__alloc_percpu(size_t size, size_t align)
{
	return pcpu_alloc(size, align, false);
}
EXPORT_SYMBOL_GPL(__alloc_percpu);
参数为type, 就是指定的需要分配的类型,通过类型,可以得出__alloc_percpu()的两个参数:

size =  sizeof(type);

align = __alignof__(type);

__alignof__()是gcc的一个功能,它会返回指定类型或lvalue所需的对齐字节数。

相应的释放所有处理器上指定的每个CPU数据:free_percpu();

/**
 * free_percpu - free percpu area
 * @ptr: pointer to area to free
 *
 * Free percpu area @ptr.
 *
 * CONTEXT:
 * Can be called from atomic context.
 */
void free_percpu(void __percpu *ptr)
{
	void *addr;
	struct pcpu_chunk *chunk;
	unsigned long flags;
	int off;

	if (!ptr)
		return;

	addr = __pcpu_ptr_to_addr(ptr);

	spin_lock_irqsave(&pcpu_lock, flags);

	chunk = pcpu_chunk_addr_search(addr);
	off = addr - chunk->base_addr;

	pcpu_free_area(chunk, off);

	/* if there are more than one fully free chunks, wake up grim reaper */
	if (chunk->free_size == pcpu_unit_size) {
		struct pcpu_chunk *pos;

		list_for_each_entry(pos, &pcpu_slot[pcpu_nr_slots - 1], list)
			if (pos != chunk) {
				schedule_work(&pcpu_reclaim_work);
				break;
			}
	}

	spin_unlock_irqrestore(&pcpu_lock, flags);
}
EXPORT_SYMBOL_GPL(free_percpu);

5. get_cpu()/put_cpu() --- 获得处理器编号

get_cpu() 在返回当前处理器编号之前,先回关闭内核抢占。

put_cpu() 重新打开内核抢占。

函数定义如下:

#define get_cpu()		({ preempt_disable(); smp_processor_id(); })
#define put_cpu()		preempt_enable()
使用案例如下:
int cpu;

cpu = get_cpu();   //禁止抢占内核,并将CPU设置为当前处理器。

//对每个处理器的数据进行操作,

put_cpu();   //使能内核抢占



<think>我们正在讨论per-CPU变量的实现机制。根据引用[4]中的示例代码,我们可以看到per-CPU变量的基本使用方法:使用`alloc_percpu`分配,使用`per_cpu_ptr`获取特定CPU的指针,并使用`get_cpu`和`put_cpu`来防止抢占。现在,我们需要深入其实现原理。 ###一、per-CPU变量的设计目标per-CPU变量主要用于解决多核环境下对共享数据的访问竞争问题。每个CPU都有自己独立的变量副本,这样在访问时就不需要加锁,提高了性能[^2]。 ###二、实现原理####1.数据存储布局在编译时,per-CPU变量被放在一个特殊的段(section)中。在Linux内核中,这个段通常命名为`.data..percpu`(在x86架构中)[^1]。当系统启动时,为每个CPU复制这个段的内容,使得每个CPU都有自己独立的数据副本。 ####2.动态分配与静态声明-**静态声明**:使用`DEFINE_PER_CPU(type,name)`宏,例如:```cDEFINE_PER_CPU(int, my_percpu_var);```这会在`.data..percpu`段中定义一个名为`my_percpu_var`的变量,每个CPU都有一个独立的副本。- **动态分配**:使用`alloc_percpu`函数,如引用[4]中的示例:```cptr= alloc_percpu(int);```####3.访问机制-**获取当前CPU的变量**:使用`get_cpu_var`和`put_cpu_var`宏。这两个宏会禁用内核抢占,确保在当前CPU上操作的原子性。```cint*val= get_cpu_var(my_percpu_var);*val =10;put_cpu_var(my_percpu_var);``` -**访问指定CPU的变量**:使用`per_cpu_ptr`宏,如引用[4]所示:```cthe_ptr= per_cpu_ptr(ptr,cpu);```####4.初始化过程在系统启动时,会调用`setup_per_cpu_areas`函数(在x86架构中位于`arch/x86/kernel/setup_percpu.c`)。该函数会为每个CPU分配内存,并将初始的per-CPU数据(即`.data..percpu`段的内容)复制到每个CPU的私有区域中。引用[3]中的代码片段展示了这一过程: ```cfor (i =0; i< NR_CPUS;i++,ptr +=size){__per_cpu_offset[i] =ptr -__per_cpu_start;memcpy(ptr,__per_cpu_start,__per_cpu_end -__per_cpu_start);} ```这里,`__per_cpu_offset`数组存储了每个CPU的per-CPU区域相对于原始数据的偏移量。通过这个偏移量,我们可以计算出每个CPU上变量的实际地址。###三、关键数据结构####1. `__per_cpu_offset`数组这是一个全局数组,存储了每个CPU的per-CPU区域的基地址相对于原始`.data..percpu`段起始地址的偏移量。通过这个偏移量,我们可以得到某个CPU上某个per-CPU变量的地址: ```c#define per_cpu_ptr(ptr,cpu)({ \(typeof(ptr)) ((char*)(ptr)+ __per_cpu_offset[cpu]); \}) ```####2.per-CPU变量的内存布局```mermaidgraphLRA[原始.data..percpu段] -->|包含初始数据| B(CPU0的per-CPU区域)A-->|复制| C(CPU1的per-CPU区域)A -->|复制| D(CPU2的per-CPU区域)A-->|...|E(其他CPU) ```###四、使用场景与性能优势1.**计数器**:如网络子系统中的统计计数器,每个CPU独立更新,减少缓存行竞争。2.**缓存**:如slab分配器的每CPU缓存,提高内存分配效率。 3. **避免锁竞争**:当数据主要被一个CPU访问时,使用per-CPU变量可以避免锁的开销。 ###五、注意事项1. **抢占控制**:在访问per-CPU变量时,必须使用`get_cpu`和`put_cpu`来防止进程被抢占到其他CPU上,否则可能导致数据不一致。2.**跨CPU访问**:如果需要访问其他CPU的per-CPU变量,必须确保该CPU已经停止运行(例如在热插拔时),否则需要额外的同步机制。 3. **内存占用**:per-CPU变量会为每个CPU都分配一份副本,因此对于大型数据结构,需要权衡内存开销。 ###六、示例代码解析(基于引用[4]) ```c#include <linux/module.h> #include<linux/kernel.h>#include <linux/init.h> #include<asm/io.h>#include <asm/uaccess.h> #include<linux/percpu.h> staticint *ptr;static int__inithello_init(void){int cpu;int *the_ptr;//动态分配一个int类型的per-CPU变量ptr= alloc_percpu(int);//遍历所有可能的CPUfor_each_possible_cpu(cpu){//禁止抢占,并返回当前CPU的ID(同时确保不会被迁移到其他CPU)get_cpu();//获取指定CPU的per-CPU变量指针the_ptr =per_cpu_ptr(ptr, cpu);//给该CPU的副本赋值*the_ptr= cpu;printk("val= %d\n", *the_ptr);//允许抢占put_cpu();}return0;} staticvoid __exit hello_exit(void){//释放per-CPU变量free_percpu(ptr); }module_init(hello_init);module_exit(hello_exit);``` 在这个示例中:-`alloc_percpu`为每个CPU分配了一个`int`大小的内存。 -`for_each_possible_cpu`遍历所有可能的CPU。-在循环中,通过`get_cpu`禁止抢占,然后使用`per_cpu_ptr`获取当前CPU的变量指针,并赋值。-最后使用`free_percpu`释放内存。###七、实现细节####1.静态per-CPU变量的实现以`DEFINE_PER_CPU(int, my_var)`为例,其展开后大致如下:```c__attribute__((section(".data..percpu"))) int__percpu__my_var; ```这样,`__percpu__my_var`就被放置在`.data..percpu`段中。 ####2.动态分配的实现`alloc_percpu`函数最终会调用`__alloc_percpu`,该函数会为每个CPU分配指定大小的内存,并返回一个指针。这个指针实际上指向原始数据(即第一个CPU的副本),但通过`per_cpu_ptr`访问时,会加上对应CPU的偏移量。 ###八、跨架构支持不同架构的per-CPU实现可能有所不同,主要体现在:1.**偏移量数组**:`__per_cpu_offset`数组的定义和初始化位置不同。2.**段名称**:如x86使用`.data..percpu`,而ARM可能使用不同的段名。3.**访问指令**:某些架构可能提供专门的指令来访问per-CPU变量(如x86的`%gs`段寄存器)。 ###九、性能优化技巧1. **对齐缓存行**:为了避免不同CPU的per-CPU变量共享同一缓存行(导致伪共享),可以使用`____cacheline_aligned_in_smp`属性进行对齐。```cDEFINE_PER_CPU_ALIGNED(int,my_var);``` 2. **使用局部变量**:在长时间操作per-CPU变量时,可以先将其复制到局部变量,操作完再写回,减少禁用抢占的时间。 ###十、相关函数总结|函数/宏|作用| |---------|------|| `DEFINE_PER_CPU(type, name)` |静态定义一个per-CPU变量| |`alloc_percpu(type)`|动态分配一个per-CPU变量 || `free_percpu(ptr)`|释放动态分配的per-CPU变量 || `get_cpu_var(var)`|获取当前CPU的变量指针(并禁用抢占) || `put_cpu_var(var)`|释放变量(并启用抢占) || `per_cpu_ptr(ptr,cpu)`|获取指定CPU的变量指针| ###相关问题1. per-CPU变量在SMP和NUMA架构中有什么不同的表现?2.如何避免per-CPU变量的伪共享(false sharing)问题? 3. per-CPU变量在中断上下文中如何使用?4.动态分配的per-CPU变量在内存管理上有何特点? 5.在多核系统中,per-CPU变量如何与RCU机制配合使用?[^2]</think>### Linux内核中per-CPU变量的实现机制 per-CPU变量是Linux内核中用于**高效处理多核共享数据**的关键技术,其核心思想是为每个CPU创建变量的独立副本,从而**避免多核访问时的锁竞争**。以下是其实现原理和方式: --- #### 一、设计目标与核心优势 1. **零锁并发** - 每个CPU独立访问自己的变量副本,无需锁机制[^2] - 解决多核缓存一致性导致的性能瓶颈 2. **缓存局部性优化** - 变量副本位于CPU本地缓存,减少缓存行冲突 3. **动态扩展性** - 支持CPU热插拔,动态调整副本数量 --- #### 二、实现原理图解 ```mermaid graph TD A[原始数据段 .data..percpu] --> B(CPU0副本) A --> C(CPU1副本) A --> D(CPU2副本) B --> E[通过__per_cpu_offset[0]定位] C --> F[通过__per_cpu_offset[1]定位] D --> G[通过__per_cpu_offset[2]定位] ``` --- #### 三、关键技术实现 ##### 1. 内存布局管理 - **专用数据段** 所有per-CPU变量编译时放入`.data..percpu`段(x86架构)[^1] ```c #define PER_CPU_BASE_SECTION ".data..percpu" ``` - **启动时复制** 系统初始化时为每个CPU创建独立副本: ```c for (i = 0; i < NR_CPUS; i++, ptr += size) { __per_cpu_offset[i] = ptr - __per_cpu_start; memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start); } // 引用[3] ``` ##### 2. 访问机制 - **偏移量数组** 全局数组`__per_cpu_offset[]`存储每个CPU副本的基地址偏移 ```c extern unsigned long __per_cpu_offset[NR_CPUS]; ``` - **地址计算宏** 获取指定CPU的变量地址: ```c #define per_cpu_ptr(ptr, cpu) \ ((typeof(ptr))((char*)(ptr) + __per_cpu_offset[cpu])) ``` ##### 3. 安全访问控制 - **抢占保护** 使用`get_cpu()`/`put_cpu()`确保操作原子性: ```c get_cpu(); // 禁用抢占 *per_cpu_ptr(var, smp_processor_id()) = value; put_cpu(); // 启用抢占 ``` --- #### 四、使用方式示例 ##### 1. 动态分配 ```c // 分配per-CPU整型数组 int __percpu *arr = alloc_percpu(int); // 获取CPU1的副本指针 int *cpu1_ptr = per_cpu_ptr(arr, 1); *cpu1_ptr = 100; // 安全写入 ``` ##### 2. 静态声明 ```c // 声明每CPU变量 DEFINE_PER_CPU(int, counter); // 安全访问当前CPU副本 get_cpu_var(counter)++; put_cpu_var(counter); ``` ##### 3. 遍历所有CPU ```c for_each_possible_cpu(cpu) { int *ptr = per_cpu_ptr(arr, cpu); printk("CPU%d: %d\n", cpu, *ptr); } ``` --- #### 五、性能优化技术 1. **缓存对齐** 使用`____cacheline_aligned`避免伪共享 ```c DEFINE_PER_CPU_ALIGNED(int, cacheline_var); ``` 2. **热插拔支持** 通过`cpuhp_setup_state()`注册回调函数 ```c cpuhp_setup_state(CPUHP_AP_ONLINE, "mymodule:online", my_online_cb, my_offline_cb); ``` --- #### 六、典型应用场景 1. **网络收发包统计** ```c struct net_device_stats __percpu *dev_stats; ``` 2. **内存分配器缓存** SLAB/SLUB分配器的每CPU缓存 3. **任务调度统计** 内核调度器的运行队列统计 --- ### 相关问题 1. per-CPU变量如何保证跨CPU访问的安全性? 2. 在NUMA架构中per-CPU变量实现有何不同? 3. 如何动态调整per-CPU变量的大小? 4. per-CPU变量与RCU机制如何协同工作?[^2] 5. 用户态程序能否直接访问内核的per-CPU变量?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值