【嵌入式Linux学习】GFP_NOIO / GFP_NOFS(Linux 内核 gfp 分配标志)

【嵌入式Linux学习】GFP_NOIO / GFP_NOFS (Linux 内核 gfp 分配标志)

GFP 全称 Get Free Pages (获取空闲页),gfp_mask 是内核内存分配时传入的标志位,用来控制内存分配器的行为。 很容易混淆 GFP_NOFS、GFP_NOIO,最大误区是以为它们是原子分配不能睡眠,实际上二者都允许睡眠阻塞 ,核心作用是限制内存回收阶段可以执行哪些 IO,解决内核子系统内部的递归死锁问题。

文章目录

标志基础对比表

标志 是否可睡眠 内存回收限制 典型使用场景
GFP_KERNEL ✅ 可以睡眠 无限制,可以执行 FS IO、块设备 IO 普通内核代码,没有持有文件系统 / 块设备锁
GFP_NOFS ✅ 可以睡眠 禁止文件系统 IO,允许普通裸块 IO 文件系统内部,已经持有文件系统锁时
GFP_NOIO ✅ 可以睡眠 全部磁盘 IO 都禁止(块 IO、文件系统 IO 都不行) 底层块驱动、块 IO 子系统内部
GFP_ATOMIC ❌ 不可睡眠 尽量不做内存回收,不能发起 IO 中断上下文、持有自旋锁的原子上下文

⚠️ 重要提醒:GFP_NOFS、GFP_NOIO ≠ GFP_ATOMIC 这两个标志是可以睡眠的!只有GFP_ATOMIC才代表原子上下文,不允许睡眠。

简单释义

  1. GFP_NOFS(No FileSystem) 内存回收的时候,不许进入文件系统代码路径,不能做文件元数据、pagecache 回写这类文件系统操作;但是底层裸块设备 IO 仍然允许执行。
  2. GFP_NOIO(No IO) 限制更强。内存回收阶段禁止任何磁盘 IO,不管是文件系统 IO 还是底层块 IO 全部不允许。

为什么会诞生这两个标志?根源:递归死锁

死锁发生的核心条件:

当前代码已经持有某子系统的一把非递归锁;调用内存分配,系统内存紧张触发内存回收;内存回收逻辑又重新跑进同一个子系统,再次尝试获取同一把锁,锁拿不到,整个线程卡死。
注意:限制的仅仅是内存分配器自动触发的回收 IO。我们代码自己主动发起的 IO 操作不受标志影响。

场景一:GFP_NOFS,文件系统内部死锁案例

假设文件系统有一把保护元数据的锁 fs_lock,这把锁不是递归锁。

❌ 错误:文件系统内部使用 GFP_KERNEL

复制代码
1. 文件系统代码拿到 fs_lock(锁被本线程占有)
2. 业务需要内存,调用 kmalloc(buf_size, GFP_KERNEL)
3. 系统内存不足,分配器触发内存回收
4. GFP_KERNEL没有限制,回收逻辑选择回收文件pagecache脏页
5. 回写脏页需要进入文件系统代码,尝试再次获取 fs_lock
6. fs_lock已经被自己占有,无法再次获取
👉 线程死锁!

✅ 修复:使用 GFP_NOFS

复制代码
1. 文件系统代码拿到 fs_lock
2. kmalloc(buf_size, GFP_NOFS)
3. 内存不足,启动内存回收
4. 因为带GFP_NOFS标志:回收器直接跳过所有文件系统相关回收逻辑,不会进入FS代码
5. 尝试其他回收手段,例如回收slab对象等,不触碰文件系统锁
6. 回收成功就返回内存;内存实在枯竭,返回NULL交给调用者处理
👉 切断递归路径,避免死锁

关键点:GFP_NOFS 只是不让内存回收跑文件系统代码。文件系统代码自己主动调用写盘 IO 完全不受影响。

场景二:GFP_NOIO,底层块 IO 层死锁案例

块 IO 子系统有保护请求队列的锁 block_lock。

❌ 错误:块层内部使用 GFP_KERNEL

