内存管理-33-GFP内存分配标志
基于msm-5.4
一、GFP分配标志
注: 这些标志为1表示功能是启用的。
1. 最初的纯整数 GFP 位掩码(不要直接使用)
//include/linux/gfp.h #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 #ifdef CONFIG_LOCKDEP #define ___GFP_NOLOCKDEP 0x800000u #else #define ___GFP_NOLOCKDEP 0 #endif #define ___GFP_CMA 0x1000000u #ifdef CONFIG_LIMIT_MOVABLE_ZONE_ALLOC #define ___GFP_OFFLINABLE 0x2000000u #else #define ___GFP_OFFLINABLE 0 #endif
这是最初的纯整数 GFP 位掩码,请勿直接使用。若是要修改这里的掩码,需要和 trace/events/mmflags.h 和 tools/perf/builtin-kmem.c 保持一致。
2. 物理地址区域修饰符
#define __GFP_DMA ((__force gfp_t)___GFP_DMA) //bit1 #define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM) //bit2 #define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32) //bit4 #define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) //bit8 /* ZONE_MOVABLE allowed */ #define __GFP_CMA ((__force gfp_t)___GFP_CMA) //0x1000000u = bit24 #define __GFP_OFFLINABLE ((__force gfp_t)___GFP_OFFLINABLE) //bit25 或 0 #define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE) //0xf
3. 页面移动性和放置提示
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE) //0x10 #define __GFP_WRITE ((__force gfp_t)___GFP_WRITE) //0x1000u #define __GFP_HARDWALL ((__force gfp_t)___GFP_HARDWALL) //0x100000u #define __GFP_THISNODE ((__force gfp_t)___GFP_THISNODE) //0x200000u #define __GFP_ACCOUNT ((__force gfp_t)___GFP_ACCOUNT) //0x400000u
这些标志提供有关页面移动性的提示。具有类似移动性的页面放置在相同的页面块内,以尽量减少由于内存外部碎片化引起的问题。
各标志含义:
_GFP_MOVABLE: 也是区域修饰符, 表示页面可以在内存压缩期间通过页面迁移移动或可以回收。
__GFP_RECLAIMABLE: 用于指定 SLAB_RECLAIM_ACCOUNT 的 slab 分配,其页面可以通过shrinkers释放。
__GFP_WRITE: 表示调用者打算弄脏页面。在可能的情况下,这些页面将分布在本地zones之间,以避免所有脏页面都在一个区域中(公平区域分配策略)。
__GFP_HARDWALL: 强制执行 cpuset 内存分配策略。
__GFP_THISNODE: 强制从请求的节点满足分配,不进行任何回退或实施放置策略。
__GFP_ACCOUNT: 导致将分配记入 kmemcg。
4. 水位修饰符
#define __GFP_ATOMIC ((__force gfp_t)___GFP_ATOMIC) //0x200 #define __GFP_HIGH ((__force gfp_t)___GFP_HIGH) //0x20 #define __GFP_MEMALLOC ((__force gfp_t)___GFP_MEMALLOC) //0x20000 #define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC) //0x80000
水位修饰符控制对针对紧急情况而预留的内存的访问。
各标志含义:
__GFP_HIGH: 表示调用者具有高优先级,并且必须先授予请求,系统才能继续前进。例如,创建 IO 上下文来清理页面。
__GFP_ATOMIC: 表示调用者上下文无法执行回收或休眠,并且具有高优先级。用户通常是中断处理程序。这可以与 __GFP_HIGH 结合使用
__GFP_MEMALLOC: 允许访问所有内存。这应该只在调用者保证分配是为了允许在很短的时间内释放更多内存时使用,例如进程退出或交换。用户应该是 MM 或与 VM 密切协调(例如通过 NFS 交换)。
__GFP_NOMEMALLOC: 用于明确禁止访问紧急预留的那部分内存。如果两者都设置,则该标志优先于 __GFP_MEMALLOC 标志。
5. 回收修饰符
#define __GFP_IO ((__force gfp_t)___GFP_IO) //0x40 #define __GFP_FS ((__force gfp_t)___GFP_FS) //0x80u #define __GFP_DIRECT_RECLAIM ((__force gfp_t)___GFP_DIRECT_RECLAIM) //0x400u /* Caller can reclaim */ #define __GFP_KSWAPD_RECLAIM ((__force gfp_t)___GFP_KSWAPD_RECLAIM) //0x800u /* kswapd can wake */ #define __GFP_RECLAIM ((__force gfp_t)(___GFP_DIRECT_RECLAIM|___GFP_KSWAPD_RECLAIM)) //0x400|0x800=0xc00u #define __GFP_RETRY_MAYFAIL ((__force gfp_t)___GFP_RETRY_MAYFAIL) //0x4000u #define __GFP_NOFAIL ((__force gfp_t)___GFP_NOFAIL) //0x8000u #define __GFP_NORETRY ((__force gfp_t)___GFP_NORETRY) //0x10000u
各标志含义:
__GFP_IO: 可以启动物理 IO。
__GFP_FS: 可以向下调用到低级 FS。清除该标志可避免分配器递归到可能已持有锁的文件系统。
__GFP_DIRECT_RECLAIM: 表示调用者可以进入直接内存回收(direct reclaim)。当有后备选项可用时,可以清除此标志以避免不必要的延迟。
__GFP_KSWAPD_RECLAIM: 表示调用者希望在达到低水位时唤醒 kswapd,并让其回收页面,直到达到高水位。当有后备选项可用且回收可能会破坏系统时,调用者可能希望清除此标志。典型示例是 THP(Transparent HugePages) 分配,其中后备成本低廉,但回收/压缩可能会导致间接卡住。
__GFP_RECLAIM: 是允许/禁止直接内存回收 和 kswapd回收的简写。
默认分配行为取决于请求分配的大小。我们有一个所谓的昂贵分配的概念, 即 order > PAGE_ALLOC_COSTLY_ORDER(3)。!costly 分配太重要而不能失败,因此默认情况下它们隐式地不会失败(有一些例外,例如, OOM受害者(OOM victims)可能会失败,因此调用者仍然必须检查失败),而昂贵的请求试图不造成破坏,甚至在不调用 OOM 杀手的情况下也会退出。以下三个修饰符可用于覆盖其中一些隐式规则:
__GFP_NORETRY: VM 实现将仅尝试非常轻量的内存直接回收,以便在内存压力下获得一些内存(因此它可能会休眠)。它将避免像 OOM 杀手这样的破坏性操作。调用者必须处理在重度内存压力下很可能发生的故障。当故障可以以较小的代价轻松处理时,例如降低吞吐量,该标志是合适的。
__GFP_RETRY_MAYFAIL: 如果有迹象表明其他地方已经取得进展,VM 实现将重试之前失败的内存回收过程。它可以等待其他任务尝试释放内存的高级方法,例如压缩(消除碎片)和页面换出(page-out)。重试次数仍然有一定的限制,但比 __GFP_NORETRY 的限制要大。
使用此标志的分配可能会失败,但只有当未使用的内存确实很少时才会失败。虽然这些分配不会直接触发 OOM 终止程序,但它们的失败表明系统很可能需要尽快使用 OOM 终止程序。调用者必须处理失败,但可以合理地通过使更高级别的请求失败或仅以效率低得多的方式完成它来做到这一点。
如果分配确实失败了,并且调用者能够释放一些非必要的内存,那么这样做可能会使整个系统受益。
__GFP_NOFAIL: VM 实现必须无限重试:调用者无法处理分配失败。分配可能会无限期阻塞,但永远不会返回失败。因此检测失败是没有意义的。
新用户需要被仔细评估(并且仅在没有合理的失败策略时才应使用该标志),但使用该标志绝对比围绕分配器进行开放式代码无限循环更好。
强烈不建议将此标志用于昂贵的分配,即 order > PAGE_ALLOC_COSTLY_ORDER(3)。。
6. 操作修饰符
#define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN) //0x2000u #define __GFP_COMP ((__force gfp_t)___GFP_COMP) //0x40000u #define __GFP_ZERO ((__force gfp_t)___GFP_ZERO) //0x100u
各标志含义:
__GFP_NOWARN: 抑制分配失败报告。
__GFP_COMP: 地址复合页面元数据。
__GFP_ZERO: 在成功时返回零页面。
7. 有用的 GFP 标志组合
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM) //0x20|0x200|0x800=0xa20 #define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS) //0xc00|0x40|0x80=0xcc0 #define GFP_KERNEL_ACCOUNT (GFP_KERNEL | __GFP_ACCOUNT) //0xcc0|0x400000=0x400cc0 #define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM) //0x800 #define GFP_NOIO (__GFP_RECLAIM) //0xc00 #define GFP_NOFS (__GFP_RECLAIM | __GFP_IO) //0xc00|0x40=0xc40 #define GFP_USER (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL) //0xc00|0x40|0x80|0x100000=0x100cc0 #define GFP_DMA __GFP_DMA //0x01 #define GFP_DMA32 __GFP_DMA32 //0x04 #define GFP_HIGHUSER (GFP_USER | __GFP_HIGHMEM) //0x100cc0|0x02=0x100cc2 #define GFP_HIGHUSER_MOVABLE (GFP_HIGHUSER | __GFP_MOVABLE) //0x100cc2|0x08=0x100cca #define GFP_TRANSHUGE_LIGHT ((GFP_HIGHUSER_MOVABLE | __GFP_COMP | __GFP_NOMEMALLOC | __GFP_NOWARN) & ~__GFP_RECLAIM) //(0x100cca|0x40000|0x80000|0x2000)&~0xc00=0x1c20ca #define GFP_TRANSHUGE (GFP_TRANSHUGE_LIGHT | __GFP_DIRECT_RECLAIM) //0x1c20ca|0x400=0x1c24ca
常用的有用 GFP 标志组合。建议子系统从这些组合之一开始,然后根据需要设置/清除 %__GFP_FOO 标志。
各标志含义:
GFP_ATOMIC: 用户不能休眠,需要能成功分配。应用较低的水印以允许访问“原子保留内存”
GFP_KERNEL: 是内核内部分配的典型代表。调用者需要 %ZONE_NORMAL 或较低的区域才能直接访问,但可以直接回收。
GFP_KERNEL_ACCOUNT: 与 GFP_KERNEL 相同,只是分配要记入 kmemcg。
GFP_NOWAIT: 用于不应因直接回收而停滞、启动物理 IO 或使用任何文件系统回调的内核分配。
GFP_NOIO: 将使用直接回收来丢弃不需要启动任何物理 IO 的干净页面或 slab 页面。请尽量避免直接使用此标志,而是使用 memalloc_noio_save()/memalloc_noio_restore() 来标记无法执行任何 IO 的整个范围,并简要说明原因。所有分配请求都将隐式继承 GFP_NOIO。
GFP_NOFS: 将使用直接回收,但不会使用任何文件系统接口。请尽量避免直接使用此标志,而应使用 memalloc_nofs_save()/memalloc_nofs_restore() 来标记整个范围,该范围不能/不应递归到 FS 层,并简要说明原因。所有分配请求都将隐式继承 GFP_NOFS。
GFP_USER: 用于用户空间分配,但这些分配的内存也需要由内核或硬件直接访问。它通常由硬件用于映射到用户空间(例如图形)的缓冲区,硬件仍必须通过 DMA 访问。这些分配强制执行 cpuset 限制。
GFP_DMA: 的存在是出于历史原因,应尽可能避免。这些标志表示调用者要求使用最低区域(%ZONE_DMA 或 x86-64 上的 16M)。理想情况下,它会被删除,但它需要仔细审核,因为有些用户确实需要它,而其他用户使用该标志来避免 %ZONE_DMA 中的低内存保留,并将最低区域视为一种紧急保留。
GFP_DMA32 与 GFP_DMA 类似,只是调用者需要 32 位地址。
GFP_HIGHUSER: 用于可能映射到用户空间的用户空间的分配,不需要由内核直接访问,但在使用时不能移动。一个例子可能是将数据直接映射到用户空间但没有寻址限制的硬件分配。
GFP_HIGHUSER_MOVABLE: 用于内核不需要直接访问但在需要访问时可以使用 kmap() 的用户空间分配。它们应该可以通过页面回收或页面迁移来移动。通常,LRU 上的页面也会使用 %GFP_HIGHUSER_MOVABLE 进行分配。
GFP_TRANSHUGE 和 %GFP_TRANSHUGE_LIGHT 用于 THP 分配。它们是复合分配,如果内存不可用,通常会很快失败,并且不会在失败时唤醒 kswapd/kcompactd。_LIGHT 版本根本不尝试回收/压缩,并且默认用于page fault路径,而 non-light 版本由 khugepaged 使用。
二、ALLOC内部标志
1. 标志定义
//mm/internal.h /* The ALLOC_WMARK bits are used as an index to zone->watermark */ #define ALLOC_WMARK_MIN WMARK_MIN //0 #define ALLOC_WMARK_LOW WMARK_LOW //1 #define ALLOC_WMARK_HIGH WMARK_HIGH //2 #define ALLOC_NO_WATERMARKS 0x04 /* don't check watermarks at all */ /* Mask to get the watermark bits */ #define ALLOC_WMARK_MASK (ALLOC_NO_WATERMARKS-1) //3 #define ALLOC_OOM 0x08 #define ALLOC_HARDER 0x10 /* try to alloc harder */ #define ALLOC_HIGH 0x20 /* __GFP_HIGH set */ #define ALLOC_CPUSET 0x40 /* check for correct cpuset */ #define ALLOC_CMA 0x80 /* allow allocations from CMA areas */ #define ALLOC_NOFRAGMENT 0x0 #define ALLOC_KSWAPD 0x200 /* allow waking of kswapd */
各标志含义:
ALLOC_WMARK_MIN: 仅在最小水位water mark及以上限制页面分配;
ALLOC_WMARK_LOW: 仅在低水位water mark及以上限制页面分配;
ALLOC_WMARK_HIGH: 仅在高水位water mark及以上限制页面分配;
ALLOC_NO_WATERMARKS: 页面分配时不检查水位
ALLOC_HARDER: 尽力分配,一般在gfp_mask设置了 __GFP_ATOMIC 时会使用。如果页面分配失败,则尽可能分配 MIGRATE_HIGHATOMIC 类型的空闲页面。
ALLOC_HIGH: 高优先级分配,一般在gfp_mask设置了 __GFP_HIGH 时使用.
ALLOC_CPUSET: 检查是否为正确的cpuset.
ALLOC_CMA: 允许从CMA区域进行分配.
ALLOC_KSWAPD: 内存不足时唤醒kswapd内核线程.
三、相关辅助宏
1. GFP_ZONE_TABLE
#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)\ ) /* 展开后为: (0 << 0*1) | (0 << 1*1) | (0 << 2*1) | (0 << 4*1) | (0 << 8*1) | (0 << (8|1)*1) | (1 << (8|2)*1) | (0 << (8|4)*1) */
GFP_ZONE_TABLE 是一个映射表,它将GFP标志映射到不同的内存区域。内核通过分析这些标志位,决定从哪个zone分配内存。
具体实现上,struct free_area 结构体中的 free_area 数组维护了不同阶数的空闲页面信息,每个zone有自己的 free_area 数组。内核在分配内存时,会根据GFP标志查询 GFP_ZONE_TABLE, 确定应该查询哪个zone的 free_area 数组,从而找到合适的内存页面进行分配。
页面分配时 prepare_alloc_pages()-->gfp_zone() 中有使用。
2. GFP_ZONE_BAD
#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) \ ) /* 1 << (0x01 | 0x02) | 1 << (0x01 | 0x04) | 1 << (0x04 | 0x02) | 1 << (0x01 | 0x04 | 0x02) | 1 << (0x08 | 0x02 | 0x01) | 1 << (0x08 | 0x04 | 0x01) | 1 << (0x08 | 0x04 | 0x02) | 1 << (0x08 | 0x04 | 0x01 | 0x02) 即,默认配置下 GFP_ZONE_BAD == 1<<3 | 1<<5 | 1<<6 | 1<<7 | 1<<11 | 1<<13 | 1<<14 | 1<<15 = 0xe8e8 */
GFP_ZONE_BAD 的作用是用于判断内存分配请求是否满足特定的条件。当内核在分配内存时,如果指定的GFP flag与 GFP_ZONE_BAD 匹配,这意味着请求的内存分配条件不符合任何有效的zone分配要求。例如,如果请求的分配条件包含了无效的zone标志位组合,GFP_ZONE_BAD 将匹配该请求,表明该分配请求无法满足任何有效的内存分配条件。
四、相关辅助函数
1. gfp_to_alloc_flags()
根据 gfp_mask 对内存分配标识进行调整。
static inline unsigned int gfp_to_alloc_flags(gfp_t gfp_mask) //page_alloc.c { /* (1) 默认值 */ unsigned int alloc_flags = ALLOC_WMARK_MIN | ALLOC_CPUSET; /* (2) 若指定了 __GFP_HIGH 标志,就将其转换为 ALLOC_HIGH 标志 */ alloc_flags |= (__force int) (gfp_mask & __GFP_HIGH); /* * (3) 在指定GFP_ATOMIC的情况下,如果没有明确禁止访问紧急预留的那部分内存,则或上 ALLOC_HARDER 标志. * (3.1) 在指定GFP_ATOMIC的情况下,强制清除 ALLOC_CPUSET 标志。 * (4) 在没有指定 __GFP_ATOMIC 的情况下, 若当前是RT线程且不在中断上下文中,则强制或上 ALLOC_HARDER. */ if (gfp_mask & __GFP_ATOMIC) { if (!(gfp_mask & __GFP_NOMEMALLOC)) //__GFP_NOMEMALLOC 用于明确禁止访问紧急预留的那部分内存 alloc_flags |= ALLOC_HARDER; alloc_flags &= ~ALLOC_CPUSET; } else if (unlikely(rt_task(current)) && !in_interrupt()) //对RT线程有特殊处理 alloc_flags |= ALLOC_HARDER; /* (5) 同步使能kswapd异步回收标志 */ if (gfp_mask & __GFP_KSWAPD_RECLAIM) alloc_flags |= ALLOC_KSWAPD; #ifdef CONFIG_CMA /* (6) 如果指定了 __GFP_MOVABLE 且指定了 __GFP_CMA,则或上 ALLOC_CMA 标志 */ if ((gfpflags_to_migratetype(gfp_mask) == MIGRATE_MOVABLE) && (gfp_mask & __GFP_CMA)) //1 0x1000000 alloc_flags |= ALLOC_CMA; #endif return alloc_flags; }
2. 涉及 current->flags 设置的分配
memalloc_noio_save()/memalloc_noio_restore() 设置 PF_MEMALLOC_NOIO 标志;memalloc_nofs_save()/memalloc_nofs_restore() 设置 PF_MEMALLOC_NOFS 标志。这两个分别标记 ~(__GFP_IO | __GFP_FS) 和 ~__GFP_IO 的区间。整个区间内分配的内存都会防止递归到文件系统或IO中,以防死锁。
static inline gfp_t current_gfp_context(gfp_t flags) //include/linux/sched/mm.h { if (unlikely(current->flags & (PF_MEMALLOC_NOIO | PF_MEMALLOC_NOFS | PF_MEMALLOC_NOCMA))) { /* NOIO 意味着 NOIO 和 NOFS(看其定义可确定),并且它是一个较弱的上下文,因此始终确保它优先 */ if (current->flags & PF_MEMALLOC_NOIO) flags &= ~(__GFP_IO | __GFP_FS); else if (current->flags & PF_MEMALLOC_NOFS) flags &= ~__GFP_FS; #ifdef CONFIG_CMA if (current->flags & PF_MEMALLOC_NOCMA) //PF_MEMALLOC_NOCMA: 表示不要从CMA区域分配内存 flags &= ~__GFP_MOVABLE; #endif } return flags; }
其调用路径:
__alloc_pages_slowpath //page_alloc.c 内存分配慢速路径 __alloc_pages_direct_reclaim //page_alloc.c __perform_reclaim //page_alloc.c try_to_free_pages //vmsacn.c reclaim_high //memcontrol.c try_charge //memcontrol.c mem_cgroup_resize_max //memcontrol.c mem_cgroup_force_empty //memcontrol.c memory_high_write //memcontrol.c memory_max_write //memcontrol.c try_to_free_mem_cgroup_pages //vmsacn.c get_page_from_freelist //page_alloc.c node_reclaim //vmsacn.c __node_reclaim //vmsacn.c __alloc_pages_nodemask //page_alloc.c 默认配置下无调用位置 cma_alloc //cma.c alloc_contig_range //page_alloc.c fs_reclaim_acquire fs_reclaim_release __need_fs_reclaim //page_alloc.c 使能 CONFIG_LOCKDEP 才生效 current_gfp_context(gfp_mask)
posted on 2024-09-02 18:31 Hello-World3 阅读(91) 评论(0) 编辑 收藏 举报