原文链接 Remote memory control-group charging

译者:弃余

Memory control groups (以下简称 Memcg) 用来跟踪和限制进程的内存使用。通常情况下,我们不希望出现这样的行为,即归属于一个 memcg 的内存使用被计数到另一个 memcg 上。然而,Shakeel Butt 在 LSFMM Summit 2019 上提出,这种行为(以下简称 remote charging)在很多情况下会发生。Remote charging 通常是个问题,但偶尔也是一个有用的特性。

Remote charging 最常见的场景之一是,由多个 memcg 共享的一页内存被交换出去。当该页被换回时,它会被计数到起初分配它的 memcg,即使这个 memcg 已经很久不用该页。这可能会导致令人惊讶的情况,例如一个 memcg 中的 page fault 会导致另一个 memcg 的内存回收甚至 OOM(out of memory)(译者理解:memcg-1 page fault,请求的页面是共享页面,且被换出 -> page(s) swapin -> 请求的页面起初是 memcg-2 分配的,换回时计数到 memcg-2 -> 计数后可能由于 memory.limit/memory.high 等限制触发内存回收,甚至回收失败导致 OOM)。Remote charging 还有可能发生在 userfaultfd() 的使用场景中,控制进程(译者理解:UFFD ioctl 相关进程)所分配的内存会被计数到缺页处理进程上。这种行为可以通过 ptrace() 跟踪进程观察到,甚至可以显示调用 get_user_pages_remote 来观察。有时候,内核空间中分配的内存也会被 remote charged。

Michal Hocko 特别提问了 userfaultfd(),想知道控制过程与缺页处理进程运行在不同 memcg 中的频率。 Shakeel Butt 回答说他不知道,但是 remote charing 是有记录的。 Michal Hocko 认为在不同的组中运行通常不是一个好主意,并表示他不愿意将代码复杂化以用于只是理论发生但在现实世界中不太可能发生的用例。

Shakeel Butt 提到的一个内核用例是当在页面上触发写回时,缓冲区头(buffer_head)的分配。 这可能会在全局内存回收过程或者其他路径上发生。 这些缓冲区头将被计数到回写页面归属的 memcg 中,即使该 memcg 与触发回写的任务没有任何关系(译者理解:memcg-1 触发 global reclaim,导致 memcg-2 的页面被全局回收写回)。

Remote charging 特殊点在于它绕过了 memory.high的限制(译者理解:memory.highmemory.limit_in_bytes 功能类似,但更为严格),该限制不会发生在 remote charged 的内存分配上。Rik van Riel 提问了这种问题的发生频率;Shakeel Butt没有确切的回答,只是说他已经看到此类现象。Michal Hocko 表示,如果 1% 的内存分配是以这种方式进行 remote charged,其实真的没多大影响;某个 memcg 中进程的内存分配总还是会被计数,并最终触发限制。但是在内存分配极不均匀的用例中,这可能是个问题。

某些场景下,remote charging 的用例真实存在。一些用户希望通过将 VM (virtual machine) 和 VMM (vm monitor) 运行在不同的 memcg 中来分离虚拟化负载。显然,这么做的主要目的是为了获取 VMM 的运行时开销。目前做法是创建一个 tmpfs 文件系统,并绑定到自己的 memcg;所有的内存分配都将计入该 memcg。但是仅有一个小问题:如果 VM 被销毁(例如被 OOM 杀死),tmpfs 文件系统会保留,一直占据内存。为了解决这个问题,内核添加了比较 hack 的方式,以发送有关 OOM kill 的特殊通知,以便有人可以清理 tmpfs 文件。

Shakeel Butt 描述了他的新想法:tmpfs 文件可以附加到一个特殊的虚拟进程中,该进程会被 OOM killer 感知。当进程被杀死时,文件将被截断;它本质上是 OOM-killable tmpfs 文件。 Johannes Weiner 表示,这种机制过于具体,无法推到 upstream。 不过,Van Riel 建议可以提一个补丁实现一个新的 tmpfs mount flag,从而推到 upstream。 如果附加到指定挂载点的 memcg 被杀,那么对应的文件系统将自动删除其内容。

Hugh Dickins 表示,OOM kill 和 tmpfs 的问题很常见,因此最好在这里找到某种解决方案。Weiner 说实现它的最佳位置可能是在 oomd (user-space OOM daemon) 中。 这种策略很难放入内核的 OOM killer 逻辑中,因为内核 OOM killer 主要是为了保护整个系统。 特定资源控制策略的实现也许最好放在用户空间进程中。