上一篇记录了伙伴系统的初始化,这里记录一下伙伴系统的组织结构,这对了解它的工作原理非常重要。
伙伴系统(buddy system)是内存管理的基础,很多内存分配接口底层都是伙伴系统。比如常见的kmalloc。伙伴系统的工作有赖于内存的组织结构。内存是按照page->zone->node来组织的。系统中的每个物理页都对应了一个struct page实例,每个内存域都关联了一个struct zone 实例,其中保存了用于管理伙伴系统的数据结构。
struct zone {
....
/* free areas of different sizes */
struct free_area free_area[MAX_ORDER];
....
}
另一个跟伙伴系统相关的重要概念是order(阶)它表示伙伴系统分配内存的单位 Bytes,范围是0~MAX_ORDER。
伙伴系统初始化完成后,单个zone中free_area中内存的组织图像为:
page分别按照1,2,4,8...的单位以链表的形式链接,这是忽略migratetype 的简化结果。
伙伴系统和zone_lists的关系非常紧密,伙伴系统和它的关系大约是这样的:
如果你了解zone_lists的组织,上图就不难理解。这里把zone_lists相关的数据结构再列一下。
// node的数据结构
typedef struct pglist_data {
...
/*
* node_zonelists contains references to all zones in all nodes.
* Generally the first zones will be references to this node's
* node_zones.
*/
// 备用zone列表
struct zonelist node_zonelists[MAX_ZONELISTS];
...
}
struct zonelist {
struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];
};
struct zoneref {
struct zone *zone; /* Pointer to actual zone */
int zone_idx; /* zone_idx(zoneref->zone) */
};
所以zone_lists就是按照一定节点顺序将所有节点的zone组织在一个链表中的数据结构。
这对伙伴系统很有帮助,因为他是按照zone来分配内存的,如果当前zone的内存不足就必须到其他的zone去找内存,这个查找的顺序就由zone_lists来提供。
内存分配中一个常见的现象是随着系统的运行,可用内存会出现碎片化。虽然内存总量上还有很多,但是却找不到足够大的连续内存。伙伴系统的反碎片化(anti-fragmentation)可以有效减少碎片化趋势。它的基础是free_area中与order正交的另一个维度-迁移类型(migrate type)。
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
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. What is important though
* is that a range of pageblocks must be aligned to
* MAX_ORDER_NR_PAGES should biggest page be bigger then
* a single pageblock.
*/
MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* can't allocate from here */
#endif
MIGRATE_TYPES
};
也就是说其实free_area其实是个二维数组,除了按阶还要按迁移类型进行组织。最终的图像大约是这样:
迁移类型最重要的是不可移动页,可回收页,可移动页。
不可移动页:内存位置固定,不能移动;
可回收页:不可移动,但是可以删除,内容可以重新生成,例如:文件映射;这种页面有利于页面回收;
可移动页:可以随意移动,用户空间使用的页面多是来自这种页,通过页表映射。可以复制页面内容同时改变页映射来移动。
对于可移动页或可回收页,很容易通过回收页和迁移页来重新组织页来开辟出大片连续空闲页面。
CMA是新的迁移类型,有空再研究一下。
MIGRATE_PCPTYPES应该指的是存在于pcp链表中的页,
struct zone {
...
struct per_cpu_pageset __percpu *pageset;
...
}
一般把单页分配和回收的任务交给pageset这个结构,给每个cpu分别建立了一个冷热页链表。
参考文献:
《深入linux内核架构》
《奔跑吧 linux内核》
linux 5.13