上一篇我们从内存结点,内存域的维度查看了Linux内存管理。本篇接着从内存分配的维度来看Linux内存管理。到现在为止,内核建立起了节点,域,页三级管理结构,并完成了页表映射。但是这一切都是在启动期内存管理器的基础上建立的,我们自建的内存管理器只完成了内存映射,还不具备内存分配的功能。为此接下来内核需要建立内存分配系统,Linux从内存分配效率,内存利用率的角度出发在前述管理结构上建立起hubby子系统来对接内存页分配,但是这还不够,考虑到系统中各种可能的对象尺寸和内存分配效率,Linux在hubby子系统的基础上构建起slab内存分配器来满足系统各种尺寸对象的分配需求,同时结合硬件缓存实现内存的高速分配。
一. 伙伴系统
Linux在构建起分页管理后,内存管理的责任由伙伴系统承担。每一个内存域都关联了一个struct zone,该结构中的free_area数组用于管理伙伴系统数据。free_area数组的索引即是内存页分配阶数,最大阶数MAX_ORDER=11,即2048页。
linux_kernel/include/linux/mmzone.h
struct zone {
struct free_area free_area[];
}
结构体free_area的free_list为对应阶数的内存页连表数组,该数组按内存页的迁移类型MIGRATE_TYPES来区分。nr_free统计空闲页数目。
linux_kernel/include/linux/mmzone.h
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
1.分配
linux_kernel/include/linux/gfp.h
static inline struct page *
__alloc_pages(gfp_t gfp_mask, unsigned int order, int preferred_nid)
{
return __alloc_pages_nodemask(gfp_mask, order, preferred_nid, NULL);
}
linux_kernel/mm/page_alloc.c
伙伴系统核心分配函数
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
struct page *page;
//尝试从空闲链表分配
page = get_page_from_freelist(alloc_mask, order, alloc_flags, &ac);
if (likely(page))
goto out;
//走慢分配
page = __alloc_pages_slowpath(alloc_mask, order, &ac);
out:
return page;
}
EXPORT_SYMBOL(__alloc_pages_nodemask);
linux_kernel/mm/page_alloc.c
遍历所有备用zone,尝试从空闲列表分配
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
......
for_next_zone_zonelist_nodemask(zone, z, ac->zonelist, ac->high_zoneidx,
ac->nodemask) {
......
page = rmqueue(ac->preferred_zoneref->zone, zone, order,
gfp_mask, alloc_flags, ac->migratetype);
......
}
}
return NULL;
}
linux_kernel/mm/page_alloc.c
按迁移类型遍历内存域
static inline
struct page *rmqueue(struct zone *preferred_zone,
struct zone *zone, unsigned int order,
gfp_t gfp_flags, unsigned int alloc_flags,
int migratetype)
{
page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);
return page;
}
linux_kernel/mm/page_alloc.c
找到合适的空闲页返回
static __always_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 = get_page_from_free_area(area, migratetype);
if (!page)
continue;
del_page_from_free_area(page, area);
expand(zone, page, order, current_order, area, migratetype);
set_pcppage_migratetype(page, migratetype);
return page;
}
return NULL;
}
2.回收
linux_kernel/mm/page_alloc.c
void __free_pages(struct page *page, unsigned int order)
{
if (put_page_testzero(page))
free_the_page(page, order);
}
EXPORT_SYMBOL(__free_pages);
linux_kernel/mm/page_alloc.c
如果是单页,不归还给伙伴系统,放到CPU缓存中
static inline void free_the_page(struct page *page, unsigned int order)
{
if (order == 0)
free_unref_page(page); //释放单页
else
__free_pages_ok(page, order);
}
linux_kernel/mm/page_alloc.c
计算内存页对应的内存域,迁移类型
static void __free_pages_ok(struct page *page, unsigned int order)
{
free_one_page(page_zone(page), page, pfn, order, migratetype);
}
linux_kernel/mm/page_alloc.c
static void free_one_p