当前位置:首页>Linux>Linux 内核内存分配:GFP_ATOMIC 是干什么的

Linux 内核内存分配:GFP_ATOMIC 是干什么的

  • 2026-10-21 13:28:52
Linux 内核内存分配:GFP_ATOMIC 是干什么的

第一章:拆解 GFP_ATOMIC 的基因

1.1 GFP 的起源:Get Free Pages

在内核的底层,内存是以“页”(Page)为单位管理的。无论你是调用kmalloc(申请小块内存)还是vmalloc(申请大块虚拟连续内存),最终都会殊途同归,走到伙伴系统(Buddy System)的入口——alloc_pages。

GFP标志位就是需求清单,告诉内核:

Zone 修饰符:内存是哪里来的(DMA 区域、Normal 区域还是 HighMem 区域)。

动作修饰符:获取内存是否要等待

1.2 ATOMIC 的真实含义

很多人误以为GFP_ATOMIC涉及 CPU 锁或原子变量,其实不是这样的。这里的Atomic指的是“分配操作不可被剥夺(Preempted)”。

在内核中,如果你正处于中断处理程序(ISR)或者持有自旋锁(Spinlock)的状态,就是“不可调度的”。一旦眠(Sleep),调度器尝试切换进程,而当前路径又握着锁,系统就会陷入永久的死锁。因此,GFP_ATOMIC的核心承诺是:绝对不进入睡眠,绝对不触发调度。

第二章:Linux 内存分配的水位线(Watermark)机制

2.1 三道防线:High, Low, Min

Linux 内核为了保证系统稳定,在每个内存管理区(Zone)都设定了三条“生命线”:

High (高水位线):内存非常富余,申请秒过。

Low (低水位线):内存开始紧张。此时内核会唤醒kswapd进程,开始后台异步回收内存(把不常用的页丢到 Swap 或者释放 Cache)。

Min (最小预留线):极度危险。这是为内核核心运转保留的“最后口粮”。

水位高度 (Page Count) ^ | [ 自由空间 ] +-----------------------+ --- High (高水位) : 内存充足,kswapd 停止工作 | [ kswapd 异步回收区 ] | +-----------------------+ --- Low (低水位) : 触发 kswapd 唤醒,后台回收 | [ 直接回收触发区 ] | +-----------------------+ --- Min (最小水位) : GFP_KERNEL 申请在此处被阻塞 | [ 紧急预留区 (Reserve)] |  | (仅限 GFP_ATOMIC 使用) | +-----------------------+ --- 0 (物理极限)

2.2 GFP_ATOMIC 的“特权卡”

普通的 GFP_KERNEL 申请,如果发现剩余内存低于 Low,就会被阻塞或者进入慢速路径。

但 GFP_ATOMIC 携带了 ALLOC_HARDER 标志。它像是一辆救护车,在内存水位已经触碰 Min 甚至低于 Min 时,依然被允许从那个“最后金库”里取钱。

第三章:GFP_ATOMIC 与 OOM Killer 的生死对决

3.1 OOM 的触发路径:为什么 ATOMIC 不参与?

当内存实在不够用时,内核会进入“慢速路径(Slowpath)”。

在这个路径里,内核会做以下尝试:

唤醒所有回收线程。

直接回收(Direct Reclaim):申请者自己停下手中的活,去扫描内存。

触发 OOM Killer:实在没办法了,选一个“胖子”进程杀掉。

关键是:GFP_ATOMIC禁止睡眠,因此它无法参与步骤 2 和 3。一旦它发现预留区也没钱了,它会选择立即放弃并返回NULL。

3.2 间接纵火者

虽然GFP_ATOMIC不杀人(不触发 OOM),但它会把水池抽干。当水池干涸后,下一个倒霉的普通进程(比如ls或者nginx)进来申请内存时,会因为触碰到水位底线而触发 OOM。所以,GFP_ATOMIC分配失败通常是系统崩溃的前兆。

第四章:内核源码层面的深度追踪

我们要深入mm/page_alloc.c源码中看看,在分配的核心函数__zone_watermark_ok中,你可以看到这样的逻辑:

// 伪代码:内核如何判断水位是否达标if (free_pages <= min + low_on_memory_reserve) {    // 如果是 GFP_ATOMIC,它会获得一个额外的豁免权    if (alloc_flags & ALLOC_HARDER)        free_pages += min / 4; // 允许冲破一部分 Min 水位限制    if (free_pages <= min)        return false; // 哪怕是特权阶层,见底了也得跪}

这段源码解释了为啥GFP_ATOMIC更有可能成功,也解释了它为啥还是可能会失败。

第五章:我们该怎么必坑呢?

5.1 必须使用的场景:锁与中断

在以下场景,你别无选择,必须使用GFP_ATOMIC:

网卡驱动收包:此时处于中断底半部(Softirq),不能睡眠。

自旋锁保护区:spin_lock_irqsave和spin_unlock_irqrestore之间。

5.2 常见的错误

spin_lock(&my_lock);// 错误!在自旋锁里用了 GFP_KERNEL,可能导致系统挂死ptr = kmalloc(size, GFP_KERNEL); spin_unlock(&my_lock);

5.3 怎么处理 NULL这个空指针呢?

由于GFP_ATOMIC经常失败,你的代码必须具备“韧性”:

网络驱动:如果分配失败,直接丢弃该数据包,等下一次重传。

磁盘驱动:将请求放入等待队列,稍后尝试。

第六章:系统调优与监控

6.1 调整急救内存的大小

如果你发现dmesg里有大量的page allocation failure,可以尝试调大预留区:

增加预留内存到 256MBsysctl -w vm.min_free_kbytes=262144

6.2 监控指标

通过cat /proc/vmstat | grep allocstall可以查看系统因为内存分配而进入“停顿”的次数。如果这个数值增长过快,说明你的GFP_ATOMIC分配压力已经传导到了整个系统。

总结一下

GFP_ATOMIC能不用就不用:只要能睡眠,首选GFP_KERNEL,必用时防失败:必须写好if (!ptr) 这里的异常分支逻辑。

最新文章

随机文章