Linux per-CPU变量分配与管理源码分析(未完)

什么是per-CPU变量?

per-CPU变量主要用在多处理器系统中,用来为系统中的每个CPU生成一个变量副本,per-CPU变量对于每个处理器都有一个互相独立的副本。per-CPU变量分为静态分配与动态分配两种,静态分配是指在编译内核期间分配好的per-CPU变量,动态分配是指运行期间调用per-CPU memory allocator 分配的per-CPU变量。


Linux使用Chunk数据结构来管理per-CPU变量的分配,当要分配某个size的per-CPU变量时,每个CPU的per-CPU变量副本都在同一个chunk当中分配,如果一个chunk分配满了,那么会再新增一个chunk.

为了便于分配与查找,Linux按照每个Chunk的空闲空间的size将Chunk链接到不同的list中,每次分配时从满足allocate size要求且空闲空间最小的list中的chunk中进行分配。(There are usually many small percpu allocations many of them being as small as 4 bytes.  The allocator organizes chunks into lists according to free size and tries to allocate from the fullest one.)



下面是源码分析:

参考源代码: Linux 3.10


Linux实现per-CPU模块的源文件主要位于 /include/linux/percpu.h 和 mm/percpu.c中。

struct 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 lists中,pcpu_slot是一个list_head数组,Linux将所有的Chunk按照其空闲空间的大小链入pcpu_slot数组对应的list中。

free_size: 表示此Chunk空闲空间的大小。

contig_hint: 最大的连续的空闲size

base_addr: 这个Chunk所管理的memory的起始地址(虚拟地址)

map_used: map数组中的已使用成员个数 

map_alloc: map数组的大小

map: 用于分配size的数组

Chunk使用map数组来实现指定size的分配,每个数组成员是一个int类型的值,记录了一个分配好的size或一个free size(正数表示可分配的size,负数表示已经分配出去的size),map数组大小初始化为PCPU_DFL_MAP_ALLOC,map_used初始化为1,表示只用了一个map成员来记录,因此初始化后map数组只有map[0]有效,大小为整个Chunk可供分配的size(正数),表示现在Chunk为空。在运行的过程中每当有新的size分配请求,Chunk会在map数组里寻找满足要求的空闲的size,找到后分配指定的size并记录在map数组中,最后将空闲的size减去己分配的size,必要的话会根据情况将某些空闲的size合并。随着不断的进行各种size的动态分配,map_used会一直增长,当初始化的map大小不够用的时候map数组的大小也会增长。(Allocation state in each chunk is kept using an array of integers on chunk->map.  A positive value in the map represents a free region and negative allocated.  Allocation inside a chunk is done by scanning this map sequentially and serving the first matching entry. )

data:  指向为Chunk分配的vms结构的指针。Chunk管理的memory本质上还是以page的形式分配的(first Chunk除外)。

immutable: 一个布尔变量,置1表示不可再allocate和map page.

populated[]: unsigned long 数组用于记录已经map成功的page


前面说过,Linux使用Chunk数据结构来管理per-CPU变量的分配,现在假设系统有Nr个CPU,那么意味着在外部调用per-cpu allocater分配变量时,Chunk可以同时为Nr个CPU分配per-CPU变量。而实际情况还要复杂一些,linux还要对Nr个CPU分组,这个后面会结合code讨论。


Linux用一组全局变量记录CPU的信息及per-cpu allocater最大可分配内存的信息:

static int pcpu_unit_pages __read_mostly;

每个Chunk中单个CPU可供分配per-cpu变量的内存的大小,单位page。(很明显对于每个CPU,这个值是一样的,因为per-cpu变量是对所有的CPU同时分配的)

static int pcpu_unit_size __read_mostly;
每个Chunk中单个CPU可供分配per-cpu变量的内存的大小,单位byte。单个CPU可供分配per-cpu变量的内存的大小称为一个unit。

static int pcpu_nr_units __read_mostly;
每个Chunk中unit的数量,也就是系统中CPU的数量。

static int pcpu_atom_size __read_mostly;

用于align的size。

static struct list_head *pcpu_slot __read_mostly;
pcpu_slot是list_head数组,按照空闲空间的大小链接各个Chunk到其中不同的list_head中。

static int pcpu_nr_slots __read_mostly;
pcpu_slot数组的size。 

static size_t pcpu_chunk_struct_size __read_mostly;
Chunk结构的size,在分配新的Chunk时用到。

void *pcpu_base_addr __read_mostly;
EXPORT_SYMBOL_GPL(pcpu_base_addr);

第一个Chunk所管理内存的基地址,(the address of the first chunk which starts with the kernel static area.)

前面知道全局变量pcpu_uint_size表示单个CPU可供分配的内存大小,又由前面叙述可知: 当要分配某个size的per-CPU变量时,每个CPU的per-CPU变量副本都在同一个chunk当中分配。因此每个Chunk管理的内存大小必须为Nr x (pcpu_uint_size)。


per-cpu变量分为静态分配(编译时分配)与动态分配(运行时分配),先来看静态分配:

per-cpu变量的静态分配通过将变量定义在特殊的数据段中来实现(include/linux/percpu-defs.h):

#define DECLARE_PER_CPU(type, name)					\
	DECLARE_PER_CPU_SECTION(type, name, "")

#define DEFINE_PER_CPU(type, name)					\
	DEFINE_PER_CPU_SECTION(type, name, "")
这两个宏静态声明与分配一个类型为type的per-cpu变量。DECLARE_PER_CPU_SECTION和DEFINE_PER_CPU_SECTION又分别定义为:
#define DECLARE_PER_CPU_SECTION(type, name, sec)			\
	extern __PCPU_ATTRS(sec) __typeof__(type) name

#define DEFINE_PER_CPU_SECTION(type, name, sec)				\
	__PCPU_ATTRS(sec) PER_CPU_DEF_ATTRIBUTES			\
	__typeof__(type) name
其中__PCPU_ATTRS定义为:

#define __PCPU_ATTRS(sec)						\
	__percpu __attribute__((section(PER_CPU_BASE_SECTION sec)))	\
	PER_CPU_ATTRIBUTES

__percpu是个编译扩展类型,在include/linux/compile.h文件中,__percpu是空的。而传进来的sec也是空的,PER_CPU_ATTRIBUTES也是空的,前面PER_CPU_DEF_ATTRIBUTES还是空的,所以DEFINE_PER_CPU(type, name)展开就是:

__attribute__((section(PER_CPU_BASE_SECTION sec)))

__typeof__(type) name

其中,PER_CPU_BASE_SECTION定义在(include/linux/asm-generic/percpu.h).

#define PER_CPU_BASE_SECTION ".data..percpu"

DEFINE_PER_CPU(type, name)最后展开就是:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值