内存分配掩码(gfp_mask) - 内存域修饰符 & 内存分配标志

本文详细解析了Linux内核版本v3.2.40中内存分配掩码的概念及其组成部分,包括内存域修饰符和内存分配标志。文章还介绍了常见内存分配场景及所使用的标志。
本文内容均基于内核版本Linux-v3.2.40。

    先说说GFP吧,曾几何时,每次写代码遇到kmalloc、alloc_page时都会疑惑,到底是GFP_KERNEL还是GPF_KERNEL,一直搞不清楚,每次都要先搜个例子看看,到后来才知道,原来GFP是get free page的缩写!现在想想,都快要被以前的自己蠢哭了,^_^

    好了,切入正题,本文主要讲解内存分配中的分配掩码。分配掩码包括两部分,内存域修饰符(占低4位)和内存分配标志(从第5位开始),如下图所示:


1. 内存域修饰符
    前面的文章已经介绍过内存域zone的几种类型:ZONE_DMA、ZONE_DMA32、ZONE_NORMAL、ZONE_HIGHMEM、ZONE_MOVABLE。与类型不同,内存域的修饰符只有___GFP_DMA、___GFP_HIGHMEM、___GFP_DMA32、___GFP_MOVABLE 4种,没有ZONE_NORMAL对应的修饰符,因为ZONE_NORMAL是默认的内存申请类型。如下所示,为内存域修饰符的定义:

#define ___GFP_DMA        0x01u
#define ___GFP_HIGHMEM    0x02u
#define ___GFP_DMA32      0x04u
#define ___GFP_MOVABLE    0x08u
这里主要想说明的是,内存域修饰符与伙伴系统分配器扫描内存域的顺序的关系,如下所示:


2. 内存分配标志
    除了内存域修饰符之外,分配掩码中还包含了大量的分配标志,如下所示:

#define __GFP_WAIT    ((__force gfp_t)___GFP_WAIT) /* 内存分配的过程中可以被打断 */
#define __GFP_HIGH    ((__force gfp_t)___GFP_HIGH) /* 请求分配非常紧急的内存,注意与__GFP_HIGHMEM的区分,__GPF_HIGHMEM指从高端内存域分配内存 */
#define __GFP_IO    ((__force gfp_t)___GFP_IO)     /* 内存分配的过程中可进行IO操作,也就是说分配过程中如果需要换出页,必须设置该标志,才能将换出的页写入磁盘 */
#define __GFP_FS    ((__force gfp_t)___GFP_FS)     /* 内存分配过程中可执行VFS操作,也就是可以调用VFS的接口 */
#define __GFP_COLD    ((__force gfp_t)___GFP_COLD) /* 分配不在cpu高速缓存中的冷页 */

#define __GFP_NOWARN    ((__force gfp_t)___GFP_NOWARN)    /* 内存分配时不允许内核发出警告,极少使用 */
#define __GFP_REPEAT    ((__force gfp_t)___GFP_REPEAT)    /* 内存分配失败后,会进行重试,重试若干次后停止 */
#define __GFP_NOFAIL    ((__force gfp_t)___GFP_NOFAIL)    /* 内存分配失败后一直重试,直至成功 */
#define __GFP_NORETRY    ((__force gfp_t)___GFP_NORETRY)  /* 内存分配失败后,不进行重试 */

#define __GFP_COMP    ((__force gfp_t)___GFP_COMP)    /* 增加复合元数据 */
#define __GFP_ZERO    ((__force gfp_t)___GFP_ZERO)    /* 申请全部填充为0的page */

#define __GFP_NOMEMALLOC ((__force gfp_t)___GFP_NOMEMALLOC)   /* 不使用紧急分配链表 */
#define __GFP_HARDWALL ((__force gfp_t)___GFP_HARDWALL)       /* 只能在当前进程可运行的cpu关联的内存节点上分配内存,如果进程可在所有cpu上运行,该标志无意义 */
#define __GFP_THISNODE    ((__force gfp_t)___GFP_THISNODE)    /* 只能在当前节点上分配内存 */
#define __GFP_RECLAIMABLE ((__force gfp_t)___GFP_RECLAIMABLE) /* 请求分配可回收的page */
#define __GFP_NOTRACK    ((__force gfp_t)___GFP_NOTRACK)      /* 不对分配的内存进行跟踪 */

#define __GFP_NO_KSWAPD    ((__force gfp_t)___GFP_NO_KSWAPD)
#define __GFP_OTHER_NODE ((__force gfp_t)___GFP_OTHER_NODE) /* On behalf of other node */

由于这些标志几乎总是组合使用,内核中作了一些分组,包含了用于各种标准情形的适当标志,如下所示:

