LWN:GFP 标志介绍以及移除 __GFP_ATOMIC!

GFP flags and the end of __GFP_ATOMIC

By Jonathan Corbet
January 27, 2023
DeepL assisted translation
https://lwn.net/Articles/920891/

内核中的内存分配是很复杂的。在任一个系统中,都只有数量严格受限的物理内存,这意味着内存分配请求通常只能通过从别人那里获取内存,但是当内存分配请求提出的时候,可能无法采取某些内存回收方式。此外,有一些分配请求会有一些特殊要求,规定内存可以位于什么地方,或者必须以多快的速度进行分配。内核的内存分配函数长期以来一直支持一组 "GFP flags",用于描述每个内存分配请求的特殊要求。由 Mel Gorman 发布的这组 patch set 来看,这些 GFP flag 可能很快就会发生一些改变了,那么我们就趁此机会来详细了解一下这些 flag 的情况。

GFP 标志中的 "GFP" 最初是来自 "get free page" 的缩写,也就是 __get_free_pages(),这是内核中一个长期存在的底层分配函数。使用 GFP 标志的代码远远不止这个函数了,但记住,它们都与与整个 page 的分配有关。分配较小块内存的函数(如 kmalloc())也可能会用到 GFP 标志,但它们只在这些函数内部走到了必须从内存管理子系统获得整个 page 的时候才会用到。

大多数开发者看到的 GFP 标志是以 GFP_ATOMIC 或 GFP_KERNEL 等宏的形式出现的,但这些宏实际上是由更底层一些 flag 组成的。例如,在 6.2-rc 内核中,GFP_ATOMIC 被定义为:

#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
    
    

flag 中的每一部分都会代表这个请求需要采取某种特殊处理。例如,__GFP_HIGH 会让这个请求变成 "高优先级" 请求,具体影响会在下面介绍。不过,早在 2021 年,Neil Brown 就注意到 "__GFP_ATOMIC 没有多大价值",并发布了一个 patch 来删除它。这个 patch 被搁置了一年多,尽管它确实激发了一场关于某些 GFP flag 含义的慢悠悠的讨论。10 月,Andrew Morton 威胁说要放弃掉这个 patch。这就促使 Gorman 把它又捡了起来,修改形成了当前的这组 patch,这就对底层 GFP flag 的工作方式做出了一些改动。

Low-level GFP flags

这些 flag 定义在 include/linux/gfp_types.h,每个 flag 有多种变种。因此,举例来说,__GFP_ATOMIC 被定义为:

#define __GFP_ATOMIC ((__force gfp_t)__GFP_ATOMIC)
    
    

而带了三个下划线的 ___GFP_ATOMIC 则简单地定义为:

#define ___GFP_ATOMIC 0x200u
    
    

中间级别(两个下划线)的 flag 是为了对一些函数的 GFP-flags 参数进行类型检查而用的,而三个下划线的这些 flag 可以让内存管理子系统中比较容易地进行修改。在这个子系统之外的开发者都不应该使用三个下划线的 flag,但其实它们才是最终定义了内存分配请求中的可用选项的 flag。

因此,对于感兴趣的读者,下面会介绍这些底层 GFP 标志,以及它们在应用 Gorman 的 patch set 后对分配请求的影响,主要分为几大类。

Placement options

