Linux per-CPU实现分析

本文深入探讨Linux内核中的Per-CPU变量机制,详细解释了如何通过链接器魔法为每个CPU分配专有数据区,并介绍了如何通过宏定义实现对这些变量的安全高效访问。

217 static DEFINE_PER_CPU(struct runqueue, runqueues);
11 #define DEFINE_PER_CPU(type, name)
12 __attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
13



首先,在arch/i386/kernel/vmlinux.lds中有

  /* will be freed after init */

  . = ALIGN(4096);		/* Init code and data */

  __init_begin = .;



  /* 此处省略若干行:) */

  

  . = ALIGN(32);

  __per_cpu_start = .;

  .data.percpu  : { *(.data.percpu) }

  __per_cpu_end = .;

  . = ALIGN(4096);

  __init_end = .;

  /* freed after init ends here */




这说明__per_cpu_start和__per_cpu_end标识.data.percpu这个section的开头和结尾
并且,整个.data.percpu这个section都在__init_begin和__init_end之间,
也就是说,该section所占内存会在系统启动后释放(free)掉

因为有
#define DEFINE_PER_CPU(type, name)
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name

所以
static DEFINE_PER_CPU(struct runqueue, runqueues);
会扩展成
__attribute__((__section__(".data.percpu"))) __typeof__(struct runqueue)
per_cpu__runqueues;
也就是在.data.percpu这个section中定义了一个变量per_cpu__runqueues,
其类型是struct runqueue。事实上,这里所谓的变量per_cpu__runqueues,
其实就是一个偏移量,标识该变量的地址。

--------------------
其次,系统启动后,在start_kernel()中会调用如下函数
unsigned long __per_cpu_offset[NR_CPUS];



static void __init setup_per_cpu_areas(void)

{

	unsigned long size, i;

	char *ptr;

	/* Created by linker magic */

	extern char __per_cpu_start[], __per_cpu_end[];



	/* Copy section for each CPU (we discard the original) */

	size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);

#ifdef CONFIG_MODULES

	if (size < PERCPU_ENOUGH_ROOM)

		size = PERCPU_ENOUGH_ROOM;

#endif



	ptr = alloc_bootmem(size * NR_CPUS);



	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);

	}

}




在该函数中,为每个CPU分配一段专有数据区,并将.data.percpu中的数据拷贝到其中,
每个CPU各有一份。由于数据从__per_cpu_start处转移到各CPU自己的专有数据区中了,
因此存取其中的变量就不能再用原先的值了,比如存取per_cpu__runqueues
就不能再用per_cpu__runqueues了,需要做一个偏移量的调整,
即需要加上各CPU自己的专有数据区首地址相对于__per_cpu_start的偏移量。
在这里也就是__per_cpu_offset[i],其中CPU i的专有数据区相对于
__per_cpu_start的偏移量为__per_cpu_offset[i]。
这样,就可以方便地计算专有数据区中各变量的新地址,比如对于per_cpu_runqueues,
其新地址即变成per_cpu_runqueues+__per_cpu_offset[i]。

经过这样的处理,.data.percpu这个section在系统初始化后就可以释放了。

--------------------
再看如何存取per cpu的变量
/* This macro obfuscates arithmetic on a variable address so that gcc

   shouldn't recognize the original var, and make assumptions about it */

#define RELOC_HIDE(ptr, off)					

  ({ unsigned long __ptr;					

    __asm__ ("" : "=g"(__ptr) : "0"(ptr));		

    (typeof(ptr)) (__ptr + (off)); })



/* var is in discarded region: offset to particular copy we want */

#define per_cpu(var, cpu) (*RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]))

#define __get_cpu_var(var) per_cpu(var, smp_processor_id())



#define get_cpu_var(var) (*({ preempt_disable(); &__get_cpu_var(var); }))




