申请pages的基础知识在“Buddy system基础 ”这篇文章中已经讲过了,这里主要分析申请pages的代码。
代码框架
alloc_pages //alloc_pages_node(numa_node_id(), gfp_mask, order)
|----->return __alloc_pages_node(nid, gfp_mask, order);
| |----->return __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
| | |----->return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL);
| | | |----->.high_zoneidx = gfp_zone(gfp_mask),
| | | | 根据传递进来的gfp_mask,选择从那个zone开始分配
| | | |----->.zonelist = zonelist,
| | | | 设置要从哪个zonelist分配,对于UMA来说,只有一个zonelist
| | | |----->.nodemask = nodemask,
| | | |----->.migratetype = gfpflags_to_migratetype(gfp_mask),
| | | | 根据传递进来的gfp_mask,选择要分配哪种迁移类型的page
| | | |----->ac.preferred_zoneref = first_zones_zonelist(ac.zonelist,
| | | | ac.high_zoneidx, ac.nodemask);
| | | | 找到最先使用的zone
| | | |----->page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
| | | | 遍历所有符合条件的zone,分配pages
| | | | |----->page = buffered_rmqueue(ac->preferred_zoneref->zone, zone, order,
| | | | | gfp_mask, alloc_flags, ac->migratetype);
| | | | | |----->如果order为0,即分配一个page,则走如下逻辑
| | | | | |----->pcp = &this_cpu_ptr(zone->pageset)->pcp;
| | | | | |----->list = &pcp->lists[migratetype];
| | | | | | 获取当前cpu的pageset链表,为该cpu分配的free pages会加入该链表
| | | | | |----->pcp->count += rmqueue_bulk(zone, 0, pcp->batch, list, migratetype, cold);
| | | | | | |----->struct page *page = __rmqueue(zone, order, migratetype);
| | | | | | | 从所选的zone,迁移类型为migratetype的free_list中,分配1个pages
| | | | | | |----->list_add_tail(&page->lru, list);
| | | | | | | 将分配到的page加入pcp->lists[migratetype]链表
| | | | | |----->page = list_first_entry(list, struct page, lru);
| | | | | |----->list_del(&page->lru);
| | | | | |----->pcp->count--;
| | | | | | 从pcp->lists[migratetype]取出一个page,并将该page从链表中删除
| | | | | |----->如果order不为0,即分配多个page,则走如下逻辑
| | | | | | |----->page = __rmqueue(zone, order, migratetype);
| | | | | | | 直接分配
| | | | |----->prep_new_page(page, order, gfp_mask, alloc_flags)
| | | | |----->return page;
重要函数分析
__rmqueue
源代码:
static struct page *__rmqueue(struct zone *zone, unsigned int order,
int migratetype)
{
struct page *page;
page = __rmqueue_smallest(zone, order, migratetype);
if (unlikely(!page)) {
if (migratetype == MIGRATE_MOVABLE)
page = __rmqueue_cma_fallback(zone, order);
if (!page)
page = __rmqueue_fallback(zone, order, migratetype);
}
trace_mm_page_alloc_zone_locked(page, order, migratetype);
return page;
}
代码分析:
首先调用__rmqueue_smallest 从migratetype对应的链表中分配1<
__rmqueue_smallest
源代码:
static inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
int migratetype)
{
unsigned int current_order;
struct free_area *area;
struct page *page;
/* Find a page of the appropriate size in the preferred list */
for (current_order = order; current_order < MAX_ORDER; ++current_order) {
area = &(zone->free_area[current_order]);
page = list_first_entry_or_null(&area->free_list[migratetype],
struct page, lru);
if (!page)
continue;
list_del(&page->lru);
rmv_page_order(page);
area->nr_free--;
expand(zone, page, order, current_order, area, migratetype);
set_pcppage_migratetype(page, migratetype);
return page;
}
return NULL;
}
代码分析:
假设我们在建立了buddy分配器之后,首次分配order为0的pages,即分配一个page.
我们首先搞清楚buddy分配器刚刚建立完成后,buddy system上下文的状况:
- 所有的pages 1024个为一组链入order为10的zone->free_area中
- 如果page num是不是1024个整数倍,则会有pages链入其他的order对应的zone->free_area中,我们暂时不考虑这种情况。
该函数会从order对应的zone->free_area分配page,我们假设order为0,则order 0-9对应的zone->free_area都是空的,order 10对应的zone->free_area不为空,所以从order 10对应的zone->free_area分配page,其过程如下:
1. 从migratetype对应的free_list中获取一个element,即1024个pages的头。
2. 将该element从链表中删除
3. 设置page->_mapcount为-1,即将其从buddy分配器中移除
4. 设置分配到的page的迁移类型
5. 从新划分zone->free_area
这里要重点说下第5点,重新划分zone->free_area。我们只需要分配一个page,但是buddy分配器给了我们1024个pages,有1023个多余的page,这1023个多余的page要重新回到buddy分配器,具体实现就是通过调用exand函数。
expand
源代码:
static inline void expand(struct zone *zone, struct page *page,
int low, int high, struct free_area *area,
int migratetype)
{
unsigned long size = 1 << high;
while (high > low) {
area--;
high--;
size >>= 1;
VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);
/*
* Mark as guard pages (or page), that will allow to
* merge back to allocator when buddy will be freed.
* Corresponding page table entries will not be touched,
* pages will stay not present in virtual address space
*/
if (set_page_guard(zone, &page[size], high, migratetype))
continue;
list_add(&page[size].lru, &area->free_list[migratetype]);
area->nr_free++;
set_page_order(&page[size], high);
}
}
代码分析:
这个函数其实很简单,我们先来解释一下参数:
zone : 当前内存域
page : 对应order的起始page
low : 需要分配的pages对应的order
high : 实际分配的pages对应的order
area : 实际分配的pages对应的order的链表
该函数很简单,首先将1024个pages一分为二,后半部分加入order为9的链表,前半部分512个pages继续拆分;将512个pages一分为2,后半部分加入order为8的链表,前半部分256个pages继续拆分;最终order为0,1,2,3,4,5,6,7,8,9对应的链表各连入一个成员,总共是1023个pages,而order为10的链表删除一个成员。
我们假设第二次要分配order为3个pages,则会直接从order为3的链表中获取pages。
fallbacks数组
源代码:
static int fallbacks[MIGRATE_TYPES][4] = {
[MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },
[MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_TYPES },
[MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
#ifdef CONFIG_CMA
[MIGRATE_CMA] = { MIGRATE_TYPES }, /* Never used */
#endif
#ifdef CONFIG_MEMORY_ISOLATION
[MIGRATE_ISOLATE] = { MIGRATE_TYPES }, /* Never used */
#endif
};
代码分析:
mem_init初始化完成后,所有的free memory都在order为10的area为迁移类型为MIGRATE_MOVABLE的list中。如果现在要分配一个其他类型的pages怎么办?很明显,没有其他办法,只能偷取迁移类型为MIGRATE_MOVABLE的pages。
这个数组定义了当某种迁移类型的链表分配不到需要的pages,就会到其备用迁移类型对应的链表分配。比如当所有的page都在MIGRATE_MOVABLE迁移类型对应的链表上时,如果要分配迁移类型为MIGRATE_UNMOVABLE的pages,则最终会在迁移类型为MIGRATE_MOVABLE的链表分配到pages,说白了,就是偷,那么具体是嗯么偷的呢?这就要看_rmqueue_fallbck函数了。
__rmqueue_fallback
源代码:
static inline struct page *
__rmqueue_fallback(struct zone *zone, unsigned int order, int start_migratetype)
{
struct free_area *area;
unsigned int current_order;
struct page *page;
int fallback_mt;
bool can_steal;
/* Find the largest possible block of pages in the other list */
for (current_order = MAX_ORDER-1;
current_order >= order && current_order <= MAX_ORDER-1;
--current_order) {
area = &(zone->free_area[current_order]);
fallback_mt = find_suitable_fallback(area, current_order,
start_migratetype, false, &can_steal);
if (fallback_mt == -1)
continue;
page = list_first_entry(&area->free_list[fallback_mt],
struct page, lru);
if (can_steal)
steal_suitable_fallback(zone, page, start_migratetype);
/* Remove the page from the freelists */
area->nr_free--;
list_del(&page->lru);
rmv_page_order(page);
expand(zone, page, order, current_order, area,
start_migratetype);
/*
* The pcppage_migratetype may differ from pageblock's
* migratetype depending on the decisions in
* find_suitable_fallback(). This is OK as long as it does not
* differ for MIGRATE_CMA pageblocks. Those can be used as
* fallback only via special __rmqueue_cma_fallback() function
*/
set_pcppage_migratetype(page, start_migratetype);
trace_mm_page_alloc_extfrag(page, order, current_order,
start_migratetype, fallback_mt);
return page;
}
return NULL;
}
代码分析:
为了尽可能少的引入碎片,偷取的时候会尽量偷取大的空闲内存,所以从order最大的free_area尝试偷取;偷取的具体实现由steal_suitable_fallback来完成,偷过来之后正常分配即可。
steal_suitable_fallback会将整个pageblock中一起偷取,该pageblock中还没有分配的memory直接偷过来,并修改该pageblock的迁移类型。当属于该pageblock的page被释放时,会根据所属pageblock的类型决定回到哪种迁移类型链表上。