本文按照Linux内核深度解析为主线,做的一些总结和梳理

1.关于内存分分配标志

参考gfp.h

1.1一些掩码

/* Plain integer GFP bitmasks. Do not use this directly. */
#define ___GFP_DMA 0x01u
#define ___GFP_HIGHMEM 0x02u
#define ___GFP_DMA32 0x04u
#define ___GFP_MOVABLE 0x08u
#define ___GFP_RECLAIMABLE 0x10u
#define ___GFP_HIGH 0x20u
#define ___GFP_IO 0x40u
#define ___GFP_FS 0x80u
#define ___GFP_ZERO 0x100u
#define ___GFP_ATOMIC 0x200u
#define ___GFP_DIRECT_RECLAIM 0x400u
#define ___GFP_KSWAPD_RECLAIM 0x800u
#define ___GFP_WRITE 0x1000u
#define ___GFP_NOWARN 0x2000u
#define ___GFP_RETRY_MAYFAIL 0x4000u
#define ___GFP_NOFAIL 0x8000u
#define ___GFP_NORETRY 0x10000u
#define ___GFP_MEMALLOC 0x20000u
#define ___GFP_COMP 0x40000u
#define ___GFP_NOMEMALLOC 0x80000u
#define ___GFP_HARDWALL 0x100000u
#define ___GFP_THISNODE 0x200000u
#define ___GFP_ACCOUNT 0x400000u
#define ___GFP_ZEROTAGS 0x800000u
#define ___GFP_SKIP_KASAN_POISON 0x1000000u
#ifdef CONFIG_LOCKDEP
#define ___GFP_NOLOCKDEP 0x2000000u
#else
#define ___GFP_NOLOCKDEP 0
#endif

1.2掩码分类

1. 内存分配区域标志(Zone Modifiers)

这些标志定义了内存分配的物理地址区域:

  • __GFP_DMA: 分配内存时,只使用低 16MB 的内存区域,这个标志主要用于设备 DMA 需要低地址的情况。
  • __GFP_DMA32: 分配内存时,只使用低 4GB 的内存,适用于需要 32 位地址设备的情况。
  • __GFP_HIGHMEM: 分配高端内存(用于 32 位系统的扩展内存)。
  • __GFP_MOVABLE: 可移动的页面,用于内存压缩和回收。

组合标志:

  • GFP_ZONEMASK: 用于组合所有内存区域相关的标志。

2. 页面移动性和位置提示标志

这些标志控制页面是否可以在内存压缩或回收时移动:

  • __GFP_RECLAIMABLE: 这些页面可以通过内核的回收机制回收。
  • __GFP_WRITE: 申请者可能会对页面进行写操作。
  • __GFP_HARDWALL: 强制执行内存分配的 cpuset 约束。
  • __GFP_THISNODE: 强制在指定的 NUMA 节点上分配内存,禁止使用其他节点。
  • __GFP_ACCOUNT: 分配的内存将被记账到 kmemcg(内核内存控制组)。

3. 水印修改标志

这些标志控制对系统应急保留内存的访问:

  • __GFP_HIGH: 高优先级分配请求,用于系统的关键任务。
  • __GFP_ATOMIC: 分配过程不能阻塞或睡眠,常用于中断处理程序。
  • __GFP_MEMALLOC: 允许访问系统的所有内存,包括应急保留区。
  • __GFP_NOMEMALLOC: 明确禁止访问应急保留区,即使设置了 __GFP_MEMALLOC

4. 回收修改标志

这些标志控制内核如何处理内存回收:

  • __GFP_IO: 分配内存时允许进行物理 I/O 操作。
  • __GFP_FS: 允许调用底层文件系统来分配内存。
  • __GFP_DIRECT_RECLAIM: 允许申请者进入直接内存回收状态。
  • __GFP_KSWAPD_RECLAIM: 在达到低水位线时唤醒 kswapd 进行内存回收。
  • __GFP_NORETRY: 避免执行耗时的回收操作,快速返回。
  • __GFP_RETRY_MAYFAIL: 允许在内存压力下重试,但分配失败是可能的。
  • __GFP_NOFAIL: 内存分配必须成功,禁止失败。

5. 操作修改标志

这些标志提供了与内存操作相关的特殊功能:

  • __GFP_NOWARN: 抑制内存分配失败的警告信息。
  • __GFP_COMP: 用于复合页面(多个物理页面的组合)。
  • __GFP_ZERO: 返回一个已清零的页面。
  • __GFP_ZEROTAGS: 返回已清零的内存标签。

