翻译: msm-5.4/Documentation/admin-guide/cgroup-v1/memory.rst
============================
内存资源控制器
===========================
注意:
本文档已过时,需要完全重写。它仍然包含有用的信息,因此我们将其保留在这里,但如果您需要更深入的了解,请务必检查当前代码。
注意:
在本文档中,内存资源控制器通常被称为内存控制器。请勿将此处使用的内存控制器与硬件中使用的内存控制器混淆。
(供编辑使用)在本文档中:
当我们提到带有内存控制器的 cgroup(cgroupfs 的目录)时,我们将其称为“memory cgroup”。当您查看 git-log 和源代码时,您会看到补丁的标题和函数名称倾向于使用“memcg”。在本文档中,我们避免使用它。
内存控制器的优点和用途
================================================
内存控制器将一组任务的内存行为与系统其余部分隔离开来。##### LWN 上的文章 [12] 提到了内存控制器的一些可能用途。内存控制器可用于
a. 隔离一个应用程序或一组应用程序。可以隔离占用大量内存的应用程序并将其限制在较少的内存量内。
b. 创建一个具有有限内存量的 cgroup;这可以作为使用 mem=XXXX 进行引导的良好替代方案。
c. 虚拟化解决方案可以控制它们想要分配给虚拟机实例的内存量。
d. CD/DVD 刻录机可以控制系统其余部分使用的内存量,以确保刻录不会因可用内存不足而失败。
e. 还有几个其他用例;找到一个或只是为了好玩而使用控制器(学习和破解 VM 子系统)。
当前状态:linux-2.6.34-mmotm(2010 年 4 月开发版本)
功能:
- 统计匿名页面、文件缓存、交换缓存的使用情况并限制它们。
- 页面仅链接到每个 memcg LRU,没有(链接到)全局 LRU。
- 可选地,可以统计和限制 memory+swap 的使用。
- 分层统计。
- 软限制。
- 可选择在移动任务时移动(重新充电)帐户。
- 使用阈值通知。
- 内存压力通知。
- oom-killer 禁用旋钮和 oom-notifier。
- root cgroup 没有限制控制。
内核内存支持是一项正在进行的工作,当前版本提供基本功能。(参见第 2.7 节)
控制文件的简要摘要。
===============================================================================
tasks: 附加一个任务并显示线程列表。
cgroup.procs: 显示进程列表。
cgroup.event_control: 针对 event_fd() 的接口。
memory.usage_in_bytes: 显示当前内存使用情况(详情请参阅 5.5)。
memory.memsw.usage_in_bytes: 显示当前 memory+Swap 的使用情况(详情请参阅 5.5)。
memory.limit_in_bytes: 设置/显示内存使用限制。
memory.memsw.limit_in_bytes: 设置/显示 memory+Swap 的使用限制。
memory.failcnt: 显示内存使用命中限制的次数。
memory.memsw.failcnt: 显示 memory+Swap 命中限制的次数。
memory.max_usage_in_bytes: 显示记录的最大内存使用情况。
memory.memsw.max_usage_in_bytes: 显示记录的最大 memory+Swap 的使用情况。
memory.soft_limit_in_bytes: 设置/显示内存使用软限制。
memory.stat: 显示各种统计数据。
memory.use_hierarchy: 设置/显示分层帐户已启用。
memory.force_empty: 触发强制页面回收。
memory.pressure_level: 设置内存压力通知。
memory.swappiness: 设置/显示 vmscan 的 swappiness 参数(参见 sysctl 的 vm.swappiness)。
memory.move_charge_at_immigrate: 设置/显示移动charges的控制。
memory.oom_control: 设置/显示 oom 控制。
memory.numa_stat: 显示每个 numa 节点的内存使用量。
memory.kmem.limit_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. History
==========
内存控制器历史悠久。Balbir Singh [1] 发布了对内存控制器的评论请求。在发布 RFC 时,有几种内存控制实现。RFC 的目标是就内存控制所需的最小功能达成共识和协议。第一个 RSS 控制器由 Balbir Singh[2] 于 2007 年 2 月发布。Pavel Emelianov [3][4][5] 此后发布了三个版本的 RSS 控制器。在 OLS 的资源管理 BoF 上,每个人都建议我们同时处理页面缓存和 RSS。另一个请求是允许用户空间处理 OOM。##### 当前的内存控制器是版本 6;它结合了映射(RSS)和非映射页面缓存控制 [11]。#####
2. Memory Control
=================
内存是一种独特的资源,因为它的数量是有限的。如果某项任务需要大量的 CPU 处理,那么该任务可以将其处理分散到几小时、几天、几个月或几年的时间段内,但对于内存,需要重复使用相同的物理内存来完成任务。
内存控制器的实现分为几个阶段。它们是:
1. 内存控制器
2. mlock(2) 控制器
3. 内核用户内存核算和 slab 控制
4. 用户映射长度控制器
内存控制器是第一个开发的控制器。
2.1. Design
-----------
设计的核心是一个名为 page_counter 的计数器。page_counter 跟踪与控制器关联的进程组的当前内存使用情况和限制。每个 cgroup 都有一个与之关联的内存控制器特定数据结构 (mem_cgroup)。
2.2. Accounting
---------------
+--------------------+ | mem_cgroup | | (page_counter) | +--------------------+ / ^ \ / | \ +---------------+ | +---------------+ | mm_struct | |.... | mm_struct | | | | | | +---------------+ | +---------------+ | + --------------+ | +---------------+ +------+--------+ | page +---------> | page_cgroup | | | | | +---------------+ +---------------+ (Figure 1: Hierarchy of Accounting)
图1 显示了控制器的重要方面
1.记帐按 per-cgroup 进行。
2.每个 mm_struct 都知道它属于哪个 cgroup。
3.每个页面都有一个指向 page_cgroup 的指针(注: msm-5.4上看变为 mem_cgroup 结构指针了),而 page_cgroup 又知道它属于哪个 cgroup。
记帐按如下方式进行:调用 mem_cgroup_charge_common() 来设置必要的数据结构(注: 从msm-4.4开始就不存在这个函数了),并检查正在charge的 cgroup 是否超出其限制。如果是,则在此 cgroup 上调用回收。更多详细信息可在本文档的回收部分中找到。如果一切顺利,则会更新名为 page_cgroup 的页面元数据结构。page_cgroup 在 cgroup 上有自己的 LRU。
(*)page_cgroup 结构是在启动/内存热插拔时分配的。
2.2.1 Accounting details
------------------------
所有映射的 匿名页面(RSS) 和 缓存页面(Page Cache) 都将被记录。一些永远无法回收且不会出现在 LRU 上的页面不会被记录。我们只是在通常的 VM 管理下记录页面。
RSS 页面在 page_fault 时被记录,除非它们之前已经被记录过。当文件页面插入 inode (radix-tree) 时,它将被视为 Page Cache。当它被映射到进程的页表中时,会小心避免重复记录。
RSS 页面在完全取消映射时取消记入帐户。PageCache 页面在从基数树中删除时取消记入帐户。即使 RSS 页面完全取消映射(由 kswapd 完成),它们也可能作为 SwapCache 存在于系统中,直到真正释放为止。此类 SwapCache 也记入帐户。交换入的页面在映射之前不记入帐户。
注意:内核执行 swapin-readahead 并一次读取多个swaps。这意味着交换入的页面可能包含用于其他任务的页面,而不是用于导致 page fault 的任务的页面。因此,我们避免在交换入 I/O 时记入帐户。
在页面迁移时,会保留记入信息。
注意:我们只记入 LRU 上的页面,因为我们的目的是控制使用的页面数量;从 VM 的角度来看,不在 LRU 上的页面往往不受控制。
2.3 Shared Page Accounting
--------------------------
共享页面是根据首次接触方法进行核算的。首次接触页面的 cgroup 将负责该页面。此方法背后的原理是,积极使用共享页面的 cgroup 最终将为此付费(一旦从引入它的 cgroup 中取消付费 - 这将在内存压力下发生)。
但请参阅第 8.2 节:将任务移动到另一个 cgroup 时,如果已选择 move_charge_at_immigrate,则其页面可能会重新充电到新 cgroup。
例外:如果未使用 CONFIG_MEMCG_SWAP。
当您执行 swapoff 并使交换出的 shmem(tmpfs)页面强制返回内存时,页面费用将由 swapoff 的调用者而不是 shmem 的用户承担。
2.4 Swap Extension (CONFIG_MEMCG_SWAP)
--------------------------------------
Swap Extension 允许您记录交换费用。如果可能,交换入的页面将重新计入原始页面分配器。
计算交换时,将添加以下文件。
- 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。
**为什么是“memory+swap”而不是 swap**
全局 LRU(kswapd)可以交换出任意页面。交换出意味着将从内存移到交换区...memory+swap 的使用没有变化。换句话说,当我们想限制交换的使用而不影响全局 LRU 时,从操作系统的角度来看,memory+swap 限制比仅仅限制 swap 更好。
**当 cgroup 达到 memory.memsw.limit_in_bytes 时会发生什么**
当 cgroup 达到 memory.memsw.limit_in_bytes 时,在此 cgroup 中进行swap-out换出是无用的。然后,cgroup 例程将不会进行换出,文件缓存将被丢弃。但如上所述,全局 LRU 可以从中进行换出内存,以保证系统内存管理状态的健全性。您无法通过 cgroup 禁止它。
2.5 Reclaim
-----------
每个 cgroup 都维护一个与全局 VM 具有相同结构的 per-cgroup LRU。当 cgroup 超出其限制时,我们首先尝试从 cgroup 回收内存,###### 以便为 cgroup 接触的新页面腾出空间。如果回收不成功,则会调用 OOM 例程来选择并终止 cgroup 中(内存占用)最庞大的任务。(请参阅下面的 10.OOM 控制。)
回收算法并未针对 cgroup 进行修改,只是选定用于回收的页面来自每个 cgroup 的 LRU 列表。
注意1:回收不适用于 root cgroup,因为我们无法对 root cgroup 设置任何限制。
注意2:当 panic_on_oom 设置为“2”时(默认设置),整个系统将崩溃。
当 oom 事件通知程序注册时,将传递事件。(参见 oom_control 部分) //TODO: 如何注册呢?
2.6 Locking
-----------
lock_page_cgroup()/unlock_page_cgroup() 不应在 i_pages 锁下调用。(注: msm-5.4上已经不存在这两个函数了)
其他锁定顺序如下:
PG_locked。 mm->page_table_lock pgdat->lru_lock lock_page_cgroup。
在许多情况下,只调用 lock_page_cgroup()。
per-zone-per-cgroup LRU(cgroup 的私有 LRU)仅由 pgdat->lru_lock 保护,它没有自己的锁。
2.7 Kernel Memory Extension (CONFIG_MEMCG_KMEM)
-----------------------------------------------
借助内核内存扩展,内存控制器能够限制系统使用的内核内存量。内核内存与用户内存有着根本的不同,因为它无法交换出去,因此可以通过消耗过多的宝贵资源来对系统进行 DoS 攻击。
默认情况下,所有内存 cgroup 都启用了内核内存核算。但可以通过在启动时将 cgroup.memory=nokmem (msm-5.4中定义在 memcontrol.c 中) 传递给内核来在系统范围内禁用它。在这种情况下,内核内存将完全不被核算。
内核内存限制不适用于 root cgroup。root cgroup 的使用情况可能会被记录,也可能不会。使用的内存会累积到 memory.kmem.usage_in_bytes 中,或者在有意义时累积到单独的计数器中。(目前仅适用于 tcp)。
主“kmem”计数器被输入到主计数器中,因此 kmem 费用也将从用户计数器中可见。
目前内核内存没有实施软限制。当达到这些限制时,触发 slab 回收是未来的工作。
2.7.1 Current Kernel Memory resources accounted
-----------------------------------------------
stack pages:
每个进程都会消耗一些堆栈页面。通过将其计入内核内存,我们可以防止在内核内存使用率过高时创建新进程。
slab pages:
跟踪由 SLAB 或 SLUB 分配器分配的页面。每次从 memcg 内部第一次接触缓存时,都会为每个 kmem_cache 创建一个副本。创建是延迟完成的,因此在创建缓存时仍可以跳过一些对象。slab 页面中的所有对象都应属于同一个 memcg。只有在缓存分配页面期间将任务迁移到不同的 memcg 时,这种情况才会失效。
sockets memory pressure:
一些套接字协议具有内存压力阈值。内存控制器允许每个 cgroup 单独控制它们,而不是全局控制。
tcp memory pressure:
tcp 协议的套接字内存压力。
2.7.2 Common use cases
----------------------
由于“kmem”计数器被馈送到主用户计数器,因此内核内存永远无法完全独立于用户内存进行限制。假设“U”是用户限制,“K”是内核限制。有三种可能的方式可以设置限制:
U != 0, K = unlimited:
这是在 kmem 核算之前已经存在的标准 memcg 限制机制。内核内存被完全忽略。
U != 0, K < U:
内核内存是用户内存的子集。此设置在 per-cgroup 的总内存量被过度使用的部署中很有用。绝对不建议过度提交内核内存限制,因为机器仍然会用尽不可回收的内存。
在这种情况下,管理员可以设置 K,使所有组的总和永远不会大于总内存,并以牺牲其 QoS 为代价自由设置 U。
WARNING:
在当前实现中,当 cgroup 达到 K 且低于 U 时,不会触发内存回收,这使得此设置不切实际。
U != 0,K >= U:
由于 kmem charges 也将馈送到用户计数器,因此将为 cgroup 触发两种内存的回收。此设置使 admin 具有统一的内存视图,对于只想跟踪内核内存使用情况的人来说也很有用。
3. User Interface
=================
3.0. Configuration
------------------
a. Enable CONFIG_CGROUPS
b. Enable CONFIG_MEMCG
c. Enable CONFIG_MEMCG_SWAP (使用交换扩展)
d. Enable CONFIG_MEMCG_KMEM (使用 kmem 扩展)
3.1. 准备 cgroups(参见 cgroups.txt,为什么需要 cgroups?)
-------------------------------------------------------------------
# mount -t tmpfs none /sys/fs/cgroup # mkdir /sys/fs/cgroup/memory # mount -t cgroup none /sys/fs/cgroup/memory -o memory
3.2. 创建新组并将 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
注意:我们可以使用后缀(k、K、m、M、g 或 G)来表示千、兆或千兆字节的值。(此处,Kilo、Mega、Giga 分别表示千兆字节、兆字节、吉字节。)
注意:我们可以写入“-1”来重置 ``*.limit_in_bytes(unlimited)``。###### 实测,设置为-1后,值变为 9223372036854771712 = 7FFFFFFFFFFFF000, 数值很大来表示不限制。
注意:我们无法再对 root cgroup 设置限制。实测,对根组下的 memory.limit_in_bytes 文件echo -1不生效。
# cat /sys/fs/cgroup/memory/0/memory.limit_in_bytes 4194304
我们可以检查用法::
# 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 文件提供记帐信息。现在,显示 caches、RSS 和活动页面/非活动页面的数量。
4. Testing
==========
有关测试功能和实现,请参阅 memcg_test.txt(只有msm-4.4上还存在这个文件)。
性能测试也很重要。要查看纯内存控制器的开销,在 tmpfs 上进行测试将为您提供大量小开销。示例:在 tmpfs 上执行内核 make。
Page-fault 可扩展性也很重要。在测量并行page fault测试时,多进程测试可能比多线程测试更好,因为它具有共享对象/状态的噪声。
但以上两个测试的是极端情况。尝试在内存控制器下进行常规测试总是有帮助的。
4.1 Troubleshooting
-------------------
有时用户可能会发现 cgroup 下的应用程序被 OOM killer 程序终止。造成这种情况的原因有多种:
(1) cgroup 限制太低(太低以至于无法执行任何有用的操作)
(2) 用户正在使用匿名内存,并且交换已关闭或太低.
同步后跟 echo 1 > /proc/sys/vm/drop_caches 将有助于清除 cgroup 中缓存的一些页面(页面缓存pages)。
要了解发生了什么,请按照“10.OOM 控制”(下文)禁用 OOM_Kill 并查看发生了什么情况,这将很有帮助。
4.2 Task migration
------------------
当任务从一个 cgroup 迁移到另一个 cgroup 时,默认情况下不会结转其费用(charge)。从原始 cgroup 分配的页面仍由其负责,当页面被释放或回收时,费用将被删除。
您可以在任务迁移过程中移动任务费用(charges)。请参阅 8. "Move charges at task migration".
4.3 Removing a cgroup
---------------------
可以通过 rmdir 删除 cgroup,但如第 4.1 和 4.2 节所述,即使所有任务都已从 cgroup 中迁移出去,cgroup 也可能带有一些与之相关的费用(charge)。(因为我们是针对pages收费,而不是针对tasks收费。)
我们将统计数据移至根目录(如果 use_hierarchy==0)或父目录(如果 use_hierarchy==1),费用(change)不会发生任何变化,除非从子目录中取消收费。
删除 cgroup 时,交换信息中记录的费用不会更新。记录的信息将被丢弃,使用交换(swapcache)的 cgroup 将作为其新所有者收费。
有关 use_hierarchy,请参阅第 6 节。
5. Misc. interfaces
===================
5.1 force_empty
---------------
提供 memory.force_empty 接口以使 cgroup 的内存使用为空。当向此接口写入任何内容时::
# echo 0 > memory.force_empty
cgroup 将被回收,并回收尽可能多的页面。
此接口的典型用例是在调用 rmdir() 之前。虽然 rmdir() 会下线 memcg,但由于文件缓存已充电,memcg 可能仍会停留在那里。一些未使用的页面缓存可能会一直处于充电状态,#### 直到出现内存压力。如果您想避免这种情况,force_empty 会很有用。
另请注意,当设置了 memory.kmem.limit_in_bytes 时,仍会看到由于内核页面而产生的费用。这不被视为失败,写入仍将返回成功。在这种情况下,预计 memory.kmem.usage_in_bytes == memory.usage_in_bytes。
关于 use_hierarchy,请参阅第 6 节。
5.2 stat file
-------------
memory.stat 文件包含以下统计数据
per-memory cgroup local status
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
==============================================================================
cache: 页面缓存占用的内存字节数。
rss: 匿名和交换缓存内存的字节数(包括透明大页面)。
rss_huge: 匿名透明大页面的字节数。
mapped_file: 映射文件的字节数(包括 tmpfs/shmem)
pgpgin: memory cgroup 的充电事件数。每次将页面视为映射到 cgroup 的匿名页面 (RSS) 或缓存页面 (Page Cache) 时,都会发生充电事件。
pgpgout: 内存 cgroup 的取消充电事件数。每次从 cgroup 中取消页面充电时,都会发生取消充电事件。
swap: swap交换使用量的字节数
dirty: 等待写回到磁盘的字节数。
writeback: 排队等待同步到磁盘的文件/匿名缓存的字节数。
inactive_anon: 非活动 LRU 列表中的匿名和交换缓存内存的字节数。
active_anon: 活动 LRU 列表中的匿名和交换缓存内存的字节数。
inactive_file: 非活动 LRU 列表中的文件支持内存的字节数。
active_file: 活动 LRU 列表中的文件支持内存的字节数。
unevictable: 无法回收的内存字节数(mlocked 等)。
==============================================================================
考虑层次结构的状态(参见 memory.use_hierarchy 设置)
============================================================================
hierarchical_memory_limit: 内存限制字节数,与内存 cgroup 所处的层次结构有关.
hierarchical_memsw_limit: 内存 + 交换限制字节数,与内存 cgroup 所处的层次结构有关。
total_<counter>: <counter> 的层次版本,除了 cgroup 自身的值外,还包括 <counter> 所有层次子级值的总和,即 total_cache
============================================================================
以下附加统计数据依赖于 CONFIG_DEBUG_VM
========================= ========================================
recent_rotated_anon VM internal parameter. (see mm/vmscan.c)
recent_rotated_file VM internal parameter. (see mm/vmscan.c)
recent_scanned_anon VM internal parameter. (see mm/vmscan.c)
recent_scanned_file VM internal parameter. (see mm/vmscan.c)
========================= ========================================
备注:
recent_rotated 表示最近 LRU 轮换的频率。recent_scanned 表示最近对 LRU 的扫描次数。为了更好地进行调试,请参阅代码以了解含义。
注意:
只有匿名和交换缓存内存被列为“rss”统计的一部分。这不应与真正的“驻留集大小(resident set size)”或 cgroup 使用的物理内存量相混淆。
“rss +mapped_file”将为您提供 cgroup 的驻留集大小。
(注意:文件和 shmem 可能在其他 cgroup 之间共享。在这种情况下,仅当内存 cgroup 是页面缓存的所有者时,mapped_file 才会被考虑。)
5.3 swappiness
--------------
覆盖特定组的 /proc/sys/vm/swappiness。根 cgroup 中的可调参数对应于全局 swappiness 设置。
请注意,与全局回收不同,限制回收强制 0 swappiness 确实可以防止任何交换,即使有可用的交换存储也是如此。如果没有文件页面可回收,这可能会导致 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_<counter>”,它除了 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 计数的和。注: Arm64没有这个文件。
6. Hierarchy support
====================
内存控制器支持深层次结构和分层记账。通过在 cgroup 文件系统中创建适当的 cgroup 来创建层次结构。例如,请考虑以下 cgroup 文件系统层次结构:
root / | \ / | \ a b c | \ | \ d e
在上图中,启用分层核算后,e 的所有内存使用情况都会被记入其祖先,#### 直到启用了 memory.use_hierarchy 的根(即 c 和 root)。如果其中一个祖先超出其限制,则回收算法会从祖先及其子级中的任务中回收。
6.1 Enabling hierarchical accounting and reclaim
------------------------------------------------
内存 cgroup 默认禁用层次结构功能。可以通过将 1 写入根 cgroup 的 memory.use_hierarchy 文件来启用支持:
# echo 1 > memory.use_hierarchy
可以通过以下方式禁用该功能:
# echo 0 > memory.use_hierarchy
注意1:
如果 cgroup 下已创建其他组,或者父 cgroup 已启用 use_hierarchy,则启用/禁用将失败。
注意2:
当 panic_on_oom 设置为“2”时,如果任何 cgroup 中发生 OOM 事件,整个系统都会崩溃。
7. Soft limits
==============
软限制允许更大程度地共享内存。软限制背后的想法是允许控制组使用尽可能多的内存,前提是
a. 没有内存争用
b. 它们不超过其硬限制
当系统检测到内存争用或内存不足时,控制组将被推回到其软限制。如果每个控制组的软限制都非常高,则尽可能地将它们推回,以确保一个控制组不会使其他控制组内存不足。
请注意,软限制是一种尽力而为的功能;它不提供任何保证,但它会尽力确保在内存竞争激烈时,根据软限制提示/设置分配内存。目前,基于软限制的回收设置为从 balance_pgdat (kswapd) 调用。
7.1 Interface
-------------
可以使用以下命令设置软限制(在此示例中,我们假设软限制为 256 MiB)::
# echo 256M > memory.soft_limit_in_bytes
如果我们想将其更改为 1G,我们可以随时使用::
# echo 1G > memory.soft_limit_in_bytes
注意1:软限制会在很长一段时间内生效,因为它们涉及回收内存以在内存 cgroup 之间进行平衡。
注意2:建议始终将软限制设置为低于硬限制,否则硬限制将优先。
8. Move charges at task migration
=================================
用户可以将与任务相关的费用与任务迁移一起移动,即从旧 cgroup 中取消任务页面的费用并将其费用分配给新 cgroup。由于缺少页表,!CONFIG_MMU 环境中不支持此功能。
8.1 Interface
-------------
默认情况下,此功能处于禁用状态。可以通过写入目标 cgroup 的 memory.move_charge_at_immigrate 来启用(并再次禁用)。
如果您想启用它::
# echo (某个正值) > memory.move_charge_at_immigrate
注意:move_charge_at_immigrate 的每个位都有其自己的含义,表示应移动哪种类型的电荷(charge)。有关详细信息,请参阅 8.2。
注意:仅当您移动 mm->owner(即线程组的领导者)时,电荷才会移动。
注意:如果我们无法在目标 cgroup 中找到足够的空间用于任务,我们会尝试通过回收内存来腾出空间。如果我们无法腾出足够的空间,任务迁移可能会失败。
注意:如果您移动电荷很多,则可能需要几秒钟。
如果您想再次禁用它::
# echo 0 > memory.move_charge_at_immigrate
8.2 Type of charges which can be moved
--------------------------------------
中的每个位都有其含义,表示应移动哪种类型的费用。但无论如何,必须注意,只有将页面或交换的帐户充入任务的当前(旧)内存 cgroup 时,才能移动该帐户。
+---+--------------------------------------------------------------------------+
bit | 什么类型的费用将被转移?
+---+--------------------------------------------------------------------------+
0 | 目标任务使用的匿名页面的电荷(或匿名页面的交换)。必须启用交换扩展(参见 2.4)才能移动交换电荷。
1 | 目标任务映射的文件页面(普通文件、tmpfs 文件(例如 ipc 共享内存)和 tmpfs 文件的交换)的负载。与匿名页面的情况不同,即使任务没有发生页面错误,任务映射范围内的文件页面(和交换)也将被移动,即它们可能不是任务的“RSS”,而是映射同一文件的其他任务的“RSS”。并且页面的映射计数将被忽略(即使 page_mapcount(page) > 1,页面也可以移动)。您必须启用交换扩展(参见 2.4)才能启用交换负载的移动。
+---+--------------------------------------------------------------------------+
8.3 TODO
--------
- 所有移动电荷操作都是在 cgroup_mutex 下完成的。长时间持有互斥锁并不是好的行为,所以我们可能需要一些技巧。
9. Memory thresholds
====================
内存 cgroup 使用 cgroups 通知 API 实现内存阈值(请参阅 cgroups.txt)。它允许注册多个 memory 和 memsw 阈值,并在超过阈值时收到通知。
要注册阈值,应用程序必须:
- 使用 eventfd(2) 创建 eventfd;
- 打开 memory.usage_in_bytes 或 memory.memsw.usage_in_bytes;
- 将字符串(如“<event_fd> <fd of memory.usage_in_bytes> <threshold>”)写入 cgroup.event_control。
当内存使用量在任何方向上超过阈值时,应用程序将通过 eventfd 收到通知。
它适用于 root 和非 root cgroup。
10. OOM Control
===============
memory.oom_control 文件用于 OOM 通知和其他控制。
Memory cgroup 使用 cgroup 通知 API 实现 OOM 通知程序(参见 cgroups.txt)。它允许注册多个 OOM 通知传递,并在发生 OOM 时获得通知。
要注册通知程序,应用程序必须:
- 使用 eventfd(2) 创建 eventfd
- 打开 memory.oom_control 文件
- 将字符串(如“<event_fd> <memory.oom_control 的 fd>”)写入 cgroup.event_control
发生 OOM 时,应用程序将通过 eventfd 收到通知。OOM 通知不适用于根 cgroup。
您可以通过将“1”写入 memory.oom_control 文件来禁用 OOM-killer,如下所示:
# echo 1 > memory.oom_control
如果禁用OOM-killer,则cgroup下的任务在请求可负责的内存时将在内存cgroup的OOM-waitqueue中挂起/休眠。
要运行它们,您必须通过扩大限制或减少使用来放宽内存cgroup的OOM状态。
要减少使用,
终止(kill)一些任务。
使用帐户迁移将一些任务移至其他组。
删除一些文件(在tmpfs上?)
然后,被停止的任务将再次工作。
读取时,将显示OOM的当前状态。
- oom_kill_disable 0 或 1(如果为1,则oom-killer已禁用)
- under_oom 0 或 1(如果为1,则内存cgroup处于OOM状态,任务可能会停止。)
11. Memory Pressure
===================
压力级别通知可用于监控内存分配成本;根据压力,应用程序可以实施不同的内存资源管理策略。压力级别定义如下:
"low" 级别表示系统正在回收内存以进行新分配。监控此回收活动可能有助于维护缓存级别。收到通知后,程序(通常是"Activity Manager")可能会分析 vmstat 并提前采取行动(即过早关闭不重要的服务)。
"medium" 级别意味着系统正面临中等内存压力,系统可能正在进行交换、分页活动文件缓存等。发生此事件时,应用程序可能会决定进一步分析 vmstat/zoneinfo/memcg 或内部内存使用情况统计数据,并释放任何可以轻松重建或从磁盘重新读取的资源。
"critical" 级别表示系统正在活跃地抖动,即将出现内存不足 (OOM) 甚至内核中的 OOM 杀手即将触发。应用程序应尽其所能帮助系统。查看 vmstat 或任何其他统计数据可能为时已晚,因此建议立即采取行动。
默认情况下,事件会向上传播,直到事件得到处理,即事件不会直通。例如,您有三个 cgroup:A->B->C。现在您在 cgroup A、B 和 C 上设置事件侦听器,并假设组 C 遇到一些压力。在这种情况下,只有组 C 会收到通知,即组 A 和 B 不会收到通知。这样做是为了避免过多的“广播”消息,这会扰乱系统,如果内存不足或系统抖动,情况会尤其糟糕。只有当组 C 没有事件列表时,组 B 才会收到通知。
有三种可选模式可指定不同的传播行为:
- "default":这是上面指定的默认行为。此模式与省略可选模式参数相同,由向后兼容性保留。
- "hierarchy":事件始终传播到根,类似于默认行为,不同之处在于,无论每个级别是否有事件侦听器,传播都会继续,使用“hierarchy”模式。在上面的示例中,组 A、B 和 C 将收到内存压力通知。
- "local":事件是直通的,即,只有当注册通知的 memcg 中出现内存压力时,事件才会收到通知。在上面的例子中,如果组 C 注册了"local"通知并且该组出现内存压力,则该组将收到通知。但是,如果组 B 注册了本地通知,则无论组 C 是否有事件侦听器,组 B 都不会收到通知。
级别和事件通知模式(如有必要,为“hierarchy”或“local”)由逗号分隔的字符串指定,即“low,hierarchy”指定所有祖先 memcgs 的分层、直通通知。默认的非直通行为通知不指定模式。“medium,local”指定中等级别的直通通知。
文件 memory.pressure_level 仅用于设置 eventfd。要注册通知,应用程序必须:
- 使用 eventfd(2) 创建 eventfd;
- 打开 memory.pressure_level;
- 将字符串“<event_fd> <fd of memory.pressure_level> <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. TODO
========
1. 让每个 cgroup 扫描器首先回收非共享页面。
2. 教导控制器考虑共享页面。
3. 当尚未达到限制但使用量越来越接近时,在后台开始回收。
Summary
=======
总体而言,该内存控制器一直是一个稳定的控制器,并且在社区中得到了相当广泛的评论和讨论。
References
==========
1. Singh, Balbir. RFC: Memory Controller, http://lwn.net/Articles/206697/
2. Singh, Balbir. Memory Controller (RSS Control), http://lwn.net/Articles/222762/
3. Emelianov, Pavel. Resource controllers based on process cgroups, http://lkml.org/lkml/2007/3/6/198
4. Emelianov, Pavel. RSS controller based on process cgroups (v2), http://lkml.org/lkml/2007/4/9/78
5. Emelianov, Pavel. RSS controller based on process cgroups (v3), http://lkml.org/lkml/2007/5/30/244
6. Menage, Paul. Control Groups v10, http://lwn.net/Articles/236032/
7. Vaidyanathan, Srinivasan, Control Groups: Pagecache accounting and control subsystem (v3), http://lwn.net/Articles/235534/
8. Singh, Balbir. RSS controller v2 test results (lmbench), http://lkml.org/lkml/2007/5/17/232
9. Singh, Balbir. RSS controller v2 AIM9 results http://lkml.org/lkml/2007/5/18/1
10. Singh, Balbir. Memory controller v6 test results, http://lkml.org/lkml/2007/8/19/36
11. Singh, Balbir. Memory controller introduction (v6), http://lkml.org/lkml/2007/8/17/69
12. Corbet, Jonathan, Controlling memory use in cgroups, http://lwn.net/Articles/243795/
标签:限制,Cgroup,bytes,v1,内存,cgroup,memory,页面 From: https://www.cnblogs.com/hellokitty2/p/18597627