关注了就能看到更多这么棒的文章哦~

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 许可协议。

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

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

LWN:GFP 标志介绍以及移除 __GFP_ATOMIC!相关推荐

  1. linux socket recv函数 MSG_PEEK标志介绍

    考虑下面的场景,server向client发送数据"_META_DATA_\r\n_USER_DATA_",要求"\r\n"之前的数据_META_DATA_在第 ...

  2. 阿里云 Aliplayer高级功能介绍(四):直播时移

    基本介绍 时移直播基于常规的HLS视频直播,直播推流被切分成TS分片,通过HLS协议向播放用户分发,用户请求的m3u8播放文件中包含不断刷新的TS分片地址:对于常规的HLS直播而言,TS分片地址及相应 ...

  3. 移远BC28指令对接电信IOT平台基本流程

    目录 概述 一.Coap协议基本流程 1.AT                           //串口波特率自行匹配 1.AT+NRB                 //软重启模组 2.AT+ ...

  4. 移通好闹钟微信小程序全套源码

    介绍: 移通好闹钟微信小程序全套源码,前端+后台+开发文档. 带视频演示文件 网盘下载地址: http://kekewl.org/ByWeMCkuZ900 图片:

  5. 语音处理的分帧,帧移,加窗,滤波,降噪,合成

    一.分帧 语音数据和视频数据不同,本没有帧的概念,但是为了传输与存储,我们采集的音频数据都是一段一段 的.为了程序能够进行批量处理,会根据指定的长度(时间段或者采样数)进行分段,结构化为我们编程 的数 ...

  6. 移卡参投的乐享互动首日破发:旗下乐刷罚单不断,逾期率高居不下

    继移卡科技(9923.HK)今年6月份于港交所上市后,9月23日,其参与投资的乐享互动也顺利在港交所上市,发行价为2.88港元,首日收跌6.25%,总市值为58.72亿港元. 据了解,乐享互动上市首日 ...

  7. 中移物联ML302开发板上手体验

    开始 中移物联网的ML302开发板是支持4G Cat.1网络的开发板,对于Cat.1这里就不再赘述,详细可以去官网了解一下. 接下来介绍中移物联网的ML302开发板以及具体的上手步骤,给那些刚拿到开发 ...

  8. 如何从GFP确定最后申请的内存来自哪个zone?

    本文基于linux-3.10的内核 申请内存时需要先确定两个值,分别是high_zoneidx和migratetype.这两个值从哪里获取到呢?都是利用上面传递过来的GFP flag. 对于high_ ...

  9. 实现MapX的移屏测距功能(转)

    本文转载自hi.baidu.com/redpanda/blog/category/Mapx/index/1 实现MapX的移屏测距功能(转) 前一段时间想利用业余时间把MapX的一些功能写出来,特别是 ...

最新文章

  1. python pptp链接_渗透技巧——PPTP口令的获取与爆破
  2. ElasticSearch(八):springboot集成ElasticSearch集群并使用
  3. 图片相似度识别_deepface:人脸识别\特征分析
  4. idea + maven + profile + tomcat 调试 javaee 和js
  5. linux系统下springboot jar方式启动后允许后台运行
  6. 栈的应用实例——计算后缀表达式
  7. LeetCode Algorithm 204. 计数质数
  8. 运算放大器单电源应用中的使用齐纳二极管偏置方法
  9. php 谷歌语音,php 语音参考
  10. BZOJ 4518: [Sdoi2016]征途 [斜率优化DP]
  11. [TCP/IP] 传输层-ethereal 抓包分析TCP包
  12. PDF虚拟打印机(virtual printer)软件汇总
  13. 黑客防线、黑客X档案专辑 NPM、PYPI、DockerHub 备份
  14. EAccessViolation 地址访问错误
  15. Linux bpf 1.1、BPF内核实现
  16. HttpWatch使用教程
  17. 无线射频芯片CC2540F256RHAR 中文资料介绍
  18. 简单解读拼多多t.gif、tne.gif接口
  19. 设计模式的六大原则(SOLID)
  20. python 爬虫框架scrapy 入门 爬取博客园新闻(代码)

热门文章

  1. 详细介绍idm下载以及配置,实现百度网盘急速下载不是梦,适合新手
  2. stol函数在linux下使用,C++ std::stol()、std::stoll()用法及代码示例
  3. 上采样,重采样和下采样,降采样
  4. 基于FOC电路低次谐波抑制Simulink仿真
  5. JS字符串格式化函数 string.format
  6. Linux网卡模块,裁剪Linux并实现网卡模块的安装(附有命令移植的脚本)
  7. 【CV-表情识别】如何衡量面部表情丰富性?
  8. Android最全的屏幕适配
  9. 简单谈谈语音评测(语音评价)
  10. (最新最详细)安装ubuntu18.04