设为首页 加入收藏

TOP

Linux per-CPU实现分析
2014-11-24 12:25:50 来源: 作者: 【 】 浏览:0
Tags:Linux per-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


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇如何使用Linux工作队列workqueue 下一篇Linux工作队列workqueue实现分析

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·数据库:推荐几款 Re (2025-12-25 12:17:11)
·如何最简单、通俗地 (2025-12-25 12:17:09)
·什么是Redis?为什么 (2025-12-25 12:17:06)
·对于一个想入坑Linux (2025-12-25 11:49:07)
·Linux 怎么读? (2025-12-25 11:49:04)