An Overview of the Linux Memory Management Framework
发表于 2025-08-26
Linux 内存管理框架
1. 基础框架图
2. 核心结构及其关系
2.1 pglist_data
struct pglist_data 是 Linux 内核中用于管理 每个 NUMA 节点物理内存 的核心结构体,简称 “节点数据结构”
非NUMA架构的机器只有一个 pglist_data结构。
typedef struct pglist_data {
/*
* node_zones 包含**当前节点**的所有内存区域(zone)。
* 并不是所有的 zone 都一定被初始化或有内存,但这个列表是完整的。
* 它不仅被当前节点的 node_zonelists 引用,也可能被其它节点的 node_zonelists 引用。
*/
struct zone node_zones[MAX_NR_ZONES];
/*
* node_zonelists 包含对所有节点中所有内存区域(zone)的引用。
* 通常,最前面的 zone 是对本节点的 node_zones 的引用。
*/
struct zonelist node_zonelists[MAX_ZONELISTS];
//部分省略
} pg_data_t;
node_zones 数组包含了不同的内存区域(zone)
2.2 zone
用于表示 内存管理中的一个“Zone”(区域) 的核心结构,定义在 include/linux/mmzone.h 中。它是 物理内存分布的一个逻辑划分单位。
struct zone {
const char *name;
struct free_area free_area[MAX_ORDER];
}
free_area 数组保存了不同order(以page为基准,order 0表示1个page; order 1表示2个page, order 2表示4个page,以此类推)下的可用内存。
2.3 free_area
用于表示某一阶(order)的空闲页块信息。
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
free_list 数组表示各迁移类型(MIGRATE_UNMOVABLE, MIGRATE_MOVABLE,MIGRATE_RECLAIMABLE等)的空闲页链表,用于 buddy 分配与合并。 nr_free 表示当前阶下的空闲块总数,用于判断是否能满足分配请求。
2.4 page
page是linux 内存管理的核心结构。在linux中内存会被分成一个一个页,而page就是用来描述对应的页内存。
它在没有被使用之前,是通过 buddy_list 链接在 free_area的free_list 链表上的。
struct page {
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
/*
* Five words (20/40 bytes) are available in this union.
* WARNING: bit 0 of the first word is used for PageTail(). That
* means the other users of this union MUST NOT use the bit to
* avoid collision and false-positive PageTail().
*/
union {
struct { /* Page cache and anonymous pages */
/**
* @lru: Pageout list, eg. active_list protected by
* lruvec->lru_lock. Sometimes used as a generic list
* by the page owner.
*/
union {
struct list_head lru;
struct {
void *__filler;
unsigned int mlock_count;
};
/* Or, free page */
struct list_head buddy_list; //通过这个链接到free_list链表。
struct list_head pcp_list;
};
};
};
};
//部分省略
} _struct_page_alignment;
3. 内存操作
3.1 alloc_pages
alloc_pages申请内存的基本执行路径如下(快速路径):
alloc_pages --> __alloc_pages --> get_page_from_freelist --> rmqueue --> rmqueue_buddy --> __rmqueue --> __rmqueue_smallest --> get_page_from_free_area
路径上各个函数的简单描述如下:
//显示尝试快速路径,如果找不到合适内存,则尝试慢速路径。
struct page *__alloc_pages(gfp_t gfp, unsigned int order, int preferred_nid,
nodemask_t *nodemask)
{
//部分省略
page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac); //快速路径
if (likely(page))
goto out;
//部分省略
page = __alloc_pages_slowpath(alloc_gfp, order, &ac); //慢速路径
//部分省略
}
//通过各种条件找到特定的zone, 尝试在zone里去分配内存
struct page *get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
}
//主要调用rmqueue_buddy函数
struct page *rmqueue(struct zone *preferred_zone, struct zone *zone, unsigned int order,
gfp_t gfp_flags, unsigned int alloc_flags, int migratetype)
{
}
//主要调用 __rmqueue 申请内存。同时兼顾其他特定条件下的申请。如果申请的内存检查失败,则会重新尝试申请。
struct page *rmqueue_buddy(struct zone *preferred_zone, struct zone *zone, unsigned int order, unsigned int alloc_flags, int migratetype)
{
struct page *page;
unsigned long flags;
do {
page = NULL;
spin_lock_irqsave(&zone->lock, flags);
/*
* order-0 request can reach here when the pcplist is skipped
* due to non-CMA allocation context. HIGHATOMIC area is
* reserved for high-order atomic allocation, so order-0
* request should skip it.
*/
if (order > 0 && alloc_flags & ALLOC_HARDER)
page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC);
if (!page) {
page = __rmqueue(zone, order, migratetype, alloc_flags);
if (!page) {
spin_unlock_irqrestore(&zone->lock, flags);
return NULL;
}
}
__mod_zone_freepage_state(zone, -(1 << order),
get_pcppage_migratetype(page));
spin_unlock_irqrestore(&zone->lock, flags);
} while (check_new_pages(page, order));
__count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order);
zone_statistics(preferred_zone, zone, 1);
return page;
}
//调用__rmqueue_smallest分配内存。如果失败,则尝试使用其他migratetype来分配。
struct page *__rmqueue(struct zone *zone, unsigned int order, int migratetype, unsigned int alloc_flags)
{
struct page *page;
if (IS_ENABLED(CONFIG_CMA)) {
/*
* Balance movable allocations between regular and CMA areas by
* allocating from CMA when over half of the zone's free memory
* is in the CMA area.
*/
if (alloc_flags & ALLOC_CMA &&
zone_page_state(zone, NR_FREE_CMA_PAGES) >
zone_page_state(zone, NR_FREE_PAGES) / 2) {
page = __rmqueue_cma_fallback(zone, order);
if (page)
return page;
}
}
retry:
page = __rmqueue_smallest(zone, order, migratetype);
if (unlikely(!page)) {
if (alloc_flags & ALLOC_CMA)
page = __rmqueue_cma_fallback(zone, order);
if (!page && __rmqueue_fallback(zone, order, migratetype,
alloc_flags))
goto retry;
}
return page;
}
//从当前order开始如果获取不到内存,则增加order继续尝试。
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_list(page, zone, current_order);
expand(zone, page, order, current_order, migratetype);
set_pcppage_migratetype(page, migratetype);
trace_mm_page_alloc_zone_locked(page, order, migratetype,
pcp_allowed_order(order) &&
migratetype < MIGRATE_PCPTYPES);
return page;
}
return NULL;
}
//从area的migratetype对应的链表中取空闲内存。如果不存在则返回NULL
struct page *get_page_from_free_area(struct free_area *area,
int migratetype)
{
return list_first_entry_or_null(&area->free_list[migratetype],
struct page, lru);
}
3.2 free_pages
free_pages 释放内存的基本执行路径如下:
free_pages --> __free_pages --> free_the_page --> __free_pages_ok --> __free_one_page
__free_one_page 函数主要做两件事。
- 查找空闲buddy page,如果找到则合并成一个order更大的page,再继续向上查找。
- 设置page的order,添加到对应的空闲链表中。
/*
* 用于 buddy 系统分配器的释放函数。
*
* buddy 系统的概念是为各种“order”(阶)大小的内存块维护一个直接映射表
* (包含位值)。最低级别的表包含最小可分配单位(此处为页面)的映射,
* 每个更高级别描述的是其下一级中两个单元的配对,因此被称为“伙伴(buddy)”系统。
*
* 从高层来看,这里的操作就是将最低层的表项标记为可用,并在必要时向上传播
* 这种状态变更,同时执行一些需要配合虚拟内存系统其它部分的统计工作。
*
* 在每个阶(order)中,我们维护一个页面链表,该链表的元素是一段连续空闲页面
* 的起始页(长度为 1 << order),这些页面通过 PageBuddy 标记。
* 页面所属的阶数记录在 page_private(page) 字段中。
* 因此,在进行分配或释放操作时,可以推导出其对应 buddy 页面的状态。
* 也就是说,如果我们分配了一个较小的块,而两个伙伴页都空闲,
* 那么剩余区域必须被拆分为多个更小的块。
* 如果释放一个块,并且它的 buddy 也是空闲的,那么这将触发合并操作,
* 以构成一个更大的块。
*
* -- nyc
*/
void __free_one_page(struct page *page, unsigned long pfn, struct zone *zone, unsigned int order, int migratetype, fpi_t fpi_flags)
{
//部分省略
while (order < MAX_ORDER - 1) {
//部分省略
buddy = find_buddy_page_pfn(page, pfn, order, &buddy_pfn); //查找buddy page
if (!buddy)
goto done_merging;
//部分省略
/*
* Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
* merge with it and move up one order.
*/
if (page_is_guard(buddy))
clear_page_guard(zone, buddy, order, migratetype);
else
del_page_from_free_list(buddy, zone, order); //buddy page从当前list 移除
combined_pfn = buddy_pfn & pfn;
page = page + (combined_pfn - pfn); //新的page
pfn = combined_pfn; //新的pfn
order++; //新的order
}
done_merging:
set_buddy_order(page, order); //设置page order
if (fpi_flags & FPI_TO_TAIL)
to_tail = true;
else if (is_shuffle_order(order))
to_tail = shuffle_pick_tail();
else
to_tail = buddy_merge_likely(pfn, buddy_pfn, page, order);
if (to_tail)
add_to_free_list_tail(page, zone, order, migratetype); //添加到新list tail
else
add_to_free_list(page, zone, order, migratetype); //或 添加到新list
/* Notify page reporting subsystem of freed page */
if (!(fpi_flags & FPI_SKIP_REPORT_NOTIFY))
page_reporting_notify_free(order);
}
本文访问次数:... 次