0.概述
- 分配掩码是描述页面分配方法的标志,它影响着页面分配的整个流程。因为Linux 内核是个通用的操作系统,所以页面分配器被设计成一个复杂的系统。它既要高效,又要兼顾很多情况,特别是在内存紧张的情况下的内存分配。gfp_mask 其实被定义成一个unsigned类型的变量。
typedef unsigned__bitwise_ gfp_t;
1.内存管理区修饰符的标志
- 内存管理区修饰符主要用于表示应当从哪些内存管理区中来分配物理内存。内存管理区修饰符使用gfp_mask的低4位来表示,其标志如表所示。
标 志 | 描 述 |
---|
__GFP_DMA | 从ZONE_DMA中分配内存 |
__GFP_DMA32 | 从ZONE_DMA32中分配内存 |
__GFP_HIGHMEM | 优先从ZONE HIGHMEM中分配内存 |
__GFP_MOVABLE | 页面可以被迁移或者回收,如用于内存规整机制 |
2.移动修饰符的标志
- 移动修饰符主要用于指示分配出来的页面具有的迁移属性,其标志如表所示。在 Linux 2.6.24内核中,为了解决外碎片化的问题,引入了迁移类型,因此在分配内存时需要指定所分配的页面具有哪些迁移属性。
标 志 | 描 述 |
---|
__GFP_RECLAIMABLE | 在slab分配器中指定了SLABRECLAIMACCOUNT标志位,表示slab分配器中使 用的页面可以通过收割机来回收 |
__GFP_HARDWALL | 使能cpuset 内存分配策略 |
__GFP_THISNODE | 从指定的内存节点中分配内存,并且没有回退机制 |
__GFP_ACCOUNT | 分配过程会被kmemcg记录 |
3. 水位修饰符的标志
- 水位修饰符用于控制是否可以访问系统预留的内存。所谓系统预留内存指的是最低警戒水位以下的内存,一般优先级的分配请求是不能访问它们的,只有高优先级的分配请求才能访问。如GFP HIGHGFPATOMIC等。水位修饰符的标志如表所示。
标 志 | 描 述 |
---|
__GFP_HIGH | 表示分配内存具有高优先级,并且这个分配请求是很有必要的,分配器可以使用系统预留的内存(即最低警戒水位线下的预留内存) |
__GFP_ATOMIC | 表示分配内存的过程不能执行页面回收或者 睡眠动作,并且具有很高的优先级,可以访问系统预留的内存。常见的一个场景是在 中断上下文中分配内存 |
__GFP_MEMALLOC | 分配过程中允许访问所有的内存,包括系统预留的内存。分配内存进程通常要保证在分配内存过程中很快会有内存被释放,如进程退出或者页面回收 |
__GFP_NOMEMALLOC | 分配过程不允许访问系统预留的内存 |
4.页面回收修饰符的标志
标 志 | 描 述 |
---|
__GFP_IO | 允许开启I/O传输 |
__GFP_FS | 允许调用底层的文件系统。这个标志清零通常是为了避免死锁的发生,如果相应的文件系统操作路径上已经持有了锁,分配内存过程又递归地调用这个文件系统的相应操 作路径,可能会产生死锁 |
__GFP_DIRECT_RECLAIM | 分配内存的过程中允许使用页面直接回收机制 |
__GFP_KSWAPD_RECLAIM | 表示当到达内存管理区的低水位时会唤醒kswapd内核线程,以异步地回收内存,直到内存管理区恢复到了高水位为止 |
__GFP_RECLAIM | 用于允许或者禁止直接页面回收和kswapd内核线程 |
__GFP_REPEAT | 当分配失败时会继续尝试 |
__GFP_NOFAIL | 当分配失败时会无限地尝试下去,直到分配成功为止。当分配者希望分配内存不失败时, 应该使用这个标志位,而不是自己写一个while循环来不断地调用页面分配接口函数 |
__GFP_NORETRY | 当使用了直接页面回收和内存规整等机制还无法分配内存时,最好不要重复尝试分配了,直接返回NULL |
5. 行为修饰符的标志
标 志 | 描 述 |
---|
__GFP_COLD | 分配的内存不会马上被使用。通常会返回一个空的高速缓存页面 |
__GFP_NOWARN | 关闭分配过程中的一些错误报告 |
__GFP_ZERO | 返回一个全部填充为0的页面 |
__GFP_NOTRACK | 不被kmemcheck机制跟踪 |
__GFP_OTHER_NODE | 在远端的一个内存节点上分配。通 常在khugepaged内核线程中使用 |
6.常见的组合标志
- 前文列出了5大类修饰符的标志,对于内核开发者或者驱动开发者来说,要正确使用这些标志是一件很困难的事情,因此定义了一些常用的标志的组合–类型标志(type flag),如表所示。类型标志提供了内核开发中常用的标志的组合, 推荐开发者使用这些类型标志。
类型标志 | 描 述 |
---|
GFP_KERNEL | 内核分配内存常用的标志之一。它可能会被阻塞,即分配过程中可能会睡眠 |
GFP_ATOMIC | 调用者不能睡眠并且保证分配会成功。它可以访问系统预留的内存 |
GFP_NOWAIT | 分配中不允许睡眠等待 |
GFP_NOFS | 不会访问任何的文件系统的接口和操作 |
GFP_NOIO | 不需要启动任何的I/O操作。如使用直接回收机制丢弃干净的页面或者为slab分配的页面 |
GFP_USER | 通常用户空间的进程用来分配内存,这些内存可以被内核或者硬件使用。常用的一个场景是硬件使用的DMA缓冲器要映射到用户空间,如显卡的缓冲器 |
GFP_DMA/GFP_DMA32 | 使用ZONE DMA或者ZONE_DMA32来分配内存 |
GFP_HIGHUSER | 用户空间进程用来分配内存,优先使用ZONE_HIGHMEM,这些内存可以映射到用户空间,内核空间不会直接访问这些内存。另外,这些内存不能迁移 |
GFP_HIGHUSER MOVABLE | 类似于GFP_HIGHUSER,但是页面可以迁移 |
GFP_TRANSHUGE/GFP_TRANSHUGE_LIGHT | 通常用于透明页面分配 |
6.1 GFP_KERNEL
- GFP_KERNEL 主要用于分配内核使用的内存,在分配过程中会引起睡眠,在中断上文和不能睡眠的内核路径里使用该类型标志需要特别警惕,因为这会引起死锁或者其他系统异常。GFP_KERNEL分配的页面通常是不可迁移的。
6.2 GFP_ATOMIC
- GFP_ATOMIC 正好和 GFP_KERNEL 相反,它可以使用在不能睡眠的内存分配路径(如中断处理程序、软中断以及tasklet等)中。GFP_KERNEL可以让调用者睡眠等待系统页面回收来释放一些内存,但GFP_ATOMIC不可以,所以可能会分配失败。
6.3 GFP_USER,GFP_HIGHUSER 和GFP_HIGHUSER_MOVABLE
- GFP_USER、GFP_HIGHUSER 和GFP_HIGHUSER_MOVABLE 这几个类型标志都是为用户空间进程分配内存的。不同之处在于,GFP_HIGHUSER 首先使用高端内存, GFP_HIGHUSER_MOVABLE优先使用高端内存并且分配的内存具有可移动属性。GFP_HIGHUSER_MOVABLE分配的页面是可迁移的。
6.4 GFP_NOIO 和GFP_NOFS
- GFP_NOIO 和GFP_NOFS 都会产生阻塞,它们用于避免某些其他的操作。GFP NOIO表示分配过程中绝不会启动任何磁盘I/O的操作。GFP_NOFS表示可以启动磁盘I/O,但是不会启动文件系统的相关操作。举一个例子,假设进程A在执行打开文件的操作中需要分配内存,但内存短缺,那么进程A会睡眠等待,系统的 OOM Killer 机制会选择一个进程来终止。假设选择了进程B,而进程B退出时需要执行一些文件系统的操作,这些操作可能会申请锁,而恰巧进程A持有这个锁,所以死锁就发生了。