linux版本:4.14.74
硬件:ARMV8 A53
1、伙伴分配器概述
内核初始化完毕后,使用页分配管理器管理物理页,当前使用的页分配器是伙伴分配器。
页分配管理自然而然,分配的单位是页。并且伙伴分配器只能分配2的n次幂个页,n称为阶(order),2的n次幂个连续页称为n阶页块。满足以下条件的两个n阶页块称为伙伴(buddy)
- 两个页块的物理地址是连续的
- 页块的第一页的物理页号必须是2的n次幂的整数倍,如0号页,2号页,4号页,8号页…
- 如果合并成(n+1)阶页块,第一页的物理页号必须是2的(n+1)次幂的整数倍。如两个2阶页块合并,第一页应是0,4,8,12…页.
2 伙伴分配器的分配和释放过程
2.1 分配过程
分配n阶页块
- 查看是否有空间的n阶页块,如果有,直接分配即可
- 没有空闲的n阶页块,查看是否有(n+1)阶的页块,如果有,把(n+1)阶页块分裂为两个n阶页块,一个插入n阶页块链表,另一个分配出去。
- 没有空闲的n+1阶页块,继续查找更大的n+2阶页块,并将其分裂为n+1阶页块,然后执行上一步,将其中一个n+1阶页块分裂为两个n阶页块。
2.2 释放过程
释放n阶页块,查看它的伙伴是否空闲,如果伙伴不空闲,那么把n阶页块插入到空闲的n阶页块链表;如果伙伴空闲,那么合并为n+1阶页块,接下来释放n+1阶页块。
3 与伙伴分配器相关的数据
struct zone {
...
struct free_area free_area[MAX_ORDER];
...
}
在区域定义struct zone中,free_area元素表示是0-MAX_ORDER阶的链表,MAX_ORDER表示的最大阶数,默认值为11
free_area是一个链表结构体
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
MIGRATE_TYPES表示的是可移动类型,内核根据可移动性把物理页分为三种类型
- 不可移动页:位置必须固定,不能移动,直接映射到内核虚拟地址空间的属于这一类。
- 可移动页:使用页表映射的页,可移动到其他的位置,然后重新修改页表映射。
- 可回收页:不能移动,但可以回收,需要数据的时候可以重新从数据源获取。
内核定义了以下迁移类型
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,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
MIGRATE_ISOLATE, /* can't allocate from here */
#endif
MIGRATE_TYPES
};
我们先只关注前三种,后面的先暂时不管。
用下面的一幅图可以来描述free_area
区域水线
struct zone {
...
unsigned long watermark[NR_WMARK];
...
}
enum zone_watermarks {
WMARK_MIN,
WMARK_LOW,
WMARK_HIGH,
NR_WMARK
};
每个内存区域有3个水线
- 高水线:如果内存区域的空闲页数大于高水线,说明该内存区域水线充足。
- 低水线:空闲页数小数低水线,说明该内存区域的内存轻微不足
- 最低水线:如果内存区域的空闲页数小于最低水线,说明该内存区域的内存严重不足。
4 核心函数
所有分配页的函数最终都会调用到函数*__alloc_pages_nodemask*
/*
* This is the 'heart' of the zoned buddy allocator.
*/
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
struct page *page;
unsigned int alloc_flags = ALLOC_WMARK_LOW;
gfp_t alloc_mask; /* The gfp_t that was actually used for allocation */
struct alloc_context ac = { };
gfp_mask &= gfp_allowed_mask;
alloc_mask = gfp_mask;
if (!prepare_alloc_pages(gfp_mask, order, preferred_nid, nodemask, &ac, &alloc_mask, &alloc_flags))
return NULL;
finalise_ac(gfp_mask, order, &ac);
/* First allocation attempt */
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
if (likely(page))
goto out;
/*
* Apply scoped allocation constraints. This is mainly about GFP_NOFS
* resp. GFP_NOIO which has to be inherited for all allocation requests
* from a particular context which has been marked by
* memalloc_no{fs,io}_{save,restore}.
*/
alloc_mask = current_gfp_context(gfp_mask);
ac.spread_dirty_pages = false;
/*
* Restore the original nodemask if it was potentially replaced with
* &cpuset_current_mems_allowed to optimize the fast-path attempt.
*/
if (unlikely(ac.nodemask != nodemask))
ac.nodemask = nodemask;
page = __alloc_pages_slowpath(alloc_mask, order, &ac);
out:
if (memcg_kmem_enabled() && (gfp_mask & __GFP_ACCOUNT) && page &&
unlikely(memcg_kmem_charge(page, gfp_mask, order) != 0)) {
__free_pages(page, order);
page = NULL;
}
trace_mm_page_alloc(page, order, alloc_mask, ac.migratetype);
return page;
}
分配函数可以分为快速分配get_page_from_freelist和慢速分配*__alloc_pages_slowpath*。申请页时,第一次尝试使用低水线来分配,也就是快速分配,如果分配失败,尝试使用最低水线,也就是慢速分配。
快速分配
/*
* get_page_from_freelist goes through the zonelist trying to allocate
* a page.
*/
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
struct zoneref *z = ac->preferred_zoneref;
struct zone *zone;
struct pglist_data *last_pgdat_dirty_limit = NULL;
/******1********/
for_next_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx,
ac->nodemask) {
struct page *page;
unsigned long mark;
/**************2**************/
if (cpusets_enabled() &&
(alloc_flags & ALLOC_CPUSET) &&
!__cpuset_zone_allowed(zone, gfp_mask))
continue;
/**************3*************/
if (ac->spread_dirty_pages) {
if (last_pgdat_dirty_limit == zone->zone_pgdat)
continue;
if (!node_dirty_ok(zone->zone_pgdat)) {
last_pgdat_dirty_limit = zone->zone_pgdat;
continue;
}
}
/**************4***********/
mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
if (!zone_watermark_fast(zone, order, mark,
ac_classzone_idx(ac), alloc_flags)) {
int ret;
/* Checked here to keep the fast path fast */
BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
/*********5*********/
if (alloc_flags & ALLOC_NO_WATERMARKS)
goto try_this_zone;
/**************6**************/
if (node_reclaim_mode == 0 ||
!zone_allows_reclaim(ac->preferred_zoneref->zone, zone))
continue;
/************7************/
ret = node_reclaim(zone->zone_pgdat, gfp_mask, order);
switch (ret) {
case NODE_RECLAIM_NOSCAN:
/* did not scan */
continue;
case NODE_RECLAIM_FULL:
/* scanned but unreclaimable */
continue;
default:
/* did we reclaim enough */
if (zone_watermark_ok(zone, order, mark,
ac_classzone_idx(ac), alloc_flags))
goto try_this_zone;
continue;
}
}
/*****************8***********/
try_this_zone:
page = rmqueue(ac->preferred_zoneref->zone, zone, order,
gfp_mask, alloc_flags, ac->migratetype);
if (page) {
prep_new_page(page, order, gfp_mask, alloc_flags);
/*
* If this is a high-order atomic allocation then check
* if the pageblock should be reserved for the future
*/
if (unlikely(order && (alloc_flags & ALLOC_HARDER)))
reserve_highatomic_pageblock(page, zone, order);
return page;
}
}
return NULL;
}
- 扫描所有符合条件的zone
- 检查cpuset
- 检查内存节点中dirty page数量数量是否超出了限制
- 检查水线是否满足要求,满足则跳到 分配内存,不满足继续向下
- 不需要检查水线,那就直接跳到 分配内存
- 回收功能没有开启,那这个节点无法分配
- 回收,然后再根据函数返回值进行再次检查是否符合要求
- 开始分配内存
未完待续。。。
参考文献
-
《Linux内核深度解析》—余华兵
-
https://www.cnblogs.com/LoyenWang/p/11626237.html