Linux 执行“机会交换”,还是神话?

sou*_*edi 13 linux memory swap

假设一个程序需要一些内存,但没有足够的可用内存。Linux 有几种不同的响应方式。一种响应是选择一些最近没有被访问过的其他已用内存,并将这个不活动的内存移动到交换区。

但是,我看到很多文章和评论都超出了这个范围。他们说即使有大量空闲内存,Linux 有时也会决定写入非活动内存进行交换。提前写入交换意味着当我们最终想要使用这块内存时,我们不必等待磁盘写入。他们说这是一种优化性能的深思熟虑的策略。

他们是对的吗?或者这是一个神话?引用您的来源。

请使用以下定义理解这个问题:

  • 空闲内存 - free命令显示的“空闲”内存。这是MemFree来自的值/proc/meminfo/proc/meminfo是内核提供的虚拟文本文件。请参阅proc(5)RHEL 文档
  • 即使有大量可用内存- 为了论证的目的,假设有超过 10% 的可用内存。

参考

这里有一些搜索词:linux“机会交换”或(交换“当系统无事可做时”或“当它无事可做时”或“系统空闲时”或“空闲时间”)

在 Google 上排名第二的结果中,StackExchange 用户询问“当 RAM 中有足够多的可用空间时为什么要使用交换?”,并复制free显示大约 20% 可用内存的命令的结果。在回答这个特定问题时,我看到这个答案得到了高度评​​价:

Linux 在 RAM 被填满之前开始交换。这样做是为了提高性能和响应能力:

  • 性能提高是因为有时 RAM 更适合用于磁盘缓存而不是存储程序内存。因此,最好将一段时间不活动的程序换掉,而将经常使用的文件保存在缓存中。

  • 通过在系统空闲时换出页面来提高响应能力,而不是在内存已满且某些程序正在运行并请求更多 RAM 来完成任务时。

当然,交换确实会降低系统速度——但交换的替代方案并不是不交换,而是拥有更多的 RAM 或使用更少的 RAM。

谷歌上的第一个结果已被标记为上述问题的重复:-)。在这种情况下,提问者复制了显示 7GB 的详细信息MemFree,共 16GB。这个问题有一个自己接受和赞成的答案:

仅当没有空闲内存时才交换,只有设置swappiness为 0时才如此。否则,在空闲时间,内核将交换内存。这样做时,数据不会从内存中删除,而是在交换分区中进行复制。

这意味着,如果出现内存耗尽的情况,则不必立即写入磁盘。在这种情况下,内核可以只覆盖已经交换的内存页面,它知道它有数据的副本。

swappiness参数基本上只是控制它执行此操作的程度。

另一个引用没有明确声明交换的数据也保留在内存中。但似乎您更喜欢这种方法,如果您即使在有 20% 的空闲内存时也进行交换,并且您这样做的原因是为了提高性能。

据我所知,Linux 确实支持在主内存和交换空间中保留相同数据的副本。

我还注意到“机会交换”发生在“空闲时间”的普遍说法。我知道它应该有助于让我确信此功能通常对性能有益。我没有在上面的定义中包含这个,因为我认为它已经有足够的细节来提出一个很好的明确问题。我不想让这变得比需要的更复杂。

原始动机

当我有千兆字节的空闲内存时,atop 会显示 `swout`(交换)。为什么?

有几个这样的报告,当有足够的空闲内存时,Linux 写入交换。“机会主义交换”可以解释这些报告。同时,至少提出了一个替代原因。作为查看可能原因的第一步:Linux 是否执行过上述定义的“机会交换”?

在我报告的例子中,现在已经回答了这个问题。原因不是机会主义的交换。

sou*_*edi 15

Linux 不执行此问题中定义的“机会交换”。


以下主要参考文献根本没有提到这个概念:

  1. 了解 Linux 虚拟内存管理器。梅尔·戈尔曼 (Mel Gorman) 的在线书籍。写于 2003 年,就在 Linux 2.6.0 发布之前。
  2. 文档/admin-guide/sysctl/vm.rst。这是 Linux 虚拟内存管理可调设置的主要文档。

进一步来说:

10.6 Pageout 守护进程 (kswapd)

过去kswapd每 10 秒唤醒一次,但现在只有当达到区域中的 pages_low 空闲页面数时,物理页面分配器才会唤醒它。[...] 在极端内存压力下,进程会kswapd同步完成工作。[...]kswapd不断释放页面,直到达到 pages_high 水印。

基于上述,当空闲页数高于“高水印”时,我们不会期望任何交换。

其次,这告诉我们这样做的目的kswapd是为了制作更多的免费页面。

kswapd写入要交换的内存页时,它会立即释放内存页。 kswapd 不会在内存中保留交换页面的副本

