linux内核percpu变量声明,Linux内核对per-cpu变量的实现

(2012-04-10)

摘自http://www.embexperts.com/viewthread.php?tid=131&extra=page%3D1

在Linux中,per-cpu变量用在多处理器系统中,用来为系统中的每个cpu都生成一个变量的副本,以避开多处理器互斥中的加锁问题,另一个是cpu本地的变量可以充分利用cpu的硬件缓存,提高性能。本贴讨论一下Linux内核对per-cpu变量的代码实现。

1.静态per-cpu变量

静态per-cpu变量通过DEFINE_PER_CPU和DECLARE_PER_CPU宏在内核源码中定义和声明一个per-cpu变量。这些变量与普通变量的主要区别是放在一个特殊的section里。

静态percpu变量比较好理解,内核的代码也比较简洁明快。

***************************************************************

下文针对SMP的情况,对于单CPU已经简化。

在 arch/arm/kernel/vmlinux.lds 中

.init:{

...

.=ALIGN(4096);

__per_cpu_start=.;

*(.data.percpu)

__per_cpu_end=.;

...

}

可以看到__per_cpu_start和__per_cpu_end定义

了.data.percpu这个section的开始和结尾。同时这

个section都在.init这个大块中,最终这个部分占用的内存会被释放掉。

以每个CPU的寄存器定义为例来看:

[lib/irq_regs.c]

DEFINE_PER_CPU(struct pt_regs *,__irq_regs);

EXPORT_PER_CPU_SYMBOL(__irq_regs);

[include/asm-generic/percpu.h]

#define DEFINE_PER_CPU(type,name) \

__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name

在GCC中__typeof__等价于typeof,用__typeof__为了兼容性。

__irq_regs 代表指向struct pt_regs的指针类型。__irq_regs实际就是一个偏移量,

标识该变量地址。

在系统启动后,在start_kerenl中会调用 setup_per_cpu_areas() 函数,在函数中

计算 (__per_cpu_end - __per_cpu_start+PERCPU_MODULE_RESERVE)大小

并保证按PAGE对齐,根据计算的大小*CPU数目分配空间(ptr指向此空间)。

for_each_possible_cpu(i){

__per_cpu_offset[i] = ptr - __per_cpu_start;

memcpy(ptr,__per_cpu_start,__per_cpu_end-__per_cpu_start);

ptr+=size;

}

由于数据从__per_cpu_start处转移到各CPU自己的专有数据区中了,因此存取其

中的变量就不能再用原先的值了,比如存取 __get_cpu_var(__irq_regs), 就不能再

用per_cpu____irq_regs了,需要做一个偏移量的调整,即需要加上各CPU自己的

专有数据区首地址相对于__per_cpu_start的偏移量。在这里也就

是__per_cpu_offset[i],其中CPU i的专有数据区相对于__per_cpu_start的偏移量

为__per_cpu_offset[i]。这样,就可以方便地计算专有数据区中各变量的新地址,

比如对于per_cpu___irq_regs,其新地址即变成per_cpu__irq_regs+__per_cpu_offset[i]。

可以参考__get_cpu_var这个宏的定义,它实际计算方式就是这样。

************************************************************************************

相对静态per-cpu变量,还有动态分配的per-cpu变量。普通变量动态分配很简单,用kmalloc或者kzalloc都可以的,其实per-cpu变量的动态分配也是需要利用Linux内核底层的分配函数,页面分配器。从这个角度而言,percpu memory allocator与slab memory allocator是一个层面的东西,都建立在page memory allocator基础之上。不过对于大部分驱动程序员而言,使用kmalloc与kzalloc的机会要远远大于percpu memory allocator。

1楼的图显示了静态per-cpu变量在有两个处理器中一个实现情况,为了描述,这里做个定义,CPU0与CPU1变量副本的空间大小完全一样,本贴统称这两个副本空间为副本空间,每个CPU变量副本所在空间为单元空间。

在内核初始化期间调用的setup_percpu_areas函数中,给图中的reserve和dynamic空间大约定义的大小是8KB和12KB,static空间由系统中定义的静态per-cpu变量的多少来决定。

Linux内核对percpu memory allocator使用了所谓chunk的实现方式,它实现了统一的静态per-cpu和动态per-cpu变量的实现(其实静态per-cpu变量的实现不需要chunk,但是为了统一,也把它放到chunk的管理体系,就算是大一统吧).

