Linux per_cpu机制的详解

1. 简介

2.6内核的特性,每个处理器都拥有自己的变量副本。

2. 优势

每个处理器访问自己的副本,无需加锁,可以放入自己的cache中,极大地提高了访问与更新效率。常用于计数器。

3. 使用

相关头文件:<linux/percpu.h>

(1) 编译期间分配

声明:

DEFINE_PER_CPU(type, name);

避免进程在访问一个per-CPU变量时被切换到另外一个处理器上运行或被其它进程抢占:

get_cpu_var(变量)++;

put_cpu_var(变量);

访问其他处理器的变量副本用这个宏:

per_cpu(变量,int cpu_id);

 

(2) 动态分配与释放

动态分配per-CPU变量:

void * alloc_percpu(type);

void * __alloc_percpu(size_t size, size_t align); //可以做特定的内存对齐

释放动态分配的per-CPU变量:

free_percpu();

访问动态分配的per-CPU变量的访问通过per_cpu_ptr完成:

per_cpu_ptr(void * per_cpu_var, int cpu_id);

 要想阻塞抢占,使用get_cpu()与put_cpu()即可:

int cpu = get_cpu();

ptr = per_cpu_ptr(per_cpu_var, cpu);

put_cpu();

 

 

(3) 导出Per-CPU变量给模块

EXPORT_PER_CPU_SYMBOL(per_cpu_var);

EXPORT_PER_CPU_SYMBOL_GPL(per_cpu_var);

要在模块中访问这样一个变量,应该这样做声明:

DECLARE_PER_CPU(type, name);

4. 注意

在某些体系结构上,per-CPU变量可使用的地址空间是受限的,要尽量保持这些变量比较小。

5. Per-CPU变量的实现

每个CPU都有对应的专有的数据区,在start_kernel()中调用setup_per_cpu_areas()进行分配和初始化。通过数据区的首地址与偏移量信息访问Per-CPU变量。



首先,在arch/i386/kernel/vmlinux.lds中有
 
 
 
/*will be free after init*/
 
.=ALIGN(4096);
 
__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


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值