☰
【嵌入式Linux学习】GFP_NOIO / GFP_NOFS(Linux 内核 gfp 分配标志)
2026/10/1 15:30:19 网站建设 项目流程

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

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

文章目录

  • 【嵌入式Linux学习】GFP_NOIO / GFP_NOFS (Linux 内核 gfp 分配标志)
    • 标志基础对比表
      • 简单释义
    • 为什么会诞生这两个标志?根源:递归死锁
      • 场景一: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 弄混。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询