复制代码
1. 块驱动代码拿到 block_lock
2. 需要分配内存,调用 kmalloc(buf_size, GFP_KERNEL)
3. 内存不够,分配器执行内存回收
4. GFP_KERNEL允许磁盘IO,回收触发块IO读写磁盘
5. 重新进入块层代码,想要获取 block_lock
6. 锁已经被当前线程持有
👉 死锁!

✅ 修复:使用 GFP_NOIO

复制代码
1. 块驱动代码拿到 block_lock
2. kmalloc(buf_size, GFP_NOIO)
3. 内存不足尝试回收内存
4. GFP_NOIO禁止内存回收做任何磁盘IO,不会触发块IO路径
5. 使用不涉及磁盘IO的回收途径
👉 不会再次争抢block_lock,规避死锁

📌 区分:为什么块层不能用 GFP_NOFS? GFP_NOFS仅仅禁止文件系统 IO,裸块设备 IO 依旧放行 。内存回收依然可以触发块 IO,依旧会死锁。所以块底层需要限制更强的GFP_NOIO。

通俗生活化类比

把 fs_lock 比作图书馆大门钥匙,同一时刻只能一个人持有;申请内存等价于索要一张草稿纸。

  • GFP_KERNEL:纸不够了,允许管理员去图书馆库房找纸。但是库房也需要大门钥匙,钥匙在你手上,管理员拿不到,直接卡死。
  • GFP_NOFS:告诉管理员,找纸的时候不许进图书馆。管理员只能去别的仓库寻找纸张,不会再来抢你手里的钥匙,就不会卡死。

你 = 文件系统代码;管理员 = 内核内存回收代码。

注意的坑

  1. 标志不等于分配保证成功 带上GFP_NOFS / GFP_NOIO只能规避死锁,不能保证内存一定分配成功。当可用内存极度紧张,可回收的资源又被标志限制,kmalloc依然会返回NULL,代码必须做返回值判空与错误处理。
  2. 普通驱动几乎不要用这两个标志 它们是为文件系统、块 IO 子系统内部设计的。普通外设驱动,没有持有 fs 锁、块锁,直接使用GFP_KERNEL就可以。乱用这两个标志会人为限制内存回收策略,增大分配失败概率。
  3. 不要和 GFP_ATOMIC 混淆 GFP_NOFS、GFP_NOIO允许睡眠,不能在自旋锁、中断上下文使用;中断 / 自旋锁上下文内存分配请使用GFP_ATOMIC。

总结

GFP_NOFS 和 GFP_NOIO 都是允许睡眠的内存分配标志,用于解决内存回收带来的递归死锁。

GFP_NOFS:内存回收禁止执行文件系统 IO,但允许裸块 IO,用于文件系统内部持有文件系统锁的场景,防止回收 pagecache 再次进入文件系统抢锁。

GFP_NOIO 限制更强,内存回收禁止全部磁盘 IO,用在底层块 IO 代码,避免回收触发块 IO 递归争抢块锁。 普通驱动开发很少用到,不要和不能睡眠的 GFP_ATOMIC 弄混。

相关推荐
坤坤子吖1 小时前
ELF加载、动态链接、GOT与PLT
linux·笔记·学习
涵美女程序猿1 小时前
ChatCut online 剪负载均衡健康检查演练录屏:后端池、探针路径与摘除阈值怎样对应
运维·程序人生·电脑·负载均衡
分布式存储与RustFS1 小时前
升级 RustFS 二进制不停机:一条一条换,留一条退路
运维·云原生·开源·对象存储·分布式存储·s3·性能基准
吴声子夜歌2 小时前
Nginx应用与运维——Nginx核心配置指令(一)
运维·nginx
android阿杜2 小时前
Ubuntu 26.04 LTS 系统每次重启后屏幕亮度都会增加一点
linux·运维·ubuntu
懿路向前2 小时前
【唤醒实战笔记】2026-09-29 | 智能体提示词迭代史(v1 → v8)
linux·笔记·算法
路溪非溪3 小时前
Linux下Shell脚本基础知识(二)
linux·运维·服务器
fruge3 小时前
飞牛OS部署Flare导航页:Docker Compose、YAML书签配置与cpolar公网访问
运维·docker·容器
71-33 小时前
MySQL密码重置
数据库·笔记·学习·mysql