/* This equals 0, but use constants in case they ever change */
#define GFP_NOWAIT    (GFP_ATOMIC & ~__GFP_HIGH)
/* GFP_ATOMIC means both !wait (__GFP_WAIT not set) and use emergency pool */
#define GFP_ATOMIC    (__GFP_HIGH) /* 表示申请内存非常紧急,不能睡眠,不能有IO和VFS操作 */
#define GFP_NOIO    (__GFP_WAIT)
#define GFP_NOFS    (__GFP_WAIT | __GFP_IO)
#define GFP_KERNEL    (__GFP_WAIT | __GFP_IO | __GFP_FS) /* 可以睡眠,可以有IO和VFS操作 */
#define GFP_TEMPORARY    (__GFP_WAIT | __GFP_IO | __GFP_FS | \
             __GFP_RECLAIMABLE)

/* 以下三个用于为用户空间申请内存 */
#define GFP_USER    (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL) /* 可以睡眠,可以有IO和VFS操作,只能从进程可运行的node上分配内存 */
#define GFP_HIGHUSER    (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | __GFP_HIGHMEM)  /* 优先从高端zone中分配内存 */
#define GFP_HIGHUSER_MOVABLE    (__GFP_WAIT | __GFP_IO | __GFP_FS | \
                 __GFP_HARDWALL | __GFP_HIGHMEM | __GFP_MOVABLE) /* 申请可移动的内存 */

#define GFP_IOFS    (__GFP_IO | __GFP_FS)
#define GFP_TRANSHUGE    (GFP_HIGHUSER_MOVABLE | __GFP_COMP | \
             __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \
             __GFP_NO_KSWAPD)
3. 常见的几种内存分配场景及使用的标志
    1) 缺页异常分配内存时:GFP_HIGHUSER | __GFP_ZERO | __GFP_MOVABLE   /* 分配可移动的page,并且将page清零 */
        do_page_fault() -> handle_pte_fault() -> do_anonymous_page() -> alloc_zeroed_user_highpage_movable()
    2) 文件映射分配内存时:GFP_HIGHUSER_MOVABLE   /*  分配可移动的page */
        do_page_fault() -> handle_pte_fault() -> do_anonymous_page() -> do_nonlinear_fault() -> __do_fault()
    3) vmalloc分配内存时:GFP_KERNEL | __GFP_HIGHMEM   /* 优先从高端内存中分配 */
        vmalloc() -> __vmalloc_node_flags(size, -1, GFP_KERNEL | __GFP_HIGHMEM)

    以上就是与内存分配掩码相关的内容,如有错误欢迎指正。



