Linux Cgroup v1(中文翻译)(6):Memory Resource Controller

  • Linux Cgroup v1(中文翻译)(6):Memory Resource Controller已关闭评论
  • 171 次浏览
  • A+
所属分类:linux技术
摘要

注意:
这个文档完完全全地过时了,需要整个地重写。但它还是包含了有用的信息,所以我们仍旧把它保留在这里,但是如果你需要深入理解的话,需要确保核对过当前的代码。


Memory Resource Controller

内存资源控制器

注意:
这个文档完完全全地过时了,需要整个地重写。但它还是包含了有用的信息,所以我们仍旧把它保留在这里,但是如果你需要深入理解的话,需要确保核对过当前的代码。

注意:
内存资源控制器在本文档中指的是内存控制器。不要混淆了这里内存控制器和硬件中使用的内存控制器。

(给编辑者)在本文档中:
当我们提到内存控制器的控制组(cgroupfs目录)时,我们称之为“内存控制组”(memory cgroup)。当你查看git-log和源代码时,你会看到补丁的抬头和功能名称倾向于使用"memcg"。在本文档中,我们避免使用它。

内存控制器的优点和目的

内存控制器把一组任务的内存从系统中其他任务的内存中隔离出来。LWN[12]中的文章提到了内存控制器的一些作用。内存控制器能用来:

  • 分离应用程序,或者一组内存饥饿的应用程序能够被分离和限制到较少量的内存上。
  • 创建内存数量有限的控制组。也可选择启动时带有mem=xxx选项。
  • 虚拟化方案能控制分配给虚拟机实例的内存数量。
  • CD/DVD烧录器能控制系统其他部分使用的内存数量以确保烧录不会因为缺少内存而失败。
  • 还有几个其他用例。查找或者使用控制器只是为了好玩(学习和hack进虚拟机)。

当前状态: linux-2.6.34-mmotm(development version of 2010/April)。

功能特性:

  • 统计和限制匿名页面、文件缓存、交换缓存的用量。
  • 页面能排他性地连接到per-memcg LRU,而不是全局的LRU。
  • 任意地统计和限制memory+swap的用量。
  • 层次结构化的统计。
  • 软限制
  • moving (recharging) account at moving a task is selectable.
  • 用量阈值通知器。
  • 内存压力通知器
  • oom-killer禁用开关和oom-notifier
  • 跟控制组无限控制。

内核内存支持是一个正在进行的工作,当前版本提供了基本功能(2.7节)。

控制文件概要

名称 说明
tasks 绑定任务(线程)并现实线程列表
cgroup.procs 显示进程列表
cgroup.event_control event_fd()的接口, CONFIG_PREEMPT_RT系统不可用
memory.usage_in_bytes 当前内存用量
memory.memsw.usage_in_bytes 当前memory+Swap用量
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 设置/显示内存用量软限制, CONFIG_PREEMPT_RT系统不可用
memory.stat 显示状态统计
memory.use_hierarchy 设置/显示分层数量,已过时不要使用
memory.force_empty 触发强制页面回收
memory.pressure_level 设置内存压力通知
memory.swappiness 设置/显示vmscan的swappiness参数
memory.move_charge_at_immigrate 设置/显示moving 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缓冲内存的硬限制
memory.kmem.tcp.usage_in_bytes 显示TCP缓冲内存分配用量
memory.kmem.tcp.failcnt 显示TCP缓冲内存用量触达限制的次数
memory.kmem.tcp.max_usage_in_bytes 显示记录的最大TCP缓冲内存用量

1. 历史

内存控制器有一段很长的历史。内存控制器的需求描述是Balbir Singh [1]发表的。同时RFC发布了内存控制的几个实现方式。RFC的目标是为内存控制的最小化需求特性构建共识和认可。西一个RSS控制器被Balbir Singh[2]在2007年2月发表。Pavel Emelianov [3][4][5]又发表了3个版本的RSS控制器。在OLS,在资源管理BoF,每个人都建议我们页面缓存和RSS都一起处理。另一个需求被提出来允许用户空间处理OOM。现在的内存控制器是版本6,它结合了RSS和页面缓存控制[11]。

2. 内存控制

内存是一种数量有限的独特资源。如果任务请求需求CPU来处理,任务能通过小时/天/月或者年的周期来扩展它的处理能力,但是内存只能被复用来完成任务。
内存控制器的实现已经被划分成多个阶段,他们是:

  1. Memory controller
  2. mlock(2) controller
  3. Kernel user memory accounting and slab control
  4. user mappings length controller

