gfp_mask转换成对应的zone和migratetype

1.node的转换

在分配内存时,都会带分配参数比如GPF_KERNEL等等,那么,一次内存分配从哪个zone分配了?

这里就必需把mask转换成zone,gfp_mask低4位用于表示分配的zone

static inline enum zone_type gfp_zone(gfp_t flags)
{
    enum zone_type z;
    int bit = (__force int) (flags & GFP_ZONEMASK);

    z = (GFP_ZONE_TABLE >> (bit * ZONES_SHIFT)) &
                     ((1 << ZONES_SHIFT) - 1);
    VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
    return z;
}

其中ZONES_SHIFT,表示系统中的zone个数不超过2的ZONE_SHIFT次方,比如zone个数为5,那么zone_SHIFT=3,用

3bit表示zone的index(0-5),如果只有三个zone,那么ZONES_SHIFT=2,因为2个bit,可以表示4个数.

GFP_ZONEMASK定义的地方如下:
#define GFP_ZONEMASK    (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE)

#ifdef CONFIG_HIGHMEM
#define OPT_ZONE_HIGHMEM ZONE_HIGHMEM
#else
#define OPT_ZONE_HIGHMEM ZONE_NORMAL
#endif

#ifdef CONFIG_ZONE_DMA
#define OPT_ZONE_DMA ZONE_DMA
#else
#define OPT_ZONE_DMA ZONE_NORMAL
#endif

#ifdef CONFIG_ZONE_DMA32
#define OPT_ZONE_DMA32 ZONE_DMA32
#else
#define OPT_ZONE_DMA32 ZONE_NORMAL
#endif

从其定义可以看出,如果没有定义HIGHMEM,则ZONE_HIGHMEM自动指向ZONE_NORMAL,其他类似。GFP_ZONE_TABLE的定义,其实是将各个ZONE的值放到对应的BIT位上,ZONE_NORMAL放到[0,ZONE_SHIFT)位,GFP_DMA放到[ZONE_SHIFT, 2*ZONE_SHIFT)位上,依此类推。

bit*ZONE_SHIFT即得到该ZONE所对应的BIT位,然后将GFP_ZONE_TABLE左移所得位数量,这时候得到的是从这个ZONE开始往上的所有BIT位,要将无关的ZONE清零,即只取低ZONE_SHIFT位,所以要&((1<<ZONE_SHITF) -1)

2.node的选择

  在进行内存分配时,需要选择从哪个node分配内存,或者当前node内存不足时,又应该从哪个node分配了?

通过gfp_zone(gfp_mask),确认一个high zoneidx(最高的zone index),分配内存的优先级从high zoneidx-> low zone index

ZONE_MOVEBLE -> ZONE_NORMAL->ZONE_DMA,也就是ZONE_MOVEBLE不足时,可以从NORMAL和DMA Zone依次分配.列如GFP_KERNEL,优先从ZONE_NORMAL,不足时从ZONE_DMA分配,而如果标识GFP_DMA,那么只能从ZONE_DMA区分配,因为DMA ZONE的index=0,为最低.

选择zone的代码:

#define for_each_zone_zonelist_nodemask(zone, z, zlist, highidx, nodemask) \
    for (z = first_zones_zonelist(zlist, highidx, nodemask, &zone);    \
        zone;                            \
        z = next_zones_zonelist(++z, highidx, nodemask),    \
            zone = zonelist_zone(z))   

static inline struct zoneref *first_zones_zonelist(struct zonelist *zonelist,
                    enum zone_type highest_zoneidx,
                    nodemask_t *nodes,
                    struct zone **zone)
{
    struct zoneref *z = next_zones_zonelist(zonelist->_zonerefs,
                            highest_zoneidx, nodes);
    *zone = zonelist_zone(z);
    return z;
}

struct zoneref *next_zones_zonelist(struct zoneref *z,
                    enum zone_type highest_zoneidx,    nodemask_t *nodes)
{    
    if (likely(nodes == NULL))
       while (zonelist_zone_idx(z) > highest_zoneidx)//找到第一个合适的zone idx
            z++;
    return z;
}

 

 

3. migratetype的转换

static inline int gfpflags_to_migratetype(const gfp_t gfp_flags)
{
    VM_WARN_ON((gfp_flags & GFP_MOVABLE_MASK) == GFP_MOVABLE_MASK);
    BUILD_BUG_ON((1UL << GFP_MOVABLE_SHIFT) != ___GFP_MOVABLE);
    BUILD_BUG_ON((___GFP_MOVABLE >> GFP_MOVABLE_SHIFT) != MIGRATE_MOVABLE);

    if (unlikely(page_group_by_mobility_disabled))
        return MIGRATE_UNMOVABLE;

    /* Group based on mobility */
    return (gfp_flags & GFP_MOVABLE_MASK) >> GFP_MOVABLE_SHIFT;
}

GFP_MOVABLE_MASK=0x18,GFP_MOVABLE_SHIFT=3,

