内存资源控制器¶
警告
本文档已完全过时,需要完全重写。它仍然包含有用的信息,所以我们保留在此处,但如果您需要更深入的了解,请务必检查当前代码。
注意
本文档中,内存资源控制器通常被称为内存控制器。不要将此处使用的内存控制器与硬件中使用的内存控制器混淆。
提示
当我们提到带有内存控制器的 cgroup(cgroupfs 的目录)时,我们称其为“内存 cgroup”。当您查看 git-log 和源代码时,您会看到补丁的标题和函数名称倾向于使用“memcg”。在本文档中,我们避免使用它。
内存控制器的优点和目的¶
内存控制器将一组任务的内存行为与系统的其余部分隔离。关于 LWN 的文章 [12] 提到了内存控制器的一些可能用途。内存控制器可用于
隔离应用程序或一组应用程序。可以将内存密集型应用程序隔离并限制在较小的内存量中。
创建具有有限内存量的 cgroup;这可以作为使用 mem=XXXX 引导的良好替代方案。
虚拟化解决方案可以控制他们想要分配给虚拟机实例的内存量。
CD/DVD 刻录机可以控制系统其余部分使用的内存量,以确保刻录不会因可用内存不足而失败。
还有其他几个用例;找到一个或仅为了好玩而使用控制器(以学习和破解 VM 子系统)。
当前状态:linux-2.6.34-mmotm(2010 年 4 月的开发版本)
功能
统计匿名页面、文件缓存、交换缓存的使用情况并限制它们。
页面专门链接到每个 memcg 的 LRU,并且没有全局 LRU。
可选地,可以统计和限制内存+交换的使用情况。
分层统计
软限制
移动任务时,可以选择移动(重新分配)帐户。
使用阈值通知器
内存压力通知器
oom 杀手禁用旋钮和 oom 通知器
根 cgroup 没有限制控制。
内核内存支持正在进行中,当前版本提供了基本功能。(请参阅第 2.7 节)
控制文件的简要摘要。
tasks |
附加一个任务(线程)并显示线程列表 |
cgroup.procs |
显示进程列表 |
cgroup.event_control |
event_fd() 的接口。此旋钮在 CONFIG_PREEMPT_RT 系统上不可用。 |
memory.usage_in_bytes |
显示当前内存使用量(详细信息请参阅 5.5) |
memory.memsw.usage_in_bytes |
显示当前内存+交换使用量(详细信息请参阅 5.5) |
memory.limit_in_bytes |
设置/显示内存使用量的限制 |
memory.memsw.limit_in_bytes |
设置/显示内存+交换使用量的限制 |
memory.failcnt |
显示内存使用量达到限制的次数 |
memory.memsw.failcnt |
显示内存+交换达到限制的次数 |
memory.max_usage_in_bytes |
显示记录的最大内存使用量 |
memory.memsw.max_usage_in_bytes |
显示记录的最大内存+交换使用量 |
memory.soft_limit_in_bytes |
设置/显示内存使用量的软限制。此旋钮在 CONFIG_PREEMPT_RT 系统上不可用。此旋钮已弃用,不应使用。 |
memory.stat |
显示各种统计信息 |
memory.use_hierarchy |
设置/显示启用分层帐户。此旋钮已弃用,不应使用。 |
memory.force_empty |
触发强制页面回收 |
memory.pressure_level |
设置内存压力通知。此旋钮已弃用,不应使用。 |
memory.swappiness |
设置/显示 vmscan 的交换性参数(请参阅 sysctl 的 vm.swappiness) |
memory.move_charge_at_immigrate |
此旋钮已弃用。 |
memory.oom_control |
设置/显示 oom 控制。此旋钮已弃用,不应使用。 |
memory.numa_stat |
显示每个 numa 节点的内存使用量 |
memory.kmem.limit_in_bytes |
用于设置和读取内核内存硬限制的已弃用旋钮。自 5.16 起不支持内核硬限制。向 do 文件写入任何值都不会有任何效果,就像指定了 nokmem 内核参数一样。内核内存仍然由 memory.kmem.usage_in_bytes 收费和报告。 |
memory.kmem.usage_in_bytes |
显示当前内核内存分配 |
memory.kmem.failcnt |
显示内核内存使用量达到限制的次数 |
memory.kmem.max_usage_in_bytes |
显示记录的最大内核内存使用量 |
memory.kmem.tcp.limit_in_bytes |
设置/显示 tcp buf 内存的硬限制。此旋钮已弃用,不应使用。 |
memory.kmem.tcp.usage_in_bytes |
显示当前 tcp buf 内存分配。此旋钮已弃用,不应使用。 |
memory.kmem.tcp.failcnt |
显示 tcp buf 内存使用量达到限制的次数。此旋钮已弃用,不应使用。 |
memory.kmem.tcp.max_usage_in_bytes |
显示记录的最大 tcp buf 内存使用量。此旋钮已弃用,不应使用。 |
1. 历史¶
内存控制器有着悠久的历史。Balbir Singh 发布了关于内存控制器的征求意见稿 [1]。在发布征求意见稿时,已经存在多种内存控制的实现。该征求意见稿的目标是就内存控制所需的最少功能达成共识和协议。第一个 RSS 控制器由 Balbir Singh 于 2007 年 2 月发布 [2]。Pavel Emelianov [3] [4] [5] 此后发布了三个版本的 RSS 控制器。在 OLS 的资源管理 BoF 中,大家都建议我们将页面缓存和 RSS 一起处理。另一个要求是允许用户空间处理 OOM。当前内存控制器是第 6 版;它结合了映射(RSS)和未映射的页面缓存控制 [11]。
2. 内存控制¶
内存是一种独特的资源,因为它数量有限。如果一个任务需要大量的 CPU 处理,该任务可以将处理分散到数小时、数天、数月甚至数年,但对于内存,需要重用相同的物理内存来完成任务。
内存控制器的实现已分为几个阶段。这些是
内存控制器
mlock(2) 控制器
内核用户内存记账和 slab 控制
用户映射长度控制器
内存控制器是第一个开发的控制器。
2.1. 设计¶
设计的核心是一个名为 page_counter 的计数器。page_counter 跟踪与控制器关联的进程组的当前内存使用情况和限制。每个 cgroup 都有一个与其关联的内存控制器特定数据结构 (mem_cgroup)。
2.2. 记账¶
+--------------------+
| mem_cgroup |
| (page_counter) |
+--------------------+
/ ^ \
/ | \
+---------------+ | +---------------+
| mm_struct | |.... | mm_struct |
| | | | |
+---------------+ | +---------------+
|
+ --------------+
|
+---------------+ +------+--------+
| page +----------> page_cgroup|
| | | |
+---------------+ +---------------+
图 1 显示了控制器的重要方面
记账是按 cgroup 进行的
每个 mm_struct 都知道它属于哪个 cgroup
每个页面都有一个指向 page_cgroup 的指针,而 page_cgroup 又知道它属于哪个 cgroup
记账按以下方式完成:调用 mem_cgroup_charge_common() 来设置必要的数据结构并检查正在计费的 cgroup 是否超出其限制。如果超出限制,则在该 cgroup 上调用回收。更多详细信息可以在本文档的回部分中找到。如果一切顺利,则会更新名为 page_cgroup 的页面元数据结构。page_cgroup 在 cgroup 上有自己的 LRU。(*) page_cgroup 结构在启动/内存热插拔时分配。
2.2.1 记账详细信息¶
所有映射的匿名页面 (RSS) 和缓存页面(页面缓存)都会被记账。一些永远无法回收且不会在 LRU 上的页面不会被记账。我们只对通常 VM 管理下的页面进行记账。
RSS 页面在发生页面错误时进行记账,除非它们之前已经被记账过。当文件页面插入到 inode (xarray) 中时,它将作为页面缓存进行记账。虽然它被映射到进程的页表中,但会小心避免重复记账。
当 RSS 页面完全取消映射时,则不进行记账。当页面缓存页面从 xarray 中删除时,则不进行记账。即使 RSS 页面被完全取消映射(通过 kswapd),它们也可能作为 SwapCache 存在于系统中,直到它们真正被释放。此类 SwapCache 也会被记账。换入页面在添加到 swapcache 后进行记账。
注意:内核会执行 swapin-readahead 并一次读取多个交换分区。由于页面的 memcg 记录到任何启用 memsw 的交换分区中,因此页面将在 swapin 后进行记账。
在页面迁移时,记账信息会被保留。
注意:我们只对 LRU 上的页面进行记账,因为我们的目的是控制使用的页面数量;从 VM 视图来看,不在 LRU 上的页面往往是失控的。
2.4 交换扩展¶
始终为每个 cgroup 记录交换使用情况。交换扩展允许您读取和限制它。
当启用 CONFIG_SWAP 时,会添加以下文件。
memory.memsw.usage_in_bytes。
memory.memsw.limit_in_bytes。
memsw 表示内存 + 交换分区。内存 + 交换分区的使用受 memsw.limit_in_bytes 限制。
示例:假设一个系统有 4G 的交换分区。在 2G 内存限制下分配 6G 内存(错误)的任务将使用所有交换分区。在这种情况下,设置 memsw.limit_in_bytes=3G 将防止滥用交换分区。通过使用 memsw 限制,您可以避免由交换分区短缺引起的系统 OOM。
2.4.1 为什么是 “内存 + 交换分区” 而不是交换分区¶
全局 LRU(kswapd) 可以换出任意页面。换出意味着将帐户从内存移动到交换分区...内存 + 交换分区的使用情况没有变化。换句话说,当我们想要限制交换分区的使用而不影响全局 LRU 时,从操作系统的角度来看,限制内存 + 交换分区比仅限制交换分区更好。
2.4.2. 当 cgroup 达到 memory.memsw.limit_in_bytes 时会发生什么¶
当 cgroup 达到 memory.memsw.limit_in_bytes 时,在此 cgroup 中进行换出是没有用的。然后,cgroup 例程不会进行换出,并且会删除文件缓存。但如上所述,全局 LRU 可以从中换出内存,以保持系统内存管理状态的健全性。您无法通过 cgroup 禁止它。
2.5 回收¶
每个 cgroup 维护一个与全局 VM 具有相同结构的每个 cgroup LRU。当 cgroup 超出其限制时,我们首先尝试从 cgroup 中回收内存,以便为 cgroup 接触到的新页面腾出空间。如果回收不成功,则会调用 OOM 例程来选择并杀死 cgroup 中最庞大的任务。(请参见下面的10. OOM 控制。)
除了选择回收的页面来自每个 cgroup LRU 列表外,回收算法并未针对 cgroup 进行修改。
注意
回收不适用于根 cgroup,因为我们无法对根 cgroup 设置任何限制。
注意
当 panic_on_oom 设置为 “2” 时,整个系统将崩溃。
当注册了 oom 事件通知器时,将传递事件。(请参见oom_control 部分)
2.6 锁定¶
锁定顺序如下
folio_lock
mm->page_table_lock or split pte_lock
folio_memcg_lock (memcg->move_lock)
mapping->i_pages lock
lruvec->lru_lock.
每个节点每个 memcgroup LRU(cgroup 的私有 LRU)由 lruvec->lru_lock 保护;在从 lruvec->lru_lock 下的 LRU 中隔离页面之前,会清除 folio LRU 标志。
2.7 内核内存扩展¶
通过内核内存扩展,内存控制器能够限制系统使用的内核内存量。内核内存与用户内存有着根本的不同,因为它不能被换出,这使得可以通过消耗太多这种宝贵的资源来对系统进行 DoS 攻击。
默认情况下,为所有内存 cgroup 启用内核内存记账。但可以通过在启动时将 cgroup.memory=nokmem 传递给内核来在系统范围内禁用它。在这种情况下,将完全不进行内核内存记账。
不为根 cgroup 强制执行内核内存限制。根 cgroup 的使用可能会或可能不会被记账。使用的内存会累积到 memory.kmem.usage_in_bytes 中,或者在有意义时累积到单独的计数器中。(目前仅适用于 tcp)。
主 “kmem” 计数器被馈送到主计数器,因此 kmem 收费也将从用户计数器中可见。
目前没有为内核内存实现软限制。当达到这些限制时触发 slab 回收是未来的工作。
2.7.1 当前记账的内核内存资源¶
- 堆栈页面
每个进程都会消耗一些堆栈页面。通过记入内核内存,我们可以防止在内核内存使用量过高时创建新进程。
- slab 页面
跟踪由 SLAB 或 SLUB 分配器分配的页面。每次从 memcg 内部首次触摸缓存时,都会创建每个 kmem_cache 的副本。创建是惰性完成的,因此在创建缓存时,仍可以跳过某些对象。slab 页面中的所有对象都应属于同一 memcg。仅当任务在缓存分配页面期间迁移到不同的 memcg 时,此操作才会失败。
- 套接字内存压力
某些套接字协议具有内存压力阈值。内存控制器允许它们按 cgroup 单独控制,而不是全局控制。
- tcp 内存压力
tcp 协议的套接字内存压力。
2.7.2 常见用例¶
由于 “kmem” 计数器被馈送到主用户计数器,因此内核内存永远无法完全独立于用户内存进行限制。假设 “U” 是用户限制,“K” 是内核限制。可以通过三种可能的方式设置限制
- U != 0, K = 无限制
这是 kmem 记账之前已经存在的标准 memcg 限制机制。内核内存被完全忽略。
- U != 0, K < U
内核内存是用户内存的子集。这种设置在每个 cgroup 的总内存量被过度提交的部署中很有用。绝对不建议过度提交内核内存限制,因为系统仍然会耗尽不可回收的内存。在这种情况下,管理员可以设置 K,以便所有组的总和永远不大于总内存,并且可以自由设置 U,但代价是其 QoS。
警告
在当前实现中,当 cgroup 达到 K 且保持在 U 以下时,不会为此 cgroup 触发内存回收,这使得这种设置不切实际。
- U != 0, K >= U
由于 kmem 收费也将馈送到用户计数器,并且将为这两种类型的内存触发 cgroup 的回收。这种设置使管理员可以统一查看内存,并且对于只想跟踪内核内存使用情况的人也很有用。
3. 用户界面¶
要使用用户界面
启用 CONFIG_CGROUPS 和 CONFIG_MEMCG 选项
准备 cgroup(有关背景信息,请参阅 为什么需要 cgroup?)
# mount -t tmpfs none /sys/fs/cgroup # mkdir /sys/fs/cgroup/memory # mount -t cgroup none /sys/fs/cgroup/memory -o memory
创建新组并将 bash 移入其中
# mkdir /sys/fs/cgroup/memory/0 # echo $$ > /sys/fs/cgroup/memory/0/tasks
由于现在我们位于 0 cgroup 中,我们可以更改内存限制
# echo 4M > /sys/fs/cgroup/memory/0/memory.limit_in_bytes
现在可以查询该限制
# cat /sys/fs/cgroup/memory/0/memory.limit_in_bytes 4194304
注意
我们可以使用后缀(k、K、m、M、g 或 G)来表示千字节、兆字节或千兆字节的值。(这里,千字节、兆字节、千兆字节分别为 Kibibytes、Mebibytes、Gibibytes。)
注意
我们可以写入 “-1” 来重置 *.limit_in_bytes(无限制)
。
注意
我们不能再在根 cgroup 上设置限制。
我们可以检查使用情况
# cat /sys/fs/cgroup/memory/0/memory.usage_in_bytes
1216512
成功写入此文件并不保证此限制成功设置为写入该文件中的值。这可能是由于多种因素造成的,例如向上舍入到页面边界或系统上内存的总可用性。用户需要在写入后重新读取此文件,以保证内核提交的值。
# echo 1 > memory.limit_in_bytes
# cat memory.limit_in_bytes
4096
memory.failcnt 字段给出 cgroup 限制被超出次数。
memory.stat 文件提供统计信息。现在,显示缓存、RSS 和活动页面/非活动页面的数量。
4. 测试¶
有关测试功能和实现,请参阅 内存资源控制器 (Memcg) 实现备忘录。
性能测试也很重要。要查看纯内存控制器的开销,在 tmpfs 上进行测试将为您提供良好的小开销数据。示例:在 tmpfs 上执行内核 make。
页面错误的可扩展性也很重要。在测量并行页面错误测试时,多进程测试可能比多线程测试更好,因为它具有共享对象/状态的噪音。
但是以上两个都是在测试极端情况。在内存控制器下尝试常规测试始终有帮助。
4.1 故障排除¶
有时用户可能会发现 cgroup 下的应用程序被 OOM killer 终止。这有几个原因
cgroup 限制太低(太低而无法执行任何有用的操作)
用户正在使用匿名内存,并且交换已关闭或太低
sync 后跟 echo 1 > /proc/sys/vm/drop_caches 将有助于摆脱 cgroup 中缓存的一些页面(页面缓存页面)。
要了解发生了什么,请按照 “10. OOM 控制”(如下)禁用 OOM_Kill,看看会发生什么将很有帮助。
4.2 任务迁移¶
当任务从一个 cgroup 迁移到另一个 cgroup 时,其费用默认情况下不会结转。从原始 cgroup 分配的页面仍然向其收费,当页面被释放或回收时,费用会减少。
您可以随着任务迁移移动任务的费用。请参阅 8. “在任务迁移时移动费用”
4.3 删除 cgroup¶
可以通过 rmdir 删除 cgroup,但正如在 第 4.1 节 和 第 4.2 节 中所讨论的那样,即使所有任务都已从中迁移,cgroup 也可能具有与之相关的某些费用。(因为我们是针对页面收费,而不是针对任务收费。)
我们将统计信息移动到父级,并且除了从子级解除收费外,费用没有变化。
交换信息中记录的费用不会在删除 cgroup 时更新。记录的信息将被丢弃,并且使用交换(交换缓存)的 cgroup 将被收取新所有者的费用。
5. 其他接口¶
5.1 force_empty¶
提供 memory.force_empty 接口以使 cgroup 的内存使用为空。当向其写入任何内容时
# echo 0 > memory.force_emptycgroup 将被回收,并尽可能多地回收页面。
此接口的典型用例是在调用 rmdir() 之前。虽然 rmdir() 会使 memcg 脱机,但由于已收费的文件缓存,memcg 可能仍然存在。一些不再使用的页面缓存可能会一直保持收费状态,直到内存压力出现。如果您想避免这种情况,force_empty 将很有用。
5.2 stat 文件¶
memory.stat 文件包括以下统计信息
每个内存 cgroup 的本地状态
缓存
页面缓存内存的字节数。
rss
匿名和交换缓存内存的字节数(包括透明巨页)。
rss_huge
匿名透明巨页的字节数。
mapped_file
映射文件的字节数(包括 tmpfs/shmem)
pgpgin
到内存 cgroup 的收费事件数。每当一个页面被计为映射的匿名页面 (RSS) 或缓存页面(页面缓存)到 cgroup 时,就会发生收费事件。
pgpgout
到内存 cgroup 的解除收费事件数。每当一个页面从 cgroup 中取消记帐时,就会发生解除收费事件。
交换
交换使用量的字节数
swapcached
缓存在内存中的交换字节数
脏
等待写回磁盘的字节数。
写回
排队等待同步到磁盘的文件/匿名缓存字节数。
inactive_anon
非活动 LRU 列表中匿名和交换缓存内存的字节数。
active_anon
活动 LRU 列表中匿名和交换缓存内存的字节数。
inactive_file
非活动 LRU 列表中文件支持的内存和 MADV_FREE 匿名内存(LazyFree 页面)的字节数。
active_file
活动 LRU 列表中文件支持的内存的字节数。
unevictable
无法回收的内存的字节数(mlocked 等)。
考虑层次结构的状态(请参阅 memory.use_hierarchy 设置)
hierarchical_memory_limit
内存 cgroup 所在的层次结构下的内存限制的字节数
hierarchical_memsw_limit
内存 cgroup 所在的层次结构下的内存 + 交换限制的字节数。
total_<计数器>
<计数器> 的层次结构版本,除了 cgroup 自身的值外,还包括所有层次结构子级的 <计数器> 值之和,例如 total_cache
其他 vm 参数(取决于 CONFIG_DEBUG_VM)
recent_rotated_anon
VM 内部参数。(请参阅 mm/vmscan.c)
recent_rotated_file
VM 内部参数。(请参阅 mm/vmscan.c)
recent_scanned_anon
VM 内部参数。(请参阅 mm/vmscan.c)
recent_scanned_file
VM 内部参数。(请参阅 mm/vmscan.c)
提示
recent_rotated 表示 LRU 最近的轮换频率。recent_scanned 表示 LRU 最近的扫描次数。为了更好地进行调试,请参阅代码了解其含义。
注意
只有匿名和交换缓存内存才被列为“rss”统计信息的一部分。这不应与真正的“常驻集大小”或 cgroup 使用的物理内存量混淆。
“rss + mapped_file”将为您提供 cgroup 的常驻集大小。
(注意:文件和 shmem 可能在其他 cgroup 之间共享。在这种情况下,仅当内存 cgroup 是页面缓存的所有者时,才会计算 mapped_file。)
5.3 swappiness¶
覆盖特定组的 /proc/sys/vm/swappiness。根 cgroup 中的可调值对应于全局交换设置。
请注意,与全局回收期间不同,限制回收强制执行 0 交换度,即使存在可用的交换存储,也确实可以防止任何交换。如果没有可回收的文件页面,这可能会导致 memcg OOM killer。
5.4 failcnt¶
内存 cgroup 提供 memory.failcnt 和 memory.memsw.failcnt 文件。此 failcnt(== 失败计数)显示使用计数器达到其限制的次数。当内存 cgroup 达到限制时,failcnt 会增加,并且会回收其下的内存。
您可以通过将 0 写入 failcnt 文件来重置 failcnt
# echo 0 > .../memory.failcnt
5.5 usage_in_bytes¶
为了提高效率,与其他内核组件一样,内存 cgroup 使用一些优化来避免不必要的缓存行伪共享。usage_in_bytes 受该方法的影响,不会显示内存(和交换)使用的“确切”值,它是一个用于有效访问的模糊值。(当然,在必要时,它会被同步。)如果您想了解更确切的内存使用情况,则应使用 memory.stat 中的 RSS+CACHE(+SWAP) 值(请参阅 5.2)。
5.6 numa_stat¶
这类似于 numa_maps,但基于每个 memcg 进行操作。这对于提供 memcg 内 numa 局部性信息的可视性很有用,因为允许从任何物理节点分配页面。用例之一是通过将此信息与应用程序的 CPU 分配相结合来评估应用程序性能。
每个 memcg 的 numa_stat 文件都包含每个节点的“total”、“file”、“anon”和“unevictable”页面计数,包括“hierarchical_<计数器>”,该计数器除了 memcg 自身的值外,还汇总了所有分层子级的值。
memory.numa_stat 的输出格式为
total=<total pages> N0=<node 0 pages> N1=<node 1 pages> ...
file=<total file pages> N0=<node 0 pages> N1=<node 1 pages> ...
anon=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
unevictable=<total anon pages> N0=<node 0 pages> N1=<node 1 pages> ...
hierarchical_<counter>=<counter pages> N0=<node 0 pages> N1=<node 1 pages> ...
“total”计数是 file + anon + unevictable 的总和。
6. 层次结构支持¶
内存控制器支持深度层次结构和分层记帐。通过在 cgroup 文件系统中创建相应的 cgroup 来创建层次结构。例如,考虑以下 cgroup 文件系统层次结构
root
/ | \
/ | \
a b c
| \
| \
d e
在上图中,启用分层记帐后,e 的所有内存使用量都将计入其祖先,直到根(即 c 和根)。如果某个祖先超过其限制,则回收算法将从祖先和祖先的子项中的任务中回收。
6.1 分层记帐和回收¶
默认情况下启用分层记帐。禁用分层记帐已弃用。尝试这样做会导致失败,并在 dmesg 中打印警告。
出于兼容性原因,向 memory.use_hierarchy 写入 1 始终会通过
# echo 1 > memory.use_hierarchy
7. 软限制(已弃用)¶
此功能已弃用!
软限制允许更大程度地共享内存。软限制背后的想法是允许控制组根据需要使用尽可能多的内存,前提是
不存在内存争用
它们不超过其硬限制
当系统检测到内存争用或内存不足时,控制组会被推回到其软限制。如果每个控制组的软限制非常高,则会尽可能地将其推回,以确保一个控制组不会使其他控制组的内存不足。
请注意,软限制是一项尽力而为的功能;它不提供任何保证,但它会尽力确保在内存高度争用时,内存会根据软限制提示/设置进行分配。目前,基于软限制的回收的设置方式使其从 balance_pgdat (kswapd) 调用。
7.1 接口¶
可以使用以下命令设置软限制(在此示例中,我们假设软限制为 256 MiB)
# echo 256M > memory.soft_limit_in_bytes
如果我们要将其更改为 1G,我们可以随时使用
# echo 1G > memory.soft_limit_in_bytes
注意
软限制会在很长一段时间内生效,因为它们涉及到回收内存以在内存 cgroup 之间进行平衡
注意
建议始终将软限制设置在硬限制之下,否则硬限制将优先生效。
8. 任务迁移时移动费用(已弃用!)¶
此功能已弃用!
读取 memory.move_charge_at_immigrate 将始终返回 0,写入它将始终返回 -EINVAL。
9. 内存阈值¶
内存 cgroup 使用 cgroups 通知 API(请参阅 控制组)实现内存阈值。 它允许注册多个内存和 memsw 阈值,并在跨越这些阈值时接收通知。
要注册阈值,应用程序必须
使用 eventfd(2) 创建一个 eventfd;
打开 memory.usage_in_bytes 或 memory.memsw.usage_in_bytes;
将类似 “<event_fd> <memory.usage_in_bytes 的 fd> <阈值>” 的字符串写入 cgroup.event_control。
当内存使用量在任何方向上跨越阈值时,应用程序将通过 eventfd 接收通知。
它适用于 root 和非 root cgroup。
10. OOM 控制(已弃用)¶
此功能已弃用!
memory.oom_control 文件用于 OOM 通知和其他控制。
内存 cgroup 使用 cgroup 通知 API(请参阅 控制组)实现 OOM 通知器。 它允许注册多个 OOM 通知传递,并在发生 OOM 时接收通知。
要注册通知器,应用程序必须
使用 eventfd(2) 创建一个 eventfd
打开 memory.oom_control 文件
将类似 “<event_fd> <memory.oom_control 的 fd>” 的字符串写入 cgroup.event_control
当发生 OOM 时,应用程序将通过 eventfd 接收通知。OOM 通知不适用于 root cgroup。
您可以通过向 memory.oom_control 文件写入“1”来禁用 OOM killer,如下所示:
#echo 1 > memory.oom_control
如果禁用了 OOM killer,则当 cgroup 下的任务请求可计费内存时,它们将在内存 cgroup 的 OOM 等待队列中挂起/休眠。
要运行它们,您必须通过以下方式放松内存 cgroup 的 OOM 状态:
扩大限制或减少使用量。
要减少使用量,
杀死一些任务。
将一些任务通过账户迁移移动到其他组。
删除一些文件(在 tmpfs 上?)
然后,停止的任务将再次工作。
在读取时,会显示 OOM 的当前状态。
oom_kill_disable 0 或 1(如果为 1,则禁用 oom-killer)
under_oom 0 或 1(如果为 1,则内存 cgroup 处于 OOM 状态,任务可能会停止。)
oom_kill 整数计数器 属于此 cgroup 的进程被任何类型的 OOM killer 杀死的次数。
11. 内存压力(已弃用)¶
此功能已弃用!
压力级别通知可用于监视内存分配成本;根据压力,应用程序可以实现不同的内存资源管理策略。 压力级别定义如下:
“low” 级别表示系统正在为新的分配回收内存。监视此回收活动可能有助于维护缓存级别。收到通知后,程序(通常是“Activity Manager”)可能会分析 vmstat 并提前采取行动(例如,过早关闭不重要的服务)。
“medium” 级别表示系统正在经历中等内存压力,系统可能会进行交换、分页输出活动文件缓存等。在此事件发生时,应用程序可能会决定进一步分析 vmstat/zoneinfo/memcg 或内部内存使用情况统计数据,并释放任何可以轻松重建或从磁盘重新读取的资源。
“critical” 级别表示系统正在积极抖动,即将耗尽内存 (OOM),甚至内核中的 OOM killer 即将触发。应用程序应尽一切努力来帮助系统。咨询 vmstat 或任何其他统计信息可能为时已晚,因此建议立即采取行动。
默认情况下,事件会向上传播,直到事件被处理,即事件不是直通的。 例如,您有三个 cgroup:A->B->C。 现在,您在 cgroups A、B 和 C 上设置了一个事件侦听器,并假设组 C 遇到了一些压力。 在这种情况下,只有组 C 会收到通知,即组 A 和 B 不会收到通知。 这样做是为了避免消息的过度“广播”,这会干扰系统,并且如果我们内存不足或抖动,则尤其糟糕。 只有当组 C 没有事件侦听器时,组 B 才会收到通知。
有三种可选模式,它们指定不同的传播行为:
“default”:这是上面指定的默认行为。 此模式与省略可选模式参数相同,通过向后兼容性保留。
“hierarchy”:事件始终传播到根,类似于默认行为,但传播会继续,而不管每个级别是否有事件侦听器,使用 “hierarchy” 模式。 在上面的示例中,组 A、B 和 C 将收到内存压力通知。
“local”:事件是直通的,即它们仅在注册通知的 memcg 中遇到内存压力时才接收通知。 在上面的示例中,如果为“local”通知注册了组 C,并且该组遇到内存压力,则组 C 将收到通知。 但是,如果组 B 注册为本地通知,则无论组 C 是否有事件侦听器,组 B 都永远不会收到通知。
级别和事件通知模式(“hierarchy”或“local”,如果需要)由逗号分隔的字符串指定,即 “low,hierarchy” 指定对所有祖先 memcg 的分层、直通通知。 作为默认的非直通行为的通知不指定模式。“medium,local” 指定中等级别的直通通知。
memory.pressure_level 文件仅用于设置 eventfd。 要注册通知,应用程序必须
使用 eventfd(2) 创建一个 eventfd;
打开 memory.pressure_level;
将字符串 “<event_fd> <memory.pressure_level 的 fd> <level[,mode]>” 写入 cgroup.event_control。
当内存压力处于特定级别(或更高)时,应用程序将通过 eventfd 接收通知。memory.pressure_level 的读取/写入操作未实现。
测试
这是一个小脚本示例,它创建一个新的 cgroup,设置一个内存限制,在该 cgroup 中设置一个通知,然后使子 cgroup 体验到严重的压力
# cd /sys/fs/cgroup/memory/ # mkdir foo # cd foo # cgroup_event_listener memory.pressure_level low,hierarchy & # echo 8000000 > memory.limit_in_bytes # echo 8000000 > memory.memsw.limit_in_bytes # echo $$ > tasks # dd if=/dev/zero | read x(预计会收到一堆通知,最终,OOM killer 将触发。)
12. 待办事项¶
使每个 cgroup 扫描器首先回收非共享页面
教导控制器来计算共享页面
在限制尚未达到但使用量越来越接近时,在后台启动回收
总结¶
总的来说,内存控制器是一个稳定的控制器,并且在社区中得到了广泛的评论和讨论。
参考文献¶
Menage, Paul. 控制组 v10, http://lwn.net/Articles/236032/
Vaidyanathan, Srinivasan, 控制组:页面缓存记账和控制子系统 (v3), http://lwn.net/Articles/235534/
Singh, Balbir. RSS 控制器 v2 测试结果 (lmbench), https://lore.kernel.org/r/[email protected]
Singh, Balbir. RSS 控制器 v2 AIM9 结果 https://lore.kernel.org/r/[email protected]
Singh, Balbir. 内存控制器 v6 测试结果, https://lore.kernel.org/r/20070819094658.654.84837.sendpatchset@balbir-laptop
Singh, Balbir. 内存控制器简介 (v6), https://lore.kernel.org/r/20070817084228.26003.12568.sendpatchset@balbir-laptop
Corbet, Jonathan, 控制 cgroups 中的内存使用, http://lwn.net/Articles/243795/