内存控制器是第一个已经被开发出来的控制器。

2.1. 设计

设计的核心是一个名叫page_counter的计数器。page_counter跟踪控制器相关的进程组的当前内存用量和限制。每个控制组有内存控制器相关的数据结构(mem_cgroup)。

2.2. 统计

     +--------------------+      |  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,它知道cgroup属于哪个控制组。

统计按照如下方式完成:mem_cgroup_charge_common() 被调用来构建必须的数据结构,检查正在记账的控制组是否超过限制。如果超过,那么在控制组内回收就会被调用。更多回收详情参看本文档的回收章节。如果一切正常,page_cgroup就会更新。page_cgroup在控制组内有他自己的LRU。page_cgroup结构在启动或者内存热插拔时被分配。

2.2.1 统计详情

所有映射的anon页面 (RSS) 和缓存页面 (Page Cache) 都会被统计。 永远不可回收的和不会用在LRU上的那些页面不会被统计。我们只统计通用VM管理下的页面。

RSS页面统计在page_fault上,除非他们之前已经统计过。文件页面插入到inode(radix-tree)时作为Page Cache统计,当它被映射到进程页表中时,要避免重复地统计。

完全未映射的RSS页面是没有统计的。从radix-tree中移除的PageCache页面也没有统计。甚至如果RSS页面完全没有被kswapd映射,他们可以作为SwapCache存在以直到被释放。这些SwapCaches也会被统计。swapped-in页面在添加到swapcache之后会被统计。

注意: 内核会一次性swapin-readahead和读取多个swap。从页面的memcg记录到swap开始而不管memsw是否被使能,页面在swapin之后都会被统计。

页面迁移时,统计信息会被保留。

注意:我们统计pages-on-LRU是因为我们的目的是控制已用页面的数量,not-on-LRU页面从VM视角来看是超出控制的。

2.3 共享页面统计

共享页面在“第一次触达页面方法”(first touch approach)的基础上统计。第一次触达页面的控制组被统计为页面(?)这种方法背后的原理是使用共享页面的控制组最后会因此而被记账。(一旦它未被从控制组中记账,它将会发生内存压力)。

但是看一下8.2小节:当移动任务到另一个控制组,如果move_charge_at_immigrate被选择,它的页面就可以被重新记账到新的控制组。

2.4 交换内存扩展(Swap Extension)

交换内存使用总是被记录。交换内存扩展允许读取和限制它。

当CONFIG_SWAP使能,下列文件就会被增加:

  • memory.memsw.usage_in_bytes
  • memory.memsw.limit_in_bytes

memsw表示memory+swap。memory+swap的用量是由memsw.limit_in_bytes限制的。

例如:假设系统有4G交换内存,不小心在2G内存限制下分配了6G内存,任务将会使用掉所有的交换内存。在这种情况下,设置memsw.limit_in_bytes=3G可以防止交换内存更坏的使用情况。通过使用memsw限制,你可以避免因为交换内存的缺乏而引起的系统OOM。

为什么是memory+swap而不是swap?
全局LRU(kswapd)能换出任何页面。换出意味着移动统计从内存切到了交换内存。而使用memory+swap则不会有任何改变。换句话说,当我们想要限制交换内存的用量而不影响全局LRU,从OS视角的观点来看,memory+swap的限制方式比仅仅限制交换内存要更好。

控制组触达memory.memsw.limit_in_bytes时会发生什么?
当控制组触达memory.memsw.limit_in_bytes,在该控制组内做swap-out是没有用的。swap-out也不可以由控制组程序和已摘除的文件缓存来进行。但是如上所述,为了系统内存管理状态的正常,全局LRU能从控制组换出内存。控制组不能阻止它。

2.5 回收Reclaim

每个控制组维护着每个控制组的跟全局VM结构相同的LRU。当控制组超过它的限制,我们首先试着从控制组内回收内存以便预留新的空闲页面。如果回收不成功,OOM程序被调用来杀死控制组内最庞重的任务。(参看第10小节OOM控制)

回收算法没有为控制组做修改,被选做回收的页面来自每个控制组的LRU列表。

注意:
回收不会对根控制组生效,因为我们不能在根控制组上设置任何限制。

注意2:
当panic_on_oom设置为2,整个系统就会panic。
当OOM时间通知器已经注册,时间就会被发出。(参看oom_control小节)

2.6 锁Locking

锁顺序如下:

Page lock (PG_locked bit of page->flags)     mm->page_table_lock or split pte_lock       lock_page_memcg (memcg->move_lock)         mapping->i_pages lock           lruvec->lru_lock   

Per-node-per-memcgroup LRU (控制组私有LRU) 由lruvec->lru_lock来守护; 从lruvec->lru_lock分离出页面之前,PG_lru bit of page->flags会被清除。

2.7 内核内存扩展(CONFIG_MEMCG_KMEM)

内存控制器能用内核内存扩展来限制系统使用的内核内存数量。内核内存完全不同于用于内存,因为它不能被交换出去,这使得它有可能通过消耗太多的这种昂贵资源来Dos系统。

内核内存统计对所有内存控制组默认为使能的。但是它能在启动时传递cgroup.memory=nokmem选项给内核来禁用。这种情况下内核内存根本就不会被统计。

内核内存限制对根控制组没有影响。根控制组的用量可以统计,也可以不用统计。已用的内存会被累加到memory.kmem.usage_in_bytes中或者独立的计数器中。

主kmem计数被传递给主计数器,因此kmem统计也可以从用户计数器里看到。

当前没有为内核内存实现软限制。当这些限制工作完成之后未来的工作就是出发slab回收。

2.7.1 当前统计的内核内存资源

stack pages:
每个进程消耗一些堆栈页面。通过记账到内核内存,我们可以防止新进程创建太高的内核内存。

slab pages:
SLAB或者SLUB分配器分配的页面都会被跟踪。每当第一次从memcg内创建缓存时,每个kmem_cache的副本就会创建出来。创建是lazily的,因为当正在创建缓存时有些对象仍然能被跳过。slab页面内的所有对象应该属于同一个memcg。当任务在页面分配期间被移动到不同的memcg时就会失败。

sockets memory pressure:
有些套接字协议有内存压力阈值。内存控制器允许他们分别由每个控制组分别地而不是全局地被控制

tcp memory pressure:
TCP协议的套接字内存压力

2.7.2 常用用例

因为"kmem"计数被传递给主用户计数器,内核内存限制绝对不会跟用户内存完全无关。"U"是用户限制,"K"是内核限制。有三种方式能设置限制:
U != 0, K = unlimited:
这是标准memcg限制机制。内核内存完全被忽略。

U != 0, K < U:
内核内存是用户内存的子集。在每个控制组内存数量被超量分配时这种方式是有用的。超量的内核内存限制肯定不推荐,因为box仍然能在不可回收的内存之外运行。这种情况下,管理员能设置"K"以便所有控制组总数绝不会超过总的内存,随意地在QOS成本范围上设置"U"。

警告:
在当前的实现中,内存回收不会在触达K而小于U时被控制组触发。

U != 0, K >= U:
因为kmem统计被传递给用户计数器,控制组将会触发两种内存的回收。这种设置给管理员一个统一的内存视角。

3. 用户接口

配置:

  • 使能CONFIG_CGROUPS
  • 使能CONFIG_MEMCG
  • 使能CONFIG_MEMCG_SWAP(来使用swap内存扩展)
  • 使能CONFIG_MEMCG_KMEM (来使用kmem内存扩展)

准备控制组:

# 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控制组内,更改内存限制:

# echo 4M > /sys/fs/cgroup/memory/0/memory.limit_in_bytes 

注意:
我们可以使用后缀(k, K, m, M, g or G)来表示字节单位值。

注意:
我们能写"-1"来重置*.limit_in_bytes为无限制。

注意:
我们不能在根控制组下设置任何限制。

# 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域给出了控制组限制超过的次数。
memory.stat文件给出了统计信息。现在缓存、RSS和激活/不激活页面数量都能被显示了。

4. 测试

为了测试功能和实现,可以参看Memcg实现demo。

性能测试也很重要。要查看纯内存控制器的开销,tmpfs上的测试将会给你很好看的少量开销数字。

缺页(Page-fault)的可测量性也很重要。在平行缺页测试中,多进程测试比单进程测试要好是因为有共享对象/状态的噪音。

但是上述两种是极限测试条件。试着做内存控制器下的正常测试总是有好帮助的。

4.1 疑难解答

有时用户可能发现控制组下的应用程序被OOM killer杀死了,它有几种原因:

  1. 控制组限制太高。
  2. 用户正在使用匿名内存,而交换内存被关闭或者太低了。

通过同步echo 1 > /proc/sys/vm/drop_caches将会帮助除去控制组中的缓存的一些页面(page cache pages)。

