Linux 2.6 内核 per cpu data 分析(转)

 

在Linux操作系统中,特别是针对SMP或者NUMA架构的多CPU系统的时候,描述每个CPU的私有数据的时候,Linux操作系统提供了per_cpu机制。

per_cpu机制就是让每个CPU都有自己的私有数据段,便于保护与访问。

通过宏DEFINE_PER_CPU,定义这种私有数据,只不过这种私有数据放在特定的数据段中。
#define DEFINE_PER_CPU(type, name)     \
__attribute__((__section__(".data.percpu")))   \
PER_CPU_ATTRIBUTES __typeof__(type) per_cpu__##name
在GCC中__typeof__相当于typeof(检查数据类型),用__typeof__为了兼容性。

例如1:在Linux操作系统中,用于描述每个I386CPU信息的数据结构的定义:
DEFINE_PER_CPU(struct cpuinfo_i386, cpu_info);

例如2:描述每个CPU的状态的变量的定义:*
* State for each CPU
*/
DEFINE_PER_CPU(int, cpu_state);

在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,其实就是相对于__per_cpu_start的偏移量。

系统如何为每个CPU保留这些私有数据的?

在start_kernel函数中调用执行函数setup_per_cpu_areas( ),

系统启动后,在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各有一份,其中CPU n对应的专有数据区的首地址为__per_cpu_offset[n]。
这样,前述相应于__per_cpu_start的偏移量per_cpu__runqueues就变成了相应于
__per_cpu_offset[n]的偏移量,这样.data.percpu这个section在系统初始化后
就可以释放了。

(即setup_per_cpu_areas( )的功能就是将.data.percpu中的数据拷贝到每个CPU的数据段中,每个CPU一份。
其中CPU n 对应的专有数据区的首地址为__per_cpu_offset[n]。

在Linux操作系统中,在系统启动的时候会生成以__per_cpu_start标识开头和__per_cpu_end标识结尾的数据段,在执行完函数setup_per_cpu_areas( )后,将.data.percpu中的数据拷贝到每个CPU数据段后,这个数据段就会释放(free)掉。
.data.percpu定义如下(在arch/i386/kernel/vmlinux.lds.S):)

再看如何存取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,也就是说可以进行赋值操作。
这正好是上述对应CPU的专有数据区的首地址加上对应偏移量per_cpu__runqueues,

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

如何存取每个CPU的这些私有数据呢?

第一种方法:取指定cpu的指定变量

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

#define RELOC_HIDE(ptr, off)      \
({ unsigned long __ptr;      \
    __asm__ ("" : "=g"(__ptr) : "0"(ptr));   \
    (typeof(ptr)) (__ptr + (off)); })

第二种方法:取当前CPU的指定变量

#define __get_cpu_var(var) (*RELOC_HIDE(&per_cpu__##var, __my_cpu_offset()))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值