#Linux 内存管理# 在页面分配器中,如何从分配掩码(gfp_mask)中确定可以从哪些zone中分配内存?

时间:2025-04-18 18:06:38

在Linux内核中,页面分配器通过 gfp_mask(分配掩码) 确定可以从哪些内存区域(zone)分配内存。这一过程涉及对zone的优先级排序和条件检查,具体逻辑如下:

 

一、内存区域(zone)的分类

 

Linux内核将物理内存划分为多个zone,常见的有:

 

ZONE_DMA

供DMA设备使用的物理内存(通常为前16MB)。

 

ZONE_DMA32

供32位DMA设备使用的内存(通常为前4GB)。

 

ZONE_NORMAL

内核可直接映射的普通内存(64位系统中通常覆盖全部物理内存)。

 

ZONE_HIGHMEM

仅在32位系统中存在,用于管理超过内核直接映射范围的内存(如物理内存 > 896MB)。

 

二、从gfp_mask解析zone优先级

 

分配器通过gfp_mask中的标志位确定允许的zone列表及其优先级顺序:

 

1. 直接指定目标zone

__GFP_DMA

强制从ZONE_DMA分配。

 

__GFP_DMA32

强制从ZONE_DMA32分配。

 

__GFP_HIGHMEM

允许从ZONE_HIGHMEM分配(仅在32位系统有效)。

 

2. 隐式zone选择(默认逻辑)

 

若未明确指定zone标志,分配器按以下优先级选择zone:

 

ZONE_NORMAL → ZONE_DMA32 → ZONE_DMA

 

适用于大多数内核分配请求(如GFP_KERNEL)。

ZONE_HIGHMEM → ZONE_NORMAL → ...

 

仅当用户请求包含__GFP_HIGHMEM且系统为32位时触发。

 

三、分配流程

 

1.解析gfp_mask

 

根据标志位生成允许的zone列表(例如__GFP_DMA | __GFP_NORMAL表示允许ZONE_DMA和ZONE_NORMAL)。

 

2.按优先级遍历zone

 

从最高优先级(如ZONE_DMA)到最低优先级(如ZONE_HIGHMEM)依次检查每个zone。

 

3.检查zone的水位线(watermark)

 

仅当zone的空闲页数 ≥ 最低水位线(min) 时,才允许分配。

 

若gfp_mask包含__GFP_HIGH或__GFP_ATOMIC,允许使用紧急保留内存(低于min但高于min+reserve)。

 

4.选择第一个满足条件的zone

 

一旦找到符合条件的zone,立即从中分配内存。

 

四、示例场景

 

场景1:DMA内存分配

 

gfp_mask = __GFP_DMA | GFP_KERNEL;

 

允许的zone:仅ZONE_DMA。

 

检查逻辑:若ZONE_DMA的空闲页 ≥ min,则分配;否则失败。

 

场景2:默认内核分配

 

gfp_mask = GFP_KERNEL; // 不指定zone标志

 

允许的zone:ZONE_NORMAL → ZONE_DMA32 → ZONE_DMA(按优先级)。

 

检查逻辑:依次检查每个zone的水位线,选择第一个满足条件的zone。

 

场景3:GFP_ATOMIC

 

分配过程不可睡眠,优先从预留内存或ZONE_NORMAL快速分配,避免触发复杂回收机制。

 

场景4:GFP_HIGHUSER_MOVABLE

 

包含__GFP_HIGHMEM标志,优先从ZONE_HIGHMEM分配用户空间可移动的页面。

 

五、核心函数与代码路径

 

1.get_page_from_freelist()

 

遍历zone列表,检查水位线并尝试分配。

 

2.gfp_zone(gfp_mask)

 

将gfp_mask转换为zone类型(如ZONE_DMA)。

 

3.zone_watermark_ok()

 

检查指定zone的水位线是否满足分配条件。

 

六、性能优化与注意事项

 

1.避免过度分散分配

 

频繁指定__GFP_DMA可能导致ZONE_NORMAL碎片化。

 

2.NUMA架构扩展

 

在多节点(NUMA)系统中,gfp_mask可能包含__GFP_THISNODE,强制从本地节点分配。

 

3.调试支持

 

启用CONFIG_DEBUG_VM时,分配器会严格检查gfp_mask与zone的合法性。

 

 

七、实现代码逻辑

在源码中,zone选择的核心逻辑如下(以Linux 5.x为例):

 

// include/linux/gfp.h

#define gfp_zone(mask) ((mask) & (__GFP_DMA | __GFP_HIGHMEM | __GFP_DMA32))

 

// mm/page_alloc.c

struct page *alloc_pages(gfp_t gfp_mask, unsigned int order) {

    enum zone_type highest_zoneidx = gfp_zone(gfp_mask);

    // 遍历允许的zone,尝试分配内存

    for_each_zone_zoneidx(zone, highest_zoneidx) {

        page = __alloc_pages_cpuset_fallback(gfp_mask, order, zone);

        if (page) return page;

    }

    return NULL;

}

 

总结:gfp_mask通过标志位隐式或显式定义允许的zone范围,分配器按优先级和内存水位线选择第一个可用的zone。这一机制在保证硬件兼容性(如DMA)的同时,优化了内存分配效率。