要想知道发生了什么,禁用OOM_Kill为“10. OOM Control”,再来看看发生的情况。

4.2 任务迁移

当任务从一个控制组迁移到另一个控制组,它的统计默认是不会携带走的。从源控制组分配的页面仍然会保持记账,当页面被释放或者回收后记账才会摘除。

你可以随着任务迁移移动记账。参看第8章“任务迁移时移动记账”

4.3 移除控制组

控制组可以通过rmdir来移除,但是根据4.1和4.2小节的讨论,即使所有任务已经迁移出去了控制组可能仍有一些统计记账。(因为我们的记账是针对页面而不是针对任务)

我们移动统计到父层就不会有记账变化。

在交换内存中的记账信息在移除控制组时不会被更新。记录的信息会被丢弃,使用交换内存的控制组会作为新的属主来记账。

5. 其他接口

5.1 force_empty

memory.force_empty接口可以使控制组的内存用量为空:

# echo 0 > memory.force_empty 

控制组将被回收,页面也会被尽可能地回收。

这个接口的典型用例就是在调用rmdie()前。通过rmdir()来使得memcg离线,但是由于文件缓存已经被记账,memcg仍然可能呆在原地不动。一些超出使用的页面缓存可能会保持记账一直到内存压力发生。如果你想要避免这些,force_empty就很有用。

5.2 stat文件

memory.stat文件包含下列统计信息:

内存控制组本地状态

名称 说明
cache # of bytes of page cache memory.
rss # of bytes of anonymous and swap cache memory (includes transparent hugepages).
rss_huge # of bytes of anonymous transparent hugepages.
mapped_file # of bytes of mapped file (includes tmpfs/shmem)
pgpgin # of charging events to the memory cgroup. The charging event happens each time a page is accounted as either mapped anon page(RSS) or cache page(Page Cache) to the cgroup.
pgpgout # of uncharging events to the memory cgroup. The uncharging event happens each time a page is unaccounted from the cgroup.
swap # of bytes of swap usage
dirty # of bytes that are waiting to get written back to the disk.
writeback # of bytes of file/anon cache that are queued for syncing to disk.
inactive_anon # of bytes of anonymous and swap cache memory on inactive LRU list.
active_anon # of bytes of anonymous and swap cache memory on active LRU list.
inactive_file # of bytes of file-backed memory on inactive LRU list.
active_file # of bytes of file-backed memory on active LRU list.
unevictable # of bytes of memory that cannot be reclaimed (mlocked etc).

分层架构相关的状态 (memory.use_hierarchy)

hierarchical_memory_limit # of bytes of memory limit with regard to hierarchy under which the memory cgroup is
hierarchical_memsw_limit # of bytes of memory+swap limit with regard to hierarchy under which memory cgroup is.
total_ # hierarchical version of , which in addition to the cgroup’s own value includes the sum of all hierarchical children’s values of , i.e. 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扫描。显示出来是为了好调试。

注意:
仅仅匿名和swap缓存内存被作为rss统计的一部分被列举。这个不应该跟resident set size或者控制组使用的物理内存弄混淆。
'rss + mapped_file'给出来的才是控制组的resident set size。

注意:
文件和shmem可以跟其它控制组共享。在此情况下,只有当内存控制组是页面缓存的属主时mapped_file才会被统计。

5.3 swappiness

特定的组可以覆盖 /proc/sys/vm/swappiness。在根控制组下可调整全局相关的swappiness设置

注意:
不同于全局回收,有限回收强制swappiness为0来防止任何交换,即使有交换内存可用也不行。在没有文件页面可回收的情况下这可能会导致memcg的OOM-killer。

5.4 failcnt

内存控制组提供了memory.failcnt和memory.memsw.failcnt文件。这个failcnt失败次数显示了用量计数器触达限制的次数。当内存控制组触及限制failcnt就会增加,控制组内的内存将会被回收。

你可以重置failcnt为0:

# echo 0 > .../memory.failcnt 

5.5 usage_in_bytes

内存控制组使用一些优化措施来避免不必要的cacheline失败共享。usage_in_bytes不会显示内存(和交换内存)用量的精确值,它是一个模糊值。如果你想要知道更精确的内存用量,你应该使用memory.stat中的RSS+CACHE(+SWAP)值。

5.6 numa_stat

它跟numa_maps相似但是基于per-memcg操作。......

每个memcg的numa_stat文件包含“total”,“file”,“anon”和“unevictable”。......

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. 分层支持

