http://embexperts.com/viewthread.php?tid=131
在Linux中,per-cpu变量用在多处理器系统中,用来为系统中的每个cpu都生成一个变量的副本,以避开多处理器互斥中的加锁问题,另一个是cpu本地的变量可以充分利用cpu的硬件缓存,提高性能。本贴讨论一下Linux内核对per-cpu变量的代码实现。 1.静态per-cpu变量 静态percpu变量比较好理解,内核的代码也比较简洁明快。 相对静态per-cpu变量,还有动态分配的per-cpu变量。普通变量动态分配很简单,用kmalloc或者kzalloc都可以的,其实per- cpu变量的动态分配也是需要利用Linux内核底层的分配函数,页面分配器。从这个角度而言,percpu memory allocator与slab memory allocator是一个层面的东西,都建立在page memory allocator基础之上。不过对于大部分驱动程序员而言,使用kmalloc与kzalloc的机会要远远大于percpu 1楼的图显示了静态per-cpu变量在有两个处理器中一个实现情况,为了描述,这里做个定义,CPU0与CPU1变量副本的空间大小完全一样,本贴统称这两个副本空间为副本空间,每个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中空闲空间的大小决定放到数组的哪个元素中。 大体上就这些。 动态分配一个per-cpu变量时,在pcpu_slot空间查找空闲空间可以满足需要的chunk,如果找不到这样的chunk,那么重新分配一个chunk,用kzalloc函数。 static struct pcpu_chunk *pcpu_create_chunk(void)
{ struct pcpu_chunk *chunk; struct vm_struct **vms; chunk = pcpu_alloc_chunk(); vms = pcpu_get_vm_areas(pcpu_group_offsets, pcpu_group_sizes, chunk->data = vms; 复制代码 pcpu_group_offsets[0]对于非变态的系统都是0. 所以,动态分配per-cpu变量时,先在chunk所管理的副本空间(在VM区中),然后用到哪个页面就往那个对应的vm上提交物理页面。 OK,分配一个新变量之后,返回给你的是一个vm区中的地址,要让每个cpu访问到自己的vm区,得用内核自己定义的宏,其实核心思想就是用smp_get_processorid等来获得对应cpu变量在变量副本中的偏移地址,然后返回来了。 要想验证上面说的对不对,可以在内核中打印出alloc_percpu返回的地址,是否在VM区。 |