对于__get_cpu_var(runqueues),将等效地扩展为
__per_cpu_offset[smp_processor_id()] + per_cpu__runqueues
并且是一个lvalue,也就是说可以进行赋值操作。
这正好是上述per_cpu__runqueues变量在对应CPU的专有数据区中的新地址。

由于不同的per cpu变量有不同的偏移量,并且不同的CPU其专有数据区首地址不同,
因此,通过__get_cpu_var()便访问到了不同的变量。

--END


转至:http://www.unixresources.net/linux/clf/linuxK/archive/00/00/47/91/479165.html


<think>嗯,用户想了解per-cpu_rwsem的实现细节和在内核编程中的使用。首先,我需要回忆一下per-cpu_rwsem的相关知识。per-cpu_rwsem是Linux内核中的一种读写信号量,它通过每CPU计数器来优化传统读写信号量的扩展性问题。传统读写信号量在高竞争场景下性能可能不佳,而per-cpu_rwsem通过减少锁争用来提升性能。接下来,我需要考虑它的实现细节。比如,每CPU计数器是如何工作的,写者如何获取锁,读者如何进入和退出。可能涉及到的结构体有structrw_semaphore,以及相关的函数如percpu_down_read、percpu_up_read等。写者可能需要使用percpu_down_write来获取独占访问权,而读者则使用percpu_down_read来进入临界区。然后,用户可能关心的使用场景,比如RCU机制、内存管理子系统等。需要举一些具体的例子,比如在mm/rmap.c中的使用情况。此外,还需要提到优化方法,比如避免长时间持有写锁,以减少对读者性能的影响。还要注意用户提到的引用部分,虽然之前的引用是关于mutex和上下文切换的开销,但per-cpu_rwsem的设计正是为了减少这种开销,所以可以适当关联。不过用户指出引用仅供参考,所以重点还是放在per-cpu_rwsem本身。可能生成的相关问题包括per-cpu_rwsem与传统读写信号量的对比,如何在内核模块中使用它,性能分析,以及常见问题排查。这些问题能帮助用户更全面地理解该机制。</think>### per-cpu_rwsem 实现细节 #### 核心设计 per-cpu_rwsem 是 Linux 内核中针对读写信号量(rw_semaphore)的扩展性优化实现,主要特点: 1. **每CPU计数器**:通过维护每个CPU的本地计数器,减少全局锁争用 2. **写者优先**:使用全局原子计数器处理写者请求,保证写者优先 3. **快速路径**:读者通过`this_cpu_inc()`原子操作快速进入临界区[^1] #### 关键数据结构 ```c // 内核实现片段(简化版) struct rw_semaphore { atomic_t count; struct percpu_counter readers; struct task_struct *writer; }; ``` #### 工作流程 1. **读者进入**: ```c void percpu_down_read(struct rw_semaphore *sem) { preempt_disable(); // 禁用抢占 this_cpu_inc(sem->readers); // 本地计数器递增 smp_mb(); // 内存屏障 } ``` 2. **写者获取**: ```c void percpu_down_write(struct rw_semaphore *sem) { atomic_inc(&sem->count); // 全局计数器递增 synchronize_rcu(); // 等待现有读者退出 } ``` #### 性能优化 - **批量更新**:通过`percpu_counter_sum()`合并多个CPU的计数器更新 - **RCU同步**:利用RCU机制实现无锁读者检测 - **自适应切换**:当检测到写者等待时,自动切换到传统读写信号量模式 ### 典型使用场景 1. **RCU机制**:用于保护RCU宽限期(grace period)[^1] 2. **内存管理**:在mm/rmap.c中保护反向映射数据结构 3. **文件系统**:XFS等文件系统的元数据保护 ### 开发注意事项 1. **读者上下文**:必须保证读者在临界区内不睡眠 2. **写者延迟**:写者需要等待所有现有读者退出 3. **调试支持**:可通过`CONFIG_DEBUG_RWSEMS`开启调试检查
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值