一些 GFP 标志是用来改变分配发生在物理内存中的位置的:

  • ___GFP_DMA

  • 这是一个很古老的 flag,反映了早期 x86 系统的局限性,它只能支持 ISA 总线上的 24 位 DMA 地址。使用它的话,会导致分配被放置在物理内存中最低的 16MB 区域中。当代的各种系统中最差的硬件也不应该再有这种限制了,但是,正如 gfp_types.h 所指出的,它不能被轻易删除。需要进行 "仔细的审查"。

  • ___GFP_DMA32

  • 和__GFP_DMA 一样,这个 flag 是为具有有限的 DMA 范围的设备准备的;限制这次分配需要使用可以用 32 位寻址的物理内存。希望只有那些无法切换到 64 位的旧硬件才需要这个标志。

  • ___GFP_HIGHMEM

  • 这个标志表示在 "high memory" 中进行分配。high memory 在 LWN 文章 (https://lwn.net/Articles/813201/ )中有介绍。它只存在于 32 位系统中,在这些系统中不可能将所有的物理内存都映射到内核的地址空间。在 64 位系统上没有 high memory,所以这个标志在那里没有影响。

  • ___GFP_MOVABLE

  • 表示内存管理子系统在一旦需要时可以进行移动的内存,从而能有助于内存回收。例如,用户空间的 page 是可移动的,因为它们是通过 page table 来访问的,可以在属主进程都不知道的情况下就被换到了一个新的位置。

  • ___GFP_RECLAIMABLE

  • 这个标志表示当资源紧张时可以通过 shrinkers (各种内存收缩机制)回收的 slab 内存。内存管理子系统试图将可移动的并且可回收的分配都放在同一个内存区域,以方便在需要时可以释放更大范围的内存。

  • ___GFP_HARDWALL

  • 这个 flag 不允许在发起调用的进程的 cpuset 限定的内存节点之外分配内存。

  • ___GFP_THISNODE

  • 带有这个标志的话,只能由当前 NUMA 节点上的内存满足这次内存分配。

Access to reserves

内存管理子系统总是竭尽全力预留(reserve)出一些空闲内存。释放内存往往需要先分配内存,例如要发起一个 I/O 传输来将 dirty page 的内容写入 persistent storage,如果分配失败的话,事情就会变得很糟糕。下面几个选项描述了分配是否会占用 reserve 内存,以及占用的程度:

  • ___GFP_HIGH

  • "高优先级的"分配会置上这个 flag。这在实践中意味着,正如 Gorman 的 patch set 中所确定下来的,这种分配被允许使用内核为重要分配所预留的 reserve 区域。有了这个标志的话,reserve 的内存区域对这次新的分配就可以在不超过预留空间正常大小的 50% 的情况下都要满足。

  • ___GFP_MEMALLOC

  • 使用这个标志进行分配的话,会绕过对 reserve 区域使用上的所有限制,获取任何一块可用的内存。只有在这次分配的目标是为了在后面马上能得到更多空余内存的情况下才应该使用。

  • ___GFP_NOMEMALLOC

  • 明确地禁止使用 reserve 内存。这个标志最初是为了防止 memory pool 把 reserve 内存全用光而引入的。

请注意,下面描述的 ___GFP_KSWAPD_RECLAIM 也跟 reserve 内存的使用有关联。

Side effects

从原子上下文(atomic context)发出的内存分配请求不能被阻塞,从文件系统发出的请求也不应该引起对该文件系统的递归调用。于是定义了一些 GFP 标志来反映了这些限制,都描述了内存管理子系统可以做哪些动作来满足这次分配请求。

  • ___GFP_IO

  • 带有这个标志的请求,就允许在需要时启动 I/O 来回收内存。对于 "正常" 的请求来说,都会设置这个 flag,但比如对于来自 storage layer 的请求来说,就应该避免递归产生 I/O 操作,不可以设置这个 flag。

  • ___GFP_FS

  • 这个 flag 允许此次请求在需要时调用文件系统层代码来回收内存;和__GFP_IO 一样,当文件系统本身正在分配内存时,不可以设置这个 flag,从而避免递归调用。

  • ___GFP_DIRECT_RECLAIM

  • 允许这次分配函数走到 direct reclaim 部分,这意味着调用线程本身可以做一些工作来释放内存,从而满足这次分配。direct reclaim 增加了分配成功的机会,但也会增加 request 的返回时间,并可能导致这个调用线程阻塞。

  • ___GFP_KSWAPD_RECLAIM

  • 这个 flag 允许调用 kswapd 进程来进行回收。这是人们通常期望发生的行为,但在某些情况下,如果让 kswapd 运行可能会干扰其他内存管理操作。此外还有一点可能不太明显(但也许更重要),这个 flag 也表示了分配请求不应该因为任何原因而阻塞;事实上,GFP_NOWAIT 跟这个 flag 是相同含义。如果__GFP_HIGH 也被设置的话,这个标志会允许访问 62.5% 的 reserve 内存。

Warnings and retries

还有一组选项描述了在最初尝试完成分配请求失败时应该做什么。

  • ___GFP_NOWARN

  • 在请求失败时不在系统日志(system log)中打印 warning。这个 flag 用于一些可能会出现失败但是有现成的解决方法可用的情况;比如在试图分配一个大的、连续的区域的时候,如果失败的话可以用很多小 size 的分配来解决。

  • ___GFP_RETRY_MAYFAIL

  • 表示一个重要的请求,如果第一次尝试分配失败,它可以等待额外的重试。

  • ___GFP_NOFAIL

  • 标志这是一个不允许失败的请求;在这种情况下,内存管理子系统将会无限重试。有时候会有一些人试图移除这个标志,他们的理论是所有的内核代码都应该能够处理分配失败,但是仍然有很多人在使用它。

  • ___GFP_NORETRY

  • 如果无法获得现成的 memory,这个 flag 将导致分配请求迅速失败。在分配失败可以相对容易处理的地方,这个 flag 是很有用的,而且最好不要通过努力回收内存来给系统带来压力。

Miscellaneous flags

最后,还有一组必不可少的 flag,它们不算前面几种类别里:

  • ___GFP_ZERO

  • 这个标志要求在返回之前将请求的 page 清零。

  • ___GFP_WRITE

  • 表示该页将很快被写入。这个标志只在几个地方用到。一个是尝试将即将被 write 的 page 分散放到各个内存区去的时候。另一个是对处理一个进程的 working set 的 "refault" 代码中的调整;如果先前回收的 file page 被拿回来进行 rewritten 的话,它不会被视为 working set 的一部分。

  • ___GFP_COMP

  • 进行 multi-page 分配应该返回一个 compound page。

  • ___GFP_ACCOUNT

  • 这个标志会让分配出来的资源被计入当前进程的控 cgroup 里。它只用在会要在内核中使用的内存上;用户空间的 page 早就已经被计算进来了。

  • ___GFP_ZEROTAGS

  • 如果 page 本身在分配时被清零,会导致与 page 相关的内部 "tags" metadata 也被清除。这是一个小的优化,只在 arm64 的 page fault 处理代码中用到了。

  • ___GFP_SKIP_ZERO

  • ___GFP_SKIP_KASAN_UNPOISON

  • ___GFP_SKIP_KASAN_POISON

  • 这三个标志是用来调整 KASAN sanitizer 的检查动作的。

  • ___GFP_NOLOCKDEP

  • 禁用 lockdep 这个 locking 检查工具对内存分配上下文的检查。这个标志只在内存管理子系统和 XFS 文件系统中使用。

In conclusion

可以看到有很多方法来改变内核中内存分配的方式。在 Gorman 的这组 patch 之后,GFP_ATOMIC 仍然存在(毕竟在内核中有超过 5000 个使用位置),但是它被定义为:

#define GFP_ATOMIC (__GFP_HIGH|__GFP_KSWAPD_RECLAIM)
    
    

所以,根据上面的列表可知,GFP_ATOMIC 分配请求永远不会被阻塞,而且它可以使用 reserve 的内存的不少空间。这就是为什么在不鼓励在那些不是必需的场景下使用 GFP_ATOMIC 的原因。

这组 patch 中的另一个改动是将 realtime tasks 的分配都缺省标记为__GFP_HIGH,因为占用一些 reserve 内存要比推迟任务执行并导致其错过 deadline 要好。但是,正如 Gorman 所指出的,如果需要访问 reserve 内存的话,说明系统已经在面临内存分配上的困难,很有可能也是 deadline 也是会无法满足的。该 patch 警告说,realtime task 的特例将在未来的某个时候会被删除。

这组 patch 已经是第三次版了,这还不算 Brown 最初发的版本。讨论似乎已经放缓,所以看起来已经接近于进入 mainline 了。届时,__GFP_ATOMIC 将不再存在,其他的一些 flag 也会得到更清晰的定义,而大多数开发者估计根本都不会注意到。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值