Linux内核管理之分配掩码(三)
分配掩码
是linux内存管理中非常重要的一个参数,它影响着页面分配的整个流程。
分配掩码gfp_mask
定义在include/linux/gfp.h
文件中,这些标志位在Linux4.4内核中被重新归类,大致可以分成以下几类:
- 内存管理区修饰符(zone modifier)
- 移动修饰符(mobility and placement modifier)
- 水位修饰符(watermark modifier)
- 页面回收修饰符(page reclaim modifier)
- 行动修饰符(action modifier)
内存管理区修饰符
内存管理区修饰符
主要是用来表示应当从哪些内存管理区中来分配物理内存。内存管理区修饰符使用gfp_mask的最低4个比特位来表示
标志 | 描述 |
---|---|
__GFP_DMA | 从ZONE_DAM中分配内存 |
__GFP_DMA32 | 从ZONE_DMA32中分配内存 |
__GFP_HIGHMEM | 优先从ZONE_HIGHMEN中分配内存 |
移动修饰符
移动修饰符
主要用来指示分配出来的页面具有的移动属性。
在Linux2.6.24内核中,为了解决外碎片化的问题,引入了迁移类型,因此在分配内存时也需要指定所分配的页面具有哪些移动属性。
标志 | 描述 |
---|---|
__GFP_MOVABLE | 页面可以被迁移或者回收,比如内存规整 |
__GFP_RECLAIMABLE | 在slab分配器中指定了SLAB_RECLAIM_ACCOUNT标志位,表示slab中使用的页面可以通过shrinkers来回收 |
__GFP_HARDWALL | 使能cpuset内存分配策略 |
__GFP_THISNODE | 从指定的内存节点中分配内存,并且没有回退机制 |
__GFP_ACCOUNT | 分配过程中会被kmemcg记录 |
水位修饰符
水位修饰符
用来控制是否可以访问系统紧急预留的内存
标志 | 描述 |
---|---|
__GFP_HIGH | 表示分配内存具有高优先级,并且这个分配请求是很有必要的,分配器可以使用紧急的内存池 |
__GFP_ATOMIC | 表示分配内存的过程不能执行页面回收或者睡眠动作,并且具有很高的优先级。常用的场景是在中断上下文分配内存 |
__GFP_MEMALLOC | 分配过程中允许访问所有的内存,包括系统预留的紧急内存 |
__GFP_NOMEMALLOC | 分配过程中不允许访问系统预留的紧急内存 |
页面回收修饰符
标志 | 描述 |
---|---|
__GFP_IO | 允许开启I/O传输 |
__GFP_FS | 允许调用底层的文件系统。。清楚这个标志位通常是为了避免死锁的发生 |
__GFP_DIRECT_RECLAIM | 分配内存的过程中调用直接页面回收机制 |
__GFP_KSWAPD_RECLAIM | 表示当到达内存管理区的低水位时会唤醒kswapd内核现成去异步地回收内存,直到内存管理区恢复到高水位为止 |
__GFP_RECLAIM | 用来允许或禁止直接页面回收和kswapd内核线程 |
__GFP_REPEAT | 当分配失败时会继续尝试 |
__GFP_NOFAIL | 当分配失败时会无限地尝试下去,直到分配成功为止。当分配者希望分配内存不失败时,应该使用这个标志位,而不是自己写一个while循环来不断调用页面分配接口函数 |
__GFP_NORETRY | 当直接页面回收和内存规整等机制都使用了还是无法分配内存时,就不用去重复尝试分配了,直接返回NULL |
行动修饰符
标志 | 描述 |
---|---|
__GFP_COLD | 分配的内存不会马上被使用 |
__GFP_NOWARN | 关闭分配过程中的一些错误报告 |
__GFP_ZERO | 返回一个全部填充为0的页面 |
__GFP_NOTRACK | 不被kmemcheck机制跟踪 |
__GFP_OTHER_NODE | 在远端一个内存节点上分配 |
类型标志
对于内核开发者或者驱动开发者来说,要正确使用这些标志位是一件很困难的事情,因此定义了一些常用的分配掩码的组合,成为类型标志
。类型标志提供了内核开发中常用的分配掩码的组合,推荐开发者使用这些类型标志。
一般来说,__GFP
开头的就为分配掩码,GFP
开头的(没有下划线)的就是类型标志
标志 | 描述 |
---|---|
GFP_ATOMIC | 调用者不能睡眠并且保证分配会成功。它可以访问系统预留的内存,这个标志位通常使用在中断处理程序、下半部、持有自旋锁或者其他不能睡眠的地方 |
GFP_KERNEL | 内核分配内存最常用的标志位之一。它可能会被阻塞,即分配过程可能会睡眠 |
GFP_NOWAIT | 分配不允许睡眠等待 |
GFP_NOIO | 不需要启动任何的I/O操作。比如使用直接回收机制去丢弃干净的页面或者为slab分配的页面 |
GFP_NOFS | 不会访问任何的文件系统的接口和操作 |
GFP_USER | 通过用户空间的进程用来分配内存,这些内存可以被内核或者硬件使用。常用的一个场景是,硬件使用的DMA缓冲器要映射到用户空间,比如显卡的缓冲器 |
GFP_DMA/GFP_DMA32 | 使用ZOME_DMA或者ZOME_DMA32来分配内存 |
GFP_HIGHUSER | 用户空间进程用来分配内存,优先使用ZONE_HIGHME,这些内存可以被映射到用户空间,内核空间不会直接访问这些内存,另外这些内存不能被迁移 |
GFP_HIGHUSER_MOVEABLE | 类似GFP_HIGHUSER,但是页面可以被迁移 |
上面表格中的类型标志都是非常常用的分配掩码组合,在实际使用的过程汇总需要注意以下事项。
-
GFP_KERNEL
GFP_KERNEL
是最常见的内存分配掩码之一,主要用于分配内核使用的的内存,需要注意的是分配过程中会引起睡眠,这在中断上下文以及不能睡眠的内核路径里调用该分配掩码需要特别警惕,因为会引起死锁或者其他系统异常 -
GFP_ATOMIC
GFP_ATOMIC
这个标志位正好和GFP_KERNEL
相反,它可以使用在不能睡眠的内存分配路径上,比如中断处理程序、软中断以及tasklet等。GFP_KERNEL
可以让调用者睡眠等待系统页面回收来释放一些内存,但是GFP_ATOMIC
不可以,所以有可能会分配失败。 -
GFP_USER
、GFP_HIGHUSER
和GFP_HIGHUSER_MOVEABLE
这三个标志位都是为用户空间进程分配内存的。不同之处在于,GFP_HIGHUSER
首先使用高端内存,GFP_HIGHUSER_MOVEABLE
首先使用高端内存并且分配的内存具有可迁移性 -
GFP_NOIN
、GFP_NOFS
这两个标志位都会产生阻塞,它们用来避免某些其他的操作。
GFP_NOIO
表示分配过程中绝不会启动任何磁盘I/O的操作。
GFP_NOFS
表示分配过程中绝不会启动文件系统的相关操作。
举个例子,假设进程A在执行打开文件的操作中需要分配内存,这时内存短缺了,那么进程A会睡眠等待,系统的OOM Killer机制会选择一个进程杀掉。假设选择了进程B,而进程B退出时需要执行一些文件系统的操作,这些操作可能会去申请锁,而恰巧进程A持有这个锁,所以死锁就发生了。
常用类型标志的使用
待完成