<think>嗯,用户现在想知道在为用户进程分配物理内存时,应该选择GFP_KERNEL还是GFP_HIGHUSER_MOVABLE作为分配掩码的适用场景。首先,我需要回忆一下之前回答的内容,其中提到了页面分配器扫描zone的顺序,以及不同分配标志的影响,比如__GFP_MOVABLE和__GFP_DMA。 根据之前的引用内容,GFP_KERNEL是内核中最常用的标志,允许休眠,适用于可以阻塞的上下文,比如进程上下文。而GFP_HIGHUSER_MOVABLE属于用户空间分配,用于可迁移的高端内存,适合用户进程的页面,尤其是可能被移动以减少内存碎片的情况。用户提到的GFP_USER可能也是相关的,但问题集中在GFP_KERNEL和GFP_HIGHUSER_MOVABLE之间。 需要区分这两个标志的使用场景。GFP_KERNEL主要用于内核内部的内存分配,比如创建数据结构、缓冲区等,而GFP_HIGHUSER_MOVABLE是用户空间通过系统调用申请内存时使用的,例如malloc()可能会触发这个标志。同时,GFP_HIGHUSER_MOVABLE包含了__GFP_MOVABLE,这有助于内存碎片管理,特别是在频繁分配释放的情况下。 用户可能混淆了内核和用户空间的内存分配场景。需要明确GFP_KERNEL是内核自己使用的,而用户进程的内存分配通常通过GFP_HIGHUSER_MOVABLE这样的标志来处理。另外,GFP_USER和GFP_HIGHUSER_MOVABLE的区别在于后者使用高端内存并且页面可迁移,适用于用户空间需要大内存且允许移动的情况。 还要注意分配掩码内存的影响。比如GFP_KERNEL可能会从NORMAL或DMA区分配,而GFP_HIGHUSER_MOVABLE则优先使用HIGHMEM区,适合用户进程的大内存需求。此外,GFP_HIGHUSER_MOVABLE的可迁移属性有助于反碎片策略,这在之前回答的水位检测和冷热页机制中有提到。 可能需要检查引用中的GFP标志定义,比如引用1中的宏定义显示GFP_HIGHUSER_MOVABLE包含__GFP_MOVABLE,而GFP_KERNEL则包含__GFP_WAIT、IO和FS权限。引用4也详细说明了各个标志的用途,例如GFP_USER用于用户进程,GFP_HIGHUSER_MOVABLE允许迁移。引用5提到在文件系统代码中使用GFP_NOFS避免死锁,这也间接说明了不同上下文下的标志选择。 总结来说,GFP_KERNEL用于内核内部可休眠的上下文,而GFP_HIGHUSER_MOVABLE用于用户空间需要可迁移内存的情况,特别是高端内存。需要将这些区别清晰地传达给用户,并结合之前的回答中的内存分配机制来解释适用场景。</think>### Linux内核中GFP_KERNEL、GFP_USER与GFP_HIGHUSER_MOVABLE的区别及使用场景 #### 一、核心区别对比 | 标志类型 | 适用对象 | 内存 | 可休眠 | 可迁移性 | 典型应用场景 | |-----------------------|----------------|---------------------|--------|----------|--------------------------------------| | `GFP_KERNEL` | 内核线程/驱动 | `ZONE_NORMAL/DMA` | 允许 | 不可迁移 | 内核数据结构、设备驱动缓存、文件系统操作[^1] | | `GFP_USER` | 用户进程 | `ZONE_NORMAL/DMA` | 允许 | 不可迁移 | 用户空间与内核共享的DMA缓冲区[^4] | | `GFP_HIGHUSER_MOVABLE`| 用户进程 | `ZONE_HIGHMEM` | 允许 | 可迁移 | 用户空间匿名页、文件映射页[^4] | #### 二、详细功能解析 1. **`GFP_KERNEL`** - **内存来源**:优先从`ZONE_NORMAL`和`ZONE_DMA`分配物理页 - **行为特性**:允许休眠(通过`__GFP_WAIT`)、允许触发文件系统回写(`__GFP_FS`) - **代码示例**: ```c // 内核模块中分配缓冲区 char *buf = kmalloc(size, GFP_KERNEL); ``` - **限制**:不可用于原子上下文(如中断处理)[^4] 2. **`GFP_USER`** - **内存映射**:分配物理地址连续的内存,通常用于用户空间与硬件直接交互(如DMA) - **特殊属性**:通过`__GFP_HARDWALL`限制内存节点分配,确保进程只使用其允许的NUMA节点 - **典型场景**:显卡显存映射、硬件寄存器访问 3. **`GFP_HIGHUSER_MOVABLE`** - **内存优化**: - 使用`ZONE_HIGHMEM`避免耗尽低端内存 - 通过`__GFP_MOVABLE`支持页面迁移,配合`CMA`(连续内存分配器)减少碎片[^2] - **分配示例**: ```c // 用户进程通过mmap申请大内存 addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); ``` - **内核实现**: ```c // 页表项标记为迁移类型 set_pageblock_migratetype(page, MIGRATE_MOVABLE); ``` #### 三、选择策略 1. **选择`GFP_KERNEL`的条件** - 需要在内核上下文(如系统调用、工作队列)中分配内存 - 内存将用于内核数据结构(如`task_struct`、`inode`缓存) - 允许触发直接内存回收(如`kswapd`唤醒)[^1][^5] 2. **选择`GFP_HIGHUSER_MOVABLE`的条件** -用户进程分配匿名页(如`malloc`、`mmap`) - 需要支持内存热迁移(如虚拟化环境动态调整内存- 避免低端内存耗尽(如嵌入式系统运行32位内核)[^4] 3. **`GFP_USER`的特殊场景** - 用户空间需要直接访问物理连续内存(如`/dev/mem`映射) - 硬件设备要求DMA缓冲区在特定区(如旧式ISA设备) #### 四、性能影响对比 $$ \text{分配延迟} = \begin{cases} O(1) & \text{使用per-CPU缓存(GFP_KERNEL)} \\ O(n) & \text{需触发页面回收(GFP_HIGHUSER_MOVABLE)} \end{cases} $$ | 指标 | GFP_KERNEL | GFP_HIGHUSER_MOVABLE | |-----------------------|------------------|----------------------| | 碎片化风险 | 较高(不可迁移) | 低(支持迁移) | | NUMA局部性 | 依赖本地节点策略 | 可跨节点迁移 | | 最大单次分配大小 | 4MB(默认限制) | 可突破至ZONE_HIGHMEM容量 | #### 五、错误使用示例 1. **中断上下文误用GFP_KERNEL** ```c // 错误:中断处理中不可休眠 void irq_handler() { char *buf = kmalloc(size, GFP_KERNEL); // 可能引发死锁 } ``` **修正**:改用`GFP_ATOMIC` 2. **用户空间误用GFP_USER** ```c // 错误:用户进程不应直接使用内核分配接口 void *p = vmalloc_user(size); // 应通过mmap接口 ``` --- ### 相关问题 1. **`GFP_ATOMIC`与`GFP_NOWAIT`在不可休眠场景下的区别是什么?** 2. **如何通过`/proc/buddyinfo`观察不同迁移类型的内存碎片情况?** 3. **CMA(连续内存分配器)如何与`GFP_HIGHUSER_MOVABLE`协作优化大块内存分配?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值