Linux 2.6 使用“ rmap ”来释放页面。在 Linux 2.4 中,情况更加复杂。当一个页面被多个进程共享时,kswapd 无法立即释放它。这是古老的历史。所有链接的帖子都是关于 Linux 2.6 或更高版本的。

互换性

此控件用于定义内核交换内存页面的积极程度。较高的值会增加攻击性,较低的值会减少交换量。值 0 指示内核在空闲和文件支持的页面数量小于区域中的高水位线之前不启动交换。

这句话描述了一种特殊情况:如果您将swappiness值配置为0. 在这种情况下,我们不应该期待任何交换,直到缓存页面的数量下降到高水位线。换句话说,内核会在开始交换之前尝试丢弃几乎所有的文件缓存。(这可能会导致大量减速。您需要有一些文件缓存!文件缓存用于保存所有正在运行的程序的代码:-)

水印是什么?

以上引用提出了一个问题:我系统上的“水印”内存预留有多大?答:在“小型”系统上,默认区域水印可能高达内存的 3%。这是由于“最小”水印的计算。在较大的系统上,水印的比例较小,接近内存的 0.3%。

因此,如果问题是关于空闲内存超过 10% 的系统,则此水印逻辑的确切细节并不重要。

每个单独“区域”的水印显示在 中/proc/zoneinfo,如proc(5) 中所述。摘自我的 zoneinfo:

Node 0, zone    DMA32
  pages free     304988
        min      7250
        low      9062
        high     10874
        spanned  1044480
        present  888973
        managed  872457
        protection: (0, 0, 4424, 4424, 4424)
...
Node 0, zone   Normal
  pages free     11977
        min      9611
        low      12013
        high     14415
        spanned  1173504
        present  1173504
        managed  1134236
        protection: (0, 0, 0, 0, 0)
Run Code Online (Sandbox Code Playgroud)

目前的“水印”是minlowhigh。如果一个程序曾经要求足够的内存来减少free以下内容min,该程序将进入“直接回收”。当内核释放内存时,程序会等待。

如果可能,我们希望避免直接回收。因此,如果free低于low水印,内核就会唤醒kswapdkswapd通过交换和/或删除缓存来释放内存,直到再次free高于high


附加限定:kswapd还将运行以保护全部 lowmem_reserve 量,用于内核 lowmem 和 DMA 使用。默认的lowmem_reserve大约是第一个 4GiB RAM(DMA32 区域)的 1/256,因此通常在 16MiB 左右。

Linux 代码提交

mm:按内存比例缩放 kswapd 水印

[...]

watermark_scale_factor:

这个因素控制了 kswapd 的攻击性。它定义了在 kswapd 被唤醒之前节点/系统中剩余的内存量以及在 kswapd 返回睡眠之前需要释放多少内存。

单位是 10,000 的分数。默认值 10 表示水印之间的距离是节点/系统中可用内存的 0.1%。最大值为 1000,即内存的 10%。

线程进入直接回收(allocstall)或 kswapd 过早休眠(kswapd_low_wmark_hit_quickly)的高比率可能表明 kswapd 由于延迟原因维护的空闲页面数量对于系统中发生的分配突发来说太小。然后可以使用此旋钮相应地调整 kswapd 的积极性。

proc: meminfo: 更保守地估计可用内存

中的MemAvailable项目/proc/meminfo是向用户提示可分配多少内存而不引起交换,因此它将区域的低水印排除为用户空间不可用。

然而,对于用户空间分配,kswapd实际上会回收,直到空闲页面达到高水位线和页面分配器的低内存保护的组合,该保护也从用户空间保留一定数量的 DMA 和 DMA32 内存。

在计算 MemAvailable 时,从可用页面数中减去我们知道用户空间不可用的全部数量。

Linux 代码

有时声称更改swappiness0将有效禁用“机会交换”。这提供了一个有趣的调查途径。如果有一种叫做“机会交换”的东西,并且它可以通过交换来调整,那么我们可以通过找到所有读取vm_swappiness. 请注意,我们可以通过假设CONFIG_MEMCG未设置来减少搜索空间(即“内存 cgroups”已禁用)。调用链如下:

shrink_node_memcg评论“这是一个基本的每节点页面释放器。由 kswapd 和直接回收使用”。即这个功能增加了空闲页面的数量。它不会尝试复制要交换的页面,以便在以后释放它们。但即使我们打折扣:

上面的链是从三个不同的函数调用的,如下所示。正如预期的那样,我们可以将调用站点分为直接回收和 kswapd。在直接回收中执行“机会主义交换”是没有意义的。

  1. /*
     * 这是直接回收路径,用于页面分配进程。只有我们
     * 尝试从区域回收页面以满足调用者的分配
     * 要求。
     *
     *如果一个区域被认为充满了固定页面,那么就给它一个灯
     *扫描然后放弃它。
     */
    静态无效收缩区域
    
  2.  * kswapd 收缩处于或低于最高可用值的页面节点
     * 当前不平衡的区域。
     *
     * 如果 kswapd 至少扫描了请求的页数,则返回 true
     * 回收或者如果由于页面写回而导致进度不足。
     * 用于确定是否需要提高扫描优先级。
     */
    static bool kswapd_shrink_node
    
  3.  * 对于 kswapd,balance_pgdat() 将从区域中跨节点回收页面
     * 有资格供呼叫者使用,直到至少一个区域被
     * 平衡。
     *
     * 返回 kswapd 完成回收的顺序。
     *
     * kswapd 在 highmem->normal->dma 方向扫描区域。它跳过
     * 具有 free_pages > high_wmark_pages(zone) 的区域,但是一旦一个区域被
     * 发现有 free_pages <= high_wmark_pages(zone),该区域中的任何页面
     * 或更低有资格回收,直到至少一个可用区域被
     * 平衡。
     */
    静态 int balance_pgdat
    

因此,大概声称 kswapd 以某种方式被唤醒,即使所有内存分配都立即从空闲内存中得到满足。我查看了 的使用wake_up_interruptible(&pgdat->kswapd_wait),我没有看到任何像这样的唤醒。

  • @Giles 知识的一个方面不是您答案的一部分,但我已将其包含在我对问题的措辞中。从搜索结果中获取一个措辞:“机会交换的很酷的事情是*服务器仍然在物理内存中保留和提供数据的副本*。但是如果后来事情变得忙碌并且服务器需要该内存来做其他事情,它可以删除它们,而不必对磁盘执行额外的不合时宜的写入。” 我不认为会发生这种情况。当您唤醒 kswapd 时,它*移动*内存进行交换,而不是复制它。 (4认同)
  • 你是直接说不,然后写很多段说是吗? (3认同)
  • 因此,“Linux 是否执行机会交换”的答案是肯定的,但是“当有大量可用内存时 Linux 是否会执行机会交换”的答案是肯定的(这不是常见的说法,当然也不是我认为的) 've made) 是没有。请注意,磁盘缓存不算作“空闲”,因此在正常使用中已达到水印。 (3认同)

Nik*_*lai 5

不,Linux 中没有机会交换这样的东西。我花了一些时间研究这个问题,所有的来源(教科书、内核开发人员邮件列表上的电子邮件、Linux 源代码和提交评论,以及与 Mel Gorman 的一些 Twitter 交流)都告诉我同样的事情:Linux 只回收内存以响应某种形式的内存压力(明显的休眠除外)。

关于这个主题的所有流行的误解可能源于一个简单的事实,即 Linux 不能等到空闲内存的最后一个字节才开始交换。它需要某种缓冲来保护它免受极端形式的内存耗尽的影响,并且有一些可调参数可以影响该缓冲的大小(例如vm.min_free_kbytes)。但这与“因为无事可做而交换”不同。

不幸的是,相对于 2.6(在 Mel Gorman 的书中详细描述了它),页框回收算法变得更加复杂,但基本思想或多或少是相同的:页面回收由失败的分配触发,然后kswapd同步唤醒或尝试释放页面(取决于内存压力、分配标志和其他因素)。

页面分配可能在剩余足够空闲内存的情况下开始失败的最明显原因是它们可能要求连续内存,而实际上内存可能过于分散而无法满足请求。从历史上看,Linux 内核开发人员竭尽全力避免对连续分配的需求。尽管如此,一些设备驱动程序仍然需要这样做——要么是因为它们不能进行多页内存 I/O(分散-聚集 DMA),要么可能只是驱动程序开发人员的草率编码。Transparent Huge Pages (THP) 的出现为在物理上连续的块中分配内存提供了另一个原因。

大约在同一时间范围内引入的区域压缩应该有助于解决内存碎片问题,但它并不总是产生预期的效果。

有各种vmscan跟踪点可以帮助了解在您的特定情况下到底发生了什么——当具有特定的调用堆栈时,在 Linux 内核代码中找到您需要的东西总是更容易,而不是仅仅扫描看起来远程相关的所有内容。

  • [与梅尔·戈尔曼的 Twitter 交流](https://twitter.com/MelGorman1/status/1151206767856160773)。我一直在寻找值得信赖的参考资料,这是一个很好的参考资料,谢谢!(而且我认为有一个特定的链接很有用,因为 Twitter 浏览起来可能很痛苦,特别是如果你不习惯的话)。 (2认同)
  • 需要明确的是,我从来没有直接问过他关于“机会交换”的问题——你可以自己尝试一下,他的 Twitter 用户名是 MelGorman1(如果你想搜索我们关于区域水印的交换,我的用户名是 oradiag) (2认同)