可以看出,计算migrate type很简单,(gfp_flags & GFP_MOVABLE_MASK) >> GFP_MOVABLE_SHIFT

可以看出gfp_flags的bit3-bit4表示migrate类型

enum {
    MIGRATE_UNMOVABLE,
    MIGRATE_MOVABLE,
    MIGRATE_RECLAIMABLE,
    MIGRATE_PCPTYPES,    /* the number of types on the pcp lists */
    MIGRATE_HIGHATOMIC = MIGRATE_PCPTYPES,
#ifdef CONFIG_CMA
    MIGRATE_CMA,
#endif
#ifdef CONFIG_MEMORY_ISOLATION
    MIGRATE_ISOLATE,    /* can't allocate from here */
#endif
    MIGRATE_TYPES
};

### GFP_KERNEL | GFP_DMA 与 GFP_KERNEL | GFP_ATOMIC 的选择差异及适用场景 #### 1. 标志组合的意义 - **GFP_KERNEL | GFP_DMA**:这种组合表示在可以睡眠的上下文中分配内存,并且要求分配的内存位于 DMA 可访问的区域。它结合了 `GFP_KERNEL` 的阻塞特性以及 `GFP_DMA` 的低地址物理内存限制[^2]。 - **GFP_KERNEL | GFP_ATOMIC**:这种组合并不常见,因为 `GFP_KERNEL` `GFP_ATOMIC` 的设计初衷是互斥的。`GFP_KERNEL` 允许阻塞,而 `GFP_ATOMIC` 不允许阻塞。如果尝试使用此组合,可能会导致行为不确定或编译错误[^3]。 #### 2. 使用场景 - **GFP_KERNEL | GFP_DMA**: - 适用于需要为硬件设备分配低地址物理内存的场景,例如某些旧的 PCI 设备或嵌入式系统中的硬件组件。 - 运行在可以睡眠的上下文中,例如进程上下文或非中断处理程序中。 - 示例代码: ```c void dma_allocation_example() { void *ptr = kmalloc(1024, GFP_KERNEL | GFP_DMA); if (!ptr) { printk(KERN_ERR "DMA memory allocation failed\n"); return; } // 使用分配的内存 memset(ptr, 0, 1024); kfree(ptr); } ``` 上述代码展示了如何在可以睡眠的上下文中为 DMA 硬件分配内存[^2]。 - **GFP_KERNEL | GFP_ATOMIC**: - 此组合不推荐使用,因为其语义冲突。如果确实需要在非阻塞上下文中分配 DMA 内存,应使用 `GFP_DMA | GFP_ATOMIC` 而非 `GFP_KERNEL | GFP_ATOMIC`。 #### 3. 行为差异 - **GFP_KERNEL | GFP_DMA**: - 分配过程可能阻塞,等待内存释放。 - 确保分配的内存位于特定的内存区域(如 ZONE_DMA 或 ZONE_DMA32),以满足硬件的 DMA 要求。 - 如果内存不足,内核会尝试回收内存并重新分配[^2]。 - **GFP_KERNEL | GFP_ATOMIC**: - 此组合的行为未明确定义,可能导致编译错误或运行时问题。通常不应使用此组合,而是根据实际需求选择 `GFP_DMA | GFP_ATOMIC` 或单独的 `GFP_KERNEL`。 #### 4. 注意事项 - 在中断、软中断或持有自旋锁的代码区中使用 `GFP_KERNEL` 会导致内核告警(如 scheduling while atomic)或死锁。因此,在这些上下文中必须使用 `GFP_ATOMIC` 或其他非阻塞标志[^1]。 - 预分配内存池是一种有效的替代方案,可以在初始化阶段提前分配内存,避免在关键路径中动态分配[^1]。 - 启用 `CONFIG_DEBUG_VM` 可检测非法内存分配行为,通过 `dump_stack()` 查看调用路径,定位错误上下文中的分配操作。 ### 示例代码 以下是一个简单的示例,展示如何正确选择标志组合: ```c #include <linux/slab.h> #include <linux/kernel.h> void example_allocation(bool dma_required, bool atomic_context) { void *ptr; if (dma_required && atomic_context) { // 需要 DMA 内存且在原子上下文中 ptr = kmalloc(1024, GFP_DMA | GFP_ATOMIC); } else if (dma_required) { // 需要 DMA 内存且在非原子上下文中 ptr = kmalloc(1024, GFP_DMA | GFP_KERNEL); } else if (atomic_context) { // 不需要 DMA 内存但在原子上下文中 ptr = kmalloc(1024, GFP_ATOMIC); } else { // 不需要 DMA 内存且在非原子上下文中 ptr = kmalloc(1024, GFP_KERNEL); } if (!ptr) { printk(KERN_ERR "Memory allocation failed\n"); return; } // 使用分配的内存 memset(ptr, 0, 1024); // 释放内存 kfree(ptr); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值