内存控制器支持深度分层和分层级统计。分层是通过在控制组文件系统下创建控制组来生成的。例如有如下一个控制组分层:

    root   /  |     /   |     a    b     c            |             |              d   e 

上图中分层统计也会被使能,所有e的内存用量被统计上溯到它的祖宗c和root上。如果有一个父系过量了,回收算法就会从祖宗和其后代的任务中回收。

6.1 分层统计和回收

分层统计默认是使能的。禁用分层统计已经过时了,试图这么做会导致失败并打印警告到dmesg中。

为了兼容,memory.use_hierarchy总是要写入1:

# echo 1 > memory.use_hierarchy 

7. 软限制Soft limits

软限制允许更多个内存共享。软限制背后的思想是允许控制组按需使用内存,提供如下机制:

  1. 没有内存争夺。
  2. 不会超过硬限制。

当系统探测到内存争夺或者低内存时,控制组就会后推他们的软限制。如果软限制非常高,他们就会被尽可能后推来确保控制组不会产生内存饥饿。
...

7.1 接口

使用下面命令来设置软限制:

# echo 256M > memory.soft_limit_in_bytes 

如果想要改为1G:

# echo 1G > memory.soft_limit_in_bytes 

注意:
从内存控制组内内存回收调用开始,软限制会影响一段很长期的时间。

注意:
建议软限制总是设置在在硬限制之下,否则硬限制就会获得优先。

8. 任务迁移时的移动记账

用户能随着任务的迁移对任务进行移动记账,也就是说,旧控制组内未记账的任务页面将会在新控制组内记账。这个特性在非CONFIG_MMU环境内因为缺乏页表而不被支持。

8.1 接口

这个特性默认是禁用的。可以写入到目的控制组memory.move_charge_at_immigrate文件来使能:

# echo (some positive value) > memory.move_charge_at_immigrate 

注意:
每个比特位各有意义,应当移动哪种记账类型。

注意:
只在你移动mm->owner时记账才会移动。换句话说,就是线程组的领导(原文:a leader of a thread group)。

注意:
如果我们在目的控制组没有找到足够空间给任务,我们会试着通过回收内存来制造空间。如果不能制造足够的空间,任务迁移会失败。

注意:
移动记账可能耗费几秒时间。

再次禁用移动记账:

# echo 0 > memory.move_charge_at_immigrate 

8.2 可被移动的记账类型

每个比特位各有意义,应当移动哪种记账类型。但是在任何情况下,当记账到任务当前的(或者旧的)内存控制组时,必须说明能被移动的页面或者swap的数量。

bit 可被移动的记账类型
0 目标任务用到的匿名页(或者交换内存)。必须使能Swap Extension(2.4小节)
1 目标任务映射的文件页(正常文件、tmpfs文件<ipc共享内存>和tmpfs交换内存)。<省略几句话>,必须使能Swap Extension(2.4小节)

8.3 TODO

所有的移动记账操作在cgroup_mutex下进行。...

9. 内存阈值

内存控制组用控制组通知API实现内存阈值。允许注册多个mem和memsw阈值来获得通知。

要注册阈值,应用程序必须:

  • 用eventfd(2)来创建eventfd;
  • 打开memory.usage_in_bytes或者memory.memsw.usage_in_bytes;
  • 写入字符串"<event_fd> "到cgroup.event_control

应用程序在内存用量的到达阈值时会被eventfd通知到。它对root和non-root控制组都是适用的。

10. OOM Control

memory.oom_control文件用作OOM通知和其他控制。

内存控制组使用控制组通知API实现了OOM通知器。允许注册多个OOM通知。

要注册通知器,应用程序必须:

  1. 用eventfd(2)创建eventfd句柄。
  2. 打开memory.oom_control文件。
  3. 写入字符串“<event_fd> ”到cgroup.event_control。
    当OOM发生时,应用程序将会通过eventfd被通知到。OOM通知对根控制组不生效。

你可以这样来禁用OOM-killer:

#echo 1 > memory.oom_control 

如果OOM-killer被禁用,当控制组下的任务请求可统计的内存时,他们将会在内存控制组的OOM-waitqueue队列里挂起/睡眠。

为了运行它,你必须释放内存控制组的OOM状态,通过:

  • 扩大限制或者减少用量

为了减少用量:

  • 杀死一些任务。
  • 移动一些任务到其他控制组,携带着记账迁移。
  • 移除一些文件(在tmpfs内?)

然后停止的任务会重新工作了。

