假设一个程序请求一些内存,但没有足够的可用内存。 Linux 有几种不同的响应方式。一种响应是选择一些其他最近未访问过的已使用内存,并将该不活动内存移至交换区。
然而,我看到很多文章和评论超出了这个范围。他们说,即使有大量可用内存,Linux 有时也会决定将不活动的内存写入交换区。提前写入交换意味着当我们最终想要使用这块内存时,我们不必等待磁盘写入。他们说这是一个优化性能的深思熟虑的策略。
他们是对的吗?或者这是一个神话?引用您的来源。
请使用以下定义来理解这个问题:
- 交换
- 自由的内存 - 显示的“空闲”内存自由的命令。这是
MemFree
来自 的值/proc/meminfo
。/proc/meminfo
是内核提供的虚拟文本文件。看过程(5), 或者RHEL 文档。 - 即使有大量可用内存- 为了论证的目的,假设有超过 10% 的可用内存。
参考
以下是一些搜索词:linux“机会交换”OR(交换“当系统没有更好的事情可做时”OR“当它没有更好的事情可做时”OR“系统空闲时”OR“空闲时间期间”)
free
在 Google 上的第二高结果中,StackExchange 用户询问“当 RAM 中有足够的可用空间时为什么要使用交换?”,并复制显示大约 20% 可用内存的命令结果。针对这个具体问题,我看到这个答案得到了高度评价:
Linux 在 RAM 被填满之前开始交换。这样做是为了提高性能和响应能力:
性能得到提高,因为有时 RAM 更适合用于磁盘缓存而不是存储程序内存。因此,最好更换一段时间不活动的程序,并将常用文件保留在缓存中。
通过在系统空闲时交换页面,而不是在内存已满且某些程序正在运行并请求更多 RAM 来完成任务时,可以提高响应能力。
当然,交换确实会降低系统速度,但交换的替代方案并不是不交换,而是使用更多 RAM 或使用更少 RAM。
Google 上的第一个结果已被标记为与上述问题重复:-)。在本例中,询问者复制的详细信息显示 7GB MemFree
(共 16GB)。这个问题有一个自己被接受和赞成的答案:
如果设置为 0,则仅在没有可用内存时才进行交换。
swappiness
否则,在空闲期间,内核将交换内存。在此过程中,数据不会从内存中删除,而是会在交换分区中创建一个副本。这意味着,如果出现内存耗尽的情况,则不必立即写入磁盘。在这种情况下,内核可以只覆盖已经交换的内存页面,因为它知道它拥有数据的副本。
该
swappiness
参数基本上只是控制它执行此操作的程度。
另一个引用没有明确声明交换的数据也保留在内存中。但如果您甚至在有 20% 可用内存的情况下进行交换,您似乎更喜欢这种方法,并且这样做的原因是为了提高性能。
据我所知,Linux 确实支持在主内存和交换空间中保留相同数据的副本。
我还注意到一种常见的说法,即“机会主义交换”发生在“空闲时间”。我知道它应该有助于让我放心,这个功能通常对性能有好处。我没有将其包含在上面的定义中,因为我认为它已经有足够的细节来提出一个清晰的问题。我不想让事情变得比需要的更复杂。
最初的动机
当我有千兆字节的可用内存时,顶部显示“swout”(交换)。为什么?
有一些这样的报告,当有足够的可用内存时,Linux 会写入交换区。 “机会主义交换”或许可以解释这些报告。与此同时,至少提出了一个替代原因。作为寻找可能原因的第一步:Linux 是否曾经执行过上面定义的“机会交换”?
在我报告的示例中,问题现已得到解答。原因不是机会主义交换。
答案1
Linux 不执行此问题中定义的“机会交换”。
以下主要参考文献根本没有提到这个概念:
- 了解 Linux 虚拟内存管理器。梅尔·戈尔曼的在线书籍。写于 2003 年,Linux 2.6.0 发布之前。
- 文档/管理指南/sysctl/vm.rst。这是 Linux 虚拟内存管理可调设置的主要文档。
进一步来说:
历史上
kswapd
用于每 10 秒唤醒一次,但现在仅当区域中的空闲页数达到 page_low 时才由物理页分配器唤醒。 [...] 在极端的内存压力下,进程将同步完成工作kswapd
。 [...]kswapd
不断释放页面,直到达到pages_high 水位线。
基于上述情况,当空闲页面数量高于“高水位线”时,我们预计不会发生任何交换。
其次,这告诉我们的目的kswapd
是为了制作更多的免费页面。
什么时候kswapd
将内存页写入交换区时,它会立即释放该内存页。 kswapd 不会在内存中保留交换页面的副本。
Linux 2.6 使用“映射“释放页面。在 Linux 2.4 中,情况更加复杂。当一个页面被多个进程共享时,kswapd 无法立即释放它。这是古老的历史。所有链接的帖子都是关于 Linux 2.6 或更高版本的。
该控制用于定义内核交换内存页面的积极程度。较高的值会增加攻击性,较低的值会减少交换量。值 0 指示内核在空闲量达到之前不启动交换和文件支持的页面小于区域中的高水位线。
此引用描述了一种特殊情况:如果将swappiness
值配置为0
.在这种情况下,我们不应该期望任何交换,直到达到缓存页面已降至高水位线。换句话说,内核将在开始交换之前尝试丢弃几乎所有文件缓存。 (这可能会导致速度大幅下降。您需要一些文件缓存!文件缓存用于保存所有正在运行的程序的代码:-)
什么是水印?
上述引文提出了一个问题:我的系统上的“水印”内存预留有多大?答:在“小型”系统上,默认区域水印可能高达内存的 3%。这是由于“最小”水印的计算造成的。在较大的系统上,水印所占的比例较小,接近内存的 0.3%。
因此,如果问题涉及空闲内存超过 10% 的系统,则此水印逻辑的具体细节并不重要。
每个单独“区域”的水印显示在 中/proc/zoneinfo
,如 中所述过程(5)。我的区域信息的摘录:
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)
当前的“水印”是min
、low
、 和high
。如果程序曾经要求足够的内存以减少free
到以下min
,则程序进入“直接回收”。当内核释放内存时,程序会等待。
如果可能的话,我们希望避免直接回收。因此,如果free
低于low
水位线,内核就会被唤醒kswapd
。 kswapd
通过交换和/或删除缓存来释放内存,直到再次free
出现上述情况high
。
附加资格:kswapd
还将运行以保护完整的 lowmem_reserve 量,以供内核 lowmem 和 DMA 使用。默认低内存储备大约是 RAM(DMA32 区域)前 4GiB 的 1/256,因此通常约为 16MiB。
Linux 代码提交
[...]
水印比例因子:
该因素控制 kswapd 的攻击性。它定义了 kswapd 被唤醒之前节点/系统中剩余的内存量以及 kswapd 重新进入睡眠状态之前需要释放多少内存。
单位为 10,000 的分数。默认值 10 表示水印之间的距离是节点/系统中可用内存的 0.1%。最大值为 1000,即内存的 10%。
进入直接回收 (allocstall) 或 kswapd 过早进入睡眠状态 (kswapd_low_wmark_hit_quickly) 的线程比例较高,可能表明 kswapd 出于延迟原因维护的空闲页数对于系统中发生的分配突发来说太小。然后可以使用该旋钮相应地调整 kswapd 的攻击性。
MemAvailable
中的项目是/proc/meminfo
为了提示用户有多少内存可分配而不会导致交换,因此它排除区域的低水位线作为用户空间不可用。然而,对于用户空间分配,
kswapd
实际上会回收,直到空闲页面达到高水位线和页面分配器的低内存保护的组合,该保护也从用户空间保留一定量的 DMA 和 DMA32 内存。计算 MemAvailable 时,从可用页数中减去我们已知的用户空间不可用的全部数量。
Linux代码
有时声称更改swappiness
为0
将有效禁用“机会交换”。这提供了一个有趣的调查途径。如果存在所谓的“机会交换”,并且它可以通过 swappiness 进行调整,那么我们可以通过查找所有读取的调用链来追踪它vm_swappiness
。请注意,我们可以通过假设CONFIG_MEMCG
未设置(即“内存 cgroup”被禁用)来减少搜索空间。调用链如下:
shrink_node_memcg
注释为“这是基本的每节点页面释放器。由 kswapd 和直接回收使用”。即这个函数增加了自由的页。它不会尝试复制页面进行交换,以便稍后可以释放它们。但即使我们不考虑这一点:
上面的链是从三个不同的函数调用的,如下所示。正如预期的那样,我们可以将调用站点分为直接回收和 kswapd。在直接回收中执行“机会主义交换”是没有意义的。
-
/* * 这是页面分配进程的直接回收路径。只有我们 * 尝试从满足调用者分配的区域回收页面 * 要求。 * * 如果某个区域被认为充满了固定页面,那么只需给它一个指示灯 * 扫描然后放弃。 */ 静态无效收缩区域
-
* kswapd 缩小等于或低于最高可用值的页面节点 * 当前不平衡的区域。 * * 如果 kswapd 扫描了至少所请求的页面数,则返回 true * 回收或者如果缺乏进度是由于页面正在写回。 * 用于确定是否需要提高扫描优先级。 */ 静态布尔值kswapd_shrink_node
-
* 对于kswapd,balance_pgdat()将从区域中跨节点回收页面 * 呼叫者可以使用这些区域,直到至少有一个区域可用 * 平衡。 * * 返回kswapd完成回收的顺序。 * * kswapd 扫描 highmem->normal->dma 方向的区域。它会跳过 * 具有 free_pages > high_wmark_pages(zone) 的区域,但是一旦区域 * 发现 free_pages <= high_wmark_pages(zone),该区域中的任何页面 * 或更低的值有资格回收,直到至少一个可用区域被回收 * 平衡。 */ 静态整型余额_pgdat
因此,大概的说法是 kswapd 会以某种方式被唤醒,即使所有内存分配都立即从可用内存中得到满足。我查看了 的用途wake_up_interruptible(&pgdat->kswapd_wait)
,但没有看到任何这样的唤醒。
答案2
不,Linux 中不存在机会交换这样的事情。我花了一些时间研究这个问题,所有的来源(教科书、内核开发人员邮件列表上的电子邮件、Linux 源代码和提交评论,以及与 Mel Gorman 的一些 Twitter 交流)都告诉我同样的事情:Linux 只回收记忆以应对某种形式的记忆压力(冬眠显然是例外)。
关于这个主题的所有流行误解可能都源于一个简单的事实:Linux 不能等到最后一个字节的可用内存才开始交换。它需要某种缓冲来保护它免受极端形式的内存耗尽的影响,并且有一些可调参数可以影响该缓冲的大小(例如vm.min_free_kbytes
)。但这与“因为没有更好的事情可做而交换”不同。
不幸的是,页框回收算法相对于 2.6 变得更加复杂(Mel Gorman 的书中详细描述了它),但基本思想或多或少是相同的:页面回收是由失败的分配触发的,然后唤醒kswapd
或尝试同步释放页面(取决于内存压力、分配标志和其他因素)。
页面分配可能在剩余足够的可用内存的情况下开始失败的最明显原因是它们可能要求连续的内存,而实际上内存可能过于碎片化而无法满足请求。从历史上看,Linux 内核开发人员竭尽全力避免连续分配的需要。尽管如此,一些设备驱动程序仍然需要这样做——要么是因为它们无法执行多页内存 I/O(分散-聚集 DMA),要么可能只是驱动程序开发人员的草率编码。透明大页 (THP) 的出现为在物理连续块中分配内存提供了另一个理由。
大约在同一时间引入的区域压缩应该有助于解决内存碎片问题,但它并不总是能产生预期的效果。
有各种vmscan
跟踪点可以帮助您了解特定情况下到底发生了什么——当具有特定的调用堆栈时,在 Linux 内核代码中找到您需要的东西总是更容易,而不是仅仅扫描看起来远程相关的所有内容。