1 per_cpu缓存数据结构
在每一个内存域结构中都有一个指针,该指针指向本内存域每cpu缓存管理结构
struct zone {
......
struct per_cpu_pageset __percpu *pageset;
......
}
struct per_cpu_pageset {
struct per_cpu_pages pcp;
......
};
struct per_cpu_pages {
int count;
int high;
int batch;
struct list_head lists[MIGRATE_PCPTYPES];
};
如果只分配一个页帧,可以直接从per_cpu缓存中分配,而不用经过伙伴系统,可以提高分配效率。Count记录了per_cpu缓存中页帧的总数,high记录了per_cpu缓存中页帧的上限,如果超过这个值就将释放 batch个页帧到伙伴系统中去,如果per_cpu中没有可分配的页帧就从伙伴系统中分配batch个页帧到缓存中来。Per_cpu缓存中的页帧的page就挂接在struct list_head lists中。为防止产生过多内存碎片,内核将页帧分类:可移动页,不可移动页,可回收页等等。内核还定义了一个枚举类型来表示这些可能的类型。
include/linux/mmzone.h
enum {
MIGRATE_UNMOVABLE,不可移动页帧
MIGRATE_RECLAIMABLE,可回收页帧
MIGRATE_MOVABLE,可移动页帧
每CPU缓存链表上能管理的页帧类型的个数
MIGRATE_PCPTYPES, /* the number of types on the pcp lists */
前三种的列表中都没用可满足分配的内存块时,就可以从MIGRATE_RESERVE分配
MIGRATE_RESERVE = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
MIGRATE_CMA,
#endif
用于跨越NUMA节点移动物理内存页,
MIGRATE_ISOLATE, /* can't allocate from here */
MIGRATE_TYPES 表示迁移类型的数目
};
2 per_cpu缓存初始化
在内核启动之初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
---------->paging_init
----------->bootmem_init
----------->arm_bootmem_free
------------>free_area_init_node
------------->free_area_init_core
-------------->zone_pcp_init
static __meminit void zone_pcp_init(struct zone *zone)
{
/*
* per cpu subsystem is not up at this point. The following code
* relies on the ability of the linker to provide the
* offset of a (static) per cpu variable into the per cpu area.
*/
zone->pageset = &boot_pageset;
if (zone->present_pages)
printk(KERN_DEBUG " %s zone: %lu pages, LIFO batch:%u\n",
zone->name, zone->present_pages,
zone_batchsize(zone));
}
初始化每个cpu的per_cpu管理结构per_cpu_pages,因为后面将有页帧分配。此时的管理结构仍然是静态分配的那个per_cpu变量,boot_pageset。
start_kernel
------->build_all_zonelists
--------->__build_all_zonelists
static __init_refok int __build_all_zonelists(void *data)
{
......
for_each_possible_cpu(cpu) {
setup_pageset(&per_cpu(boot_pageset, cpu), 0);
......
}
start_kernel
---------->setup_per_cpu_pageset
前面所用per_cpu管理结构都是临时静态分配的。现在per_cpu机制已经启动,所以重新动态分配一个per_cpu管理结构并初始化。
void __init setup_per_cpu_pageset(void)
{
struct zone *zone;
遍历每一个内存域,如果某内存域有实际内存,就初始化该内存域的per_cpu管理结构。
for_each_populated_zone(zone)
setup_zone_pageset(zone);
}
static void setup_zone_pageset(struct zone *zone)
{
int cpu;
动态分配per_cpu管理结构
zone->pageset = alloc_percpu(struct per_cpu_pageset);
for_each_possible_cpu(cpu) {
struct per_cpu_pageset *pcp = per_cpu_ptr(zone->pageset, cpu);
初始化per_cpu管理结构的相关字段,函数zone_batchsize根据内存域中页帧多少计算batch的大小
setup_pageset(pcp, zone_batchsize(zone));
变量percpu_pagelist_fraction可以再/proc/sys/vm/percpu_pagelist_fraction中设置。
if (percpu_pagelist_fraction)
setup_pagelist_highmark(pcp,
(zone->present_pages /
percpu_pagelist_fraction));
}
}
Batch值大约为内存域中内存的0.25‰。
static void setup_pageset(struct per_cpu_pageset *p, unsigned long batch)
{
struct per_cpu_pages *pcp;
int migratetype;
memset(p, 0, sizeof(*p));
pcp = &p->pcp;
pcp->count = 0;
pcp->high = 6 * batch; per_cpu缓存中页帧数不能超过6 *batch。
pcp->batch = max(1UL, 1 * batch);
清空链表,每一种类型的页帧都有对应类型的链表
for (migratetype = 0; migratetype < MIGRATE_PCPTYPES; migratetype++)
INIT_LIST_HEAD(&pcp->lists[migratetype]);
}