在linux内核的migratetype中,有MIGRATE_ISOLATE这样一个内存迁移类型,注释里写着can’t allocate from here,这个是怎么做到的呢?他又有什么作用呢?
本文分析基于linux4.19.195
可以看到内核里,是函数set_migratetype_isolate将一个内存块设置成了MIGRATE_ISOLATE这个类型。
static int set_migratetype_isolate(struct page *page, int migratetype,
bool skip_hwpoisoned_pages)
{
struct zone *zone;
unsigned long flags, pfn;
struct memory_isolate_notify arg;
int notifier_ret;
int ret = -EBUSY;
zone = page_zone(page);
spin_lock_irqsave(&zone->lock, flags);
/*
* We assume the caller intended to SET migrate type to isolate.
* If it is already set, then someone else must have raced and
* set it before us. Return -EBUSY
*/
if (is_migrate_isolate_page(page))
goto out;
pfn = page_to_pfn(page);
arg.start_pfn = pfn;
arg.nr_pages = pageblock_nr_pages;
arg.pages_found = 0;
/*
* It may be possible to isolate a pageblock even if the
* migratetype is not MIGRATE_MOVABLE. The memory isolation
* notifier chain is used by balloon drivers to return the
* number of pages in a range that are held by the balloon
* driver to shrink memory. If all the pages are accounted for
* by balloons, are free, or on the LRU, isolation can continue.
* Later, for example, when memory hotplug notifier runs, these
* pages reported as "can be isolated" should be isolated(freed)
* by the balloon driver through the memory notifier chain.
*/
notifier_ret = memory_isolate_notify(MEM_ISOLATE_COUNT, &arg);
notifier_ret = notifier_to_errno(notifier_ret);
if (notifier_ret)
goto out;
/*
* FIXME: Now, memory hotplug doesn't call shrink_slab() by itself.
* We just check MOVABLE pages.
*/
if (!has_unmovable_pages(zone, page, arg.pages_found, migratetype,
skip_hwpoisoned_pages))
ret = 0;
/*
* immobile means "not-on-lru" pages. If immobile is larger than
* removable-by-driver pages reported by notifier, we'll fail.
*/
out:
if (!ret) {
unsigned long nr_pages;
int mt = get_pageblock_migratetype(page);
set_pageblock_migratetype(page, MIGRATE_ISOLATE);
zone->nr_isolate_pageblock++;
nr_pages = move_freepages_block(zone, page, MIGRATE_ISOLATE,
NULL);
__mod_zone_freepage_state(zone, -nr_pages, mt);
}
spin_unlock_irqrestore(&zone->lock, flags);
if (!ret)
drain_all_pages(zone);
return ret;
}
函数首先判断这个内存块是不是MIGRATE_ISOLATE类型的话,如果是的话,直接退出即可。
然后,调用has_unmovable_pages,查看是否有unmovable的页,如果有的话,那可能无法通过migration等方法把这个页隔离出来,从而实现“can’t allocate from here”的效果。
如果前面检查都通过了的话,那么就会调用move_freepages_block()函数,把这个内存块在buddy中的所有空闲页,都挪到zone->free_area[order].free_list[MIGRATE_ISOLATE]链表上去。
那么,这个链表上的page,是不会被buddy分配出去的吗?
答案是yes,具体怎么做到的,可以看函数prepare_alloc_pages()的实现。
static inline bool prepare_alloc_pages(gfp_t gfp_mask, unsigned int order,
int preferred_nid, nodemask_t *nodemask,
struct alloc_context *ac, gfp_t *alloc_mask,
unsigned int *alloc_flags)
{
ac->high_zoneidx = gfp_zone(gfp_mask);
ac->zonelist = node_zonelist(preferred_nid, gfp_mask);
ac->nodemask = nodemask;
ac->migratetype = gfpflags_to_migratetype(gfp_mask);
if (cpusets_enabled()) {
*alloc_mask |= __GFP_HARDWALL;
if (!ac->nodemask)
ac->nodemask = &cpuset_current_mems_allowed; //cpuset.mem相关
else
*alloc_flags |= ALLOC_CPUSET;
}
fs_reclaim_acquire(gfp_mask);
fs_reclaim_release(gfp_mask);
might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM);
if (should_fail_alloc_page(gfp_mask, order))
return false;
if (IS_ENABLED(CONFIG_CMA) && ac->migratetype == MIGRATE_MOVABLE)
*alloc_flags |= ALLOC_CMA;
return true;
}
结构体alloc_context中的migratetype决定了初始分配的migratetype,这个成员是由函数gfpflags_to_migratetype()赋值的,从函数的实现可以很清晰的看到,只有可能返回__GFP_RECLAIMABLE、__GFP_MOVABLE或者0,0也就代表了MIGRATE_UNMOVABLE,对,没有MIGRATE_ISOLATE。通过函数gfpflags_to_migratetype()的实现,我们也能清楚的看到,enum migratetype里迁移类型的顺序,是和__GFP_RECLAIMABLE、__GFP_MOVABLE的值对应的上的。
那么,如果初始分配的migratetype无法分配出内存,fallback的话会怎么样呢?
__rmqueue_fallback()函数的实现给了答案,该函数会调用find_suitable_fallback函数做fallback migrate type的赋值。函数很简单,就是查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
};
无需做任何解释,显然是无法从MIGRATE_ISOLATE这个类型里分配出页了。
那MIGRATE_ISOLATE的作用是什么?我们看看谁会用set_migratetype_isolate()这个函数。
挑个最常见的例子,alloc_contig_range()这个函数就会调用set_migratetype_isolate(),那么系统会在什么时候调用alloc_contig_range()呢?一个很常见的场景,就是我们往/sys/kernel/mm/hugepages/hugepages-xxx/nr_hugepages写入数值申请预留大页的时候。alloc_contig_range()这个函数的作用是:tries to allocate given range of pages。那他怎么实现的呢?
int alloc_contig_range(unsigned long start, unsigned long end,
unsigned migratetype, gfp_t gfp_mask)
{
****
start_isolate_page_range()
__alloc_contig_migrate_range()
****
}
这个函数要实现的事分配一段连续的物理内存,但是系统运行起来后,内存碎片的很常见的,那怎么办呢?函数alloc_contig_range()首先调用start_isolate_page_range,将相关内存块配置成MIGRATE_ISOLATE,保证待申请范围里的内存,不会被buddy分配出去。然后,通过__alloc_contig_migrate_range函数,将那些待申请范围里的已经被分配出去的内存,尽可能的通过迁移(页迁移可以参考博客)的方法回收回来。可以看到,如果没有MIGRATE_ISOLATE,那么在__alloc_contig_migrate_range做迁移的过程中,还在buddy中的内存如果被分配出去了,那__alloc_contig_migrate_range()函数做的事情也是无用功。