chunk干什么事呢?chunk是一个管理数据结构,就称之为容器吧。看看具体的数据结构还是很有必要:

struct pcpu_chunk {

struct list_head        list;                /* linked to pcpu_slot lists */

int                        free_size;        /* free bytes in the chunk */

int                        contig_hint;        /* max contiguous size hint */

void                        *base_addr;        /* base address of this chunk */

int                        map_used;        /* # of map entries used */

int                        map_alloc;        /* # of map entries allocated */

int                        *map;                /* allocation map */

void                        *data;                /* chunk data */

bool                        immutable;        /* no [de]population allowed */

unsigned long                populated[];        /* populated bitmap */

};

list:用来把chunk链接起来形成链表。每一个链表又都放到pcpu_slot数组中,根据chunk中空闲空间的大小决定放到数组的哪个元素中。

contig_hint:该chunk所管理的副本空间中空闲空间大小。

base_addr:简单地说,副本空间首地址。1楼图的副本空间也是由一个chunk来管,称之为first chunk中,副本空间中的dynamic空间用来给动态per-cpu变量使用

map_used:为了对chunk所管理的副本空间分配情况的跟踪,用来表示可以管理的个数

map_alloc:已经分配的小块个数,因为每个分配的小块都是给动态per-cpu使用的,所以其实是已经分配的变量的个数

map:整数数组,用来表示副本空间分配情况。正数表示该空间空闲,负数就已经分配给一个变量了

data:指向分配的页数据

大体上就这些。

动态分配一个per-cpu变量时,在pcpu_slot空间查找空闲空间可以满足需要的chunk,如果找不到这样的chunk,那么重新分配一个chunk,用kzalloc函数。

对一个新的chunk都会调用pcpu_get_vm_areas分配VM空间地址:

static struct pcpu_chunk *pcpu_create_chunk(void)

{

struct pcpu_chunk *chunk;

struct vm_struct **vms;

chunk = pcpu_alloc_chunk();

if (!chunk)

return NULL;

vms = pcpu_get_vm_areas(pcpu_group_offsets, pcpu_group_sizes,

pcpu_nr_groups, pcpu_atom_size, GFP_KERNEL);

if (!vms) {

pcpu_free_chunk(chunk);

return NULL;

}

chunk->data = vms;

chunk->base_addr = vms[0]->addr - pcpu_group_offsets[0];

return chunk;

}

pcpu_group_offsets[0]对于非变态的系统都是0.

所以,动态分配per-cpu变量时,先在chunk所管理的副本空间(在VM区中),然后用到哪个页面就往那个对应的vm上提交物理页面。

副本空间上实行小额分配,实际上就是有新变量分配,就在副本空间里头找,找到以后看这个vm处的地址有没有被映射到物理地址,没有就提交页面,否则不提(都提了干吗还提交呢?!),判断vm处是否提交了物理页面用bit map跟踪,chunk的数据结构中的后两个成员用来干这事。

OK,分配一个新变量之后,返回给你的是一个vm区中的地址,要让每个cpu访问到自己的vm区,得用内核自己定义的宏,其实核心思想就是用smp_get_processorid等来获得对应cpu变量在变量副本中的偏移地址,然后返回来了。

要想验证上面说的对不对,可以在内核中打印出alloc_percpu返回的地址,是否在VM区。

访问per-cpu变量为什么要禁止内核抢占?

这个和进程迁移相关。如果访问per-cpu变量的进程被抢占(如发生中断而重新调度),该进程已经得到自己per-cpu变量副本的偏移地址,当它被恢复执行并有可能迁移到别的CPU上,这时候该偏移地址对新的CPU是无效的。

struct module 中有个percpu变量,不知道如何用!

如在load_module 函数实现中:

......

if (pcpuindex) {

/* We have a special allocation for this section. */

percpu = percpu_modalloc(sechdrs[pcpuindex].sh_size,

sechdrs[pcpuindex].sh_addralign,

mod->name);

if (!percpu) {

err = -ENOMEM;

goto free_mod;

}

sechdrs[pcpuindex].sh_flags &= ~(unsigned long)SHF_ALLOC;

mod->percpu = percpu;

......

}

模块的per-cpu section是ELF文件中一个特殊的section,属于data区,模块加载时,会根据系统中CPU个数,将这个section中的数据复制相应的份数,存放在CORE section区域。这个主要在SMP系统中,不同CPU可以访问模块per-cpu section中的数据而无需使用CPU间的互斥机制。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值