6. 常用 GFP 标志组合

  • GFP_ATOMIC: 不允许睡眠,必须分配成功,通常用于中断上下文。
  • GFP_KERNEL: 用于内核内部的典型分配,允许进入回收状态。
  • GFP_NOWAIT: 不允许分配过程中进入回收或 I/O。
  • GFP_NOIO: 允许回收但不进行任何 I/O 操作。
  • GFP_NOFS: 允许回收但不进行文件系统的调用。
  • GFP_USER: 用于用户空间分配,通常是直接可访问的内存。

1.3 区域选择

共有16种可能性。

有个table,将可能被选的分配到16个区域。

通过page的flag(取低四位),将table右移动flag*区域长度,取低四位(区域长度),则可得到对应应该分配到哪个区域。

当然也有无效区域,因此需要VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);

/*
* GFP_ZONE_TABLE is a word size bitstring that is used for looking up the
* zone to use given the lowest 4 bits of gfp_t. Entries are GFP_ZONES_SHIFT
* bits long and there are 16 of them to cover all possible combinations of
* __GFP_DMA, __GFP_DMA32, __GFP_MOVABLE and __GFP_HIGHMEM.
*
* The zone fallback order is MOVABLE=>HIGHMEM=>NORMAL=>DMA32=>DMA.
* But GFP_MOVABLE is not only a zone specifier but also an allocation
* policy. Therefore __GFP_MOVABLE plus another zone selector is valid.
* Only 1 bit of the lowest 3 bits (DMA,DMA32,HIGHMEM) can be set to "1".
*
* bit result
* =================
* 0x0 => NORMAL
* 0x1 => DMA or NORMAL
* 0x2 => HIGHMEM or NORMAL
* 0x3 => BAD (DMA+HIGHMEM)
* 0x4 => DMA32 or NORMAL
* 0x5 => BAD (DMA+DMA32)
* 0x6 => BAD (HIGHMEM+DMA32)
* 0x7 => BAD (HIGHMEM+DMA32+DMA)
* 0x8 => NORMAL (MOVABLE+0)
* 0x9 => DMA or NORMAL (MOVABLE+DMA)
* 0xa => MOVABLE (Movable is valid only if HIGHMEM is set too)
* 0xb => BAD (MOVABLE+HIGHMEM+DMA)
* 0xc => DMA32 or NORMAL (MOVABLE+DMA32)
* 0xd => BAD (MOVABLE+DMA32+DMA)
* 0xe => BAD (MOVABLE+DMA32+HIGHMEM)
* 0xf => BAD (MOVABLE+DMA32+HIGHMEM+DMA)
*
* GFP_ZONES_SHIFT must be <= 2 on 32 bit platforms.
*/

#if defined(CONFIG_ZONE_DEVICE) && (MAX_NR_ZONES-1) <= 4
/* ZONE_DEVICE is not a valid GFP zone specifier */
#define GFP_ZONES_SHIFT 2
#else
#define GFP_ZONES_SHIFT ZONES_SHIFT
#endif

#if 16 * GFP_ZONES_SHIFT > BITS_PER_LONG
#error GFP_ZONES_SHIFT too large to create GFP_ZONE_TABLE integer
#endif
/*将每个标志位映射到每一位,到时候找回需要右移动回去*/
#define GFP_ZONE_TABLE ( \
(ZONE_NORMAL << 0 * GFP_ZONES_SHIFT) \
| (OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT) \
| (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT) \
| (OPT_ZONE_DMA32 << ___GFP_DMA32 * GFP_ZONES_SHIFT) \
| (ZONE_NORMAL << ___GFP_MOVABLE * GFP_ZONES_SHIFT) \
| (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * GFP_ZONES_SHIFT) \
| (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * GFP_ZONES_SHIFT)\
| (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * GFP_ZONES_SHIFT)\
)

/*
* GFP_ZONE_BAD is a bitmap for all combinations of __GFP_DMA, __GFP_DMA32
* __GFP_HIGHMEM and __GFP_MOVABLE that are not permitted. One flag per
* entry starting with bit 0. Bit is set if the combination is not
* allowed.
*/
#define GFP_ZONE_BAD ( \
1 << (___GFP_DMA | ___GFP_HIGHMEM) \
| 1 << (___GFP_DMA | ___GFP_DMA32) \
| 1 << (___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_DMA | ___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_MOVABLE | ___GFP_HIGHMEM | ___GFP_DMA) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_HIGHMEM) \
| 1 << (___GFP_MOVABLE | ___GFP_DMA32 | ___GFP_DMA | ___GFP_HIGHMEM) \
)

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 * GFP_ZONES_SHIFT)) &
((1 << GFP_ZONES_SHIFT) - 1);
VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1);
return z;
}