文章目录
- 背景
- 数据结构
- pcp的初始化流程
背景
Linux系统中0阶内存分配需求是最多的, 而且经常存在频繁分配释放的行为,如果每次都去伙伴系统中申请,会经常需要获取zone->lock锁住整个zone区域。随着CPU核心数的增加,伙伴系统锁竞争激烈程度也会越来越大。
为了改善这个问题,linux内核中引入了per_cpu_pageset(下面简称pcp)。优化思路是先从zone一次性拿一些页出来,放到每个cpu自己本地中。释放也先放回到这里,等满了再一起还给zone。
数据结构
struct zone {
...
struct per_cpu_pages __percpu *per_cpu_pageset;
...
};
内核使用per_cpu_pageset数据结构记录pcp相关信息,其内嵌在zone数据结构中。
/* Fields and list protected by pagesets local_lock in page_alloc.c */
struct per_cpu_pages {
spinlock_t lock; /* Protects lists field */
int count; /* number of pages in the list */
int high; /* high watermark, emptying needed */
int batch; /* chunk size for buddy add/remove */
short free_factor; /* batch scaling factor during free */
#ifdef CONFIG_NUMA
short expire; /* When 0, remote pagesets are drained */
#endif
/* Lists of pages, one per migrate type stored on the pcp-lists */
struct list_head lists[NR_PCP_LISTS];
} ____cacheline_aligned_in_smp;
下面为几个关键成员的含义:
count记录了per_cpu缓存中页帧的总数,
high记录了per_cpu缓存中页帧的上限,如果超过这个值就将释放 batch个页帧到伙伴系统中去,如果per_cpu中没有可分配的页帧就从伙伴系统中分配batch个页帧到缓存中来。
per_cpu缓存中的页帧的page就挂接在链表数组struct list_head lists中。
为防止产生过多内存碎片,内核将页帧分类:可移动页,不可移动。内核还定义了一个枚举类型来表示这些可能的类型。
enum migratetype {
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
MIGRATE_RECLAIMABLE,
MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
/*
* MIGRATE_CMA migration type is designed to mimic the way
* ZONE_MOVABLE works. Only movable pages can be allocated
* from MIGRATE_CMA pageblocks and page allocator never
* implicitly change migration type of MIGRATE_CMA pageblock.
*
* The way to use it is to change migratetype of a range of
* pageblocks to MIGRATE_CMA which can be done by
* __free_pageblock_cma() function.
*/
MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* can't allocate from here */
#endif
MIGRATE_TYPES
};
pcp页帧的类型只可能是:
MIGRATE_UNMOVABLE,
MIGRATE_MOVABLE,
MIGRATE_RECLAIMABLE,
不同类型和大小的pcp页帧挂在不同链表上,所以上面的lists是个数组。
pcp的初始化流程
在内核启动之初per_cpu机制还没有初始化,用于动态分配per_cpu变量的空间还没有分配,所以定义了一个静态的per_cpu变量boot_pageset,用以暂时管理内存域的per_cpu缓存。
mm/page_alloc.c
static DEFINE_PER_CPU(struct per_cpu_pageset, boot_pageset);
初始化内存域的zone->pageset字段:
start_kernel
------->setup_arch
----------->bootmem_init
------------->zone_sizes_init
----------->free_area_init
------------>free_area_init_node
------------->free_area_init_core
------------->zone_init_internals
-------------->zone_pcp_init