在读取时,当前的OOM状态被显示:

  • oom_kill_disable:0或者1 (1表示oom-killer禁用)
  • under_oom:0或1 (1表示内存控制组在OOM下,任务可以被停止)
  • oom_kill:整数计数器,控制组内被OOM killer杀死的进程数量。

11. 内存压力

压力等级通知能被用来监测内存分配成本。基于压力,应用程序能实现不同的内存资源管理策略。压力等级定义如下:

  • “low”水平表示系统正在回收内存。监控回收活动对于维护缓存水平会有帮助的。在通知中,程序(一般是Activity Manager)可以分析vmstat然后提前采取行动(例如关闭不重要的服务)。
  • “medium”水平表示系统正在经历中等内存压力。系统可能正在产生交换内存,分配文件缓存等等。在这个事件中,应用程序可以进一步分析vmstat/zoneinfo/memcg或者内部内存用量统计,释放那些能很容易重建或者从磁盘中重新读取的任何资源。
  • “critical”水平表示系统正在遭受冲击,即将要触发OOM。应用程序应该尽其所能帮助系统。此时来咨询vmstat或者其他统计数据可能已经太晚了,建议立刻采取行动。

默认情况下,事件会被层层繁衍上报,直到被处理为止,就是说事件不会穿透。例如,你有三个控制组A->B->C,现在你在控制组A、B、C上都配置了事件监听器,假设C控制组经受到了压力,此时只有C会收到通知,而A和B不会收到。这么做避免了过度广播消息干扰系统,如果我们在低内存或者遭受连续冲击的情况下会特别有坏处。如果C没有事件监听器,那么B将会收到通知。

有三种可选模式来定义不同的上报行为:

  • “default”: 默认行为。这种模式忽略可选模式参数,留作后向兼容。
  • “hierarchy”: 事件总是被上报到根,不管是否有事件监听器。上述例子中,控制组A、B、C都会收到压力等级的通知。
  • “local”: 事件被穿透。只有注册了通知的memcg会收到通知。上述例子中,如果控制组C注册了“local”通知,那么C将会收到通知。如果控制组B注册了“local”通知,然而控制组B绝对不会收到通知,不管C是否注册了事件监听器。

压力等级和事件通知模式是以逗号分隔符字符串定义的,例如“low,hierarchy”定义了分层穿透通知到所有祖先memcg。通知默认是非穿透的。“medium,local”为中等水平定义了穿透通知。

memory.pressure_level文件只是被用来配置eventfd句柄。要注册同志,应用程序必须:

  1. 用eventfd(2)创建eventfd句柄。
  2. 打开memory.pressure_level。
  3. 写入字符串“<event_fd> <level[,mode]>”到cgroup.event_control。
    应用程序在内存压力达到或超过指定的等级水平时将会通过eventfd被通知到。对memory.pressure_level的读写操作没有被实现。

测试:
这里有个小脚本,创建新控制组,配置内存限制,配置通知然后在子控制组中经受critical压力:

# 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 (Expect a bunch of notifications, and eventually, the oom-killer will trigger.) 

12. TODO

  1. Make per-cgroup scanner reclaim not-shared pages first
  2. 教会控制器来对共享页面做统计。
  3. 当限制仍旧没有触达而用量正在接近时在后台启动回收。

总结

总而言之,内存控制器是稳定的控制器,已经被提交了,在社区里也得到非常广泛地讨论。

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 https://lore.kernel.org/r/45ED7DEC.7010403@sw.ru
  4. Emelianov, Pavel. RSS controller based on process cgroups (v2) https://lore.kernel.org/r/461A3010.90403@sw.ru
  5. Emelianov, Pavel. RSS controller based on process cgroups (v3) https://lore.kernel.org/r/465D9739.8070209@openvz.org
  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), https://lore.kernel.org/r/464C95D4.7070806@linux.vnet.ibm.com
  9. Singh, Balbir. RSS controller v2 AIM9 results https://lore.kernel.org/r/464D267A.50107@linux.vnet.ibm.com
  10. Singh, Balbir. Memory controller v6 test results, https://lore.kernel.org/r/20070819094658.654.84837.sendpatchset@balbir-laptop
  11. Singh, Balbir. Memory controller introduction (v6), https://lore.kernel.org/r/20070817084228.26003.12568.sendpatchset@balbir-laptop
  12. Corbet, Jonathan, Controlling memory use in cgroups, http://lwn.net/Articles/243795/

.


英文原文:
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/memory.html
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/memcg_test.html