Red Hat Training

A Red Hat training course is available for Red Hat Linux

A.7. memory

memory 子系统自动生成 cgroup 任务使用内存资源的报告,并限定这些任务所用内存的大小:
memory.stat
报告大范围内存统计,见下表:

表 A.2. memory.stat 报告的值

统计数据描述
cache缓存页,包括 tmpfsshmem),单位为字节
rss匿名和 swap 缓存,“”包括 tmpfsshmem),单位为字节
mapped_filememory-mapped 映射文件大小,包括 tmpfsshmem),单位为字节
pgpgin读入内存的页数
pgpgout从内存中读出的页数
swapswap 用量,单位为字节
active_anon激活的“近期最少使用”(least-recently-used,LRU)列表中的匿名和 swap 缓存,包括 tmpfsshmem),单位为字节
inactive_anon未激活的 LRU 列表中的匿名和 swap 缓存,包括 tmpfsshmem),单位为字节
active_file激活的 LRU 列表中的 file-backed 内存,以字节为单位
inactive_file未激活 LRU 列表中的 file-backed 内存,以字节为单位
unevictable无法收回的内存,以字节为单位
hierarchical_memory_limit包含 memory cgroup 层级的内存限制,单位为字节
hierarchical_memsw_limit包含 memory cgroup 层级的内存加 swap 限制,单位为字节
另外,这些文件除 hierarchical_memory_limithierarchical_memsw_limit 之外,都有一个对应前缀 total,它不仅可在该 cgroup 中报告,还可在其子 cgroup 中报告。例如:swap 报告 cgroup 的 swap 用量,total_swap 报告该 cgroup 及其所有子群组的 swap 用量总和。
当您解读 memory.stat 报告的数值时,请注意各个统计数据之间的关系:
  • active_anon + inactive_anon = 匿名内存 + tmpfs 文件缓存 + swap 缓存
    因此,active_anon + inactive_anonrss,因为 rss 不包括 tmpfs
  • active_file + inactive_file = 缓存 - tmpfs 大小
memory.usage_in_bytes
报告 cgroup 中进程当前所用的内存总量(以字节为单位)。
memory.memsw.usage_in_bytes
报告该 cgroup 中进程当前所用的内存量和 swap 空间总和(以字节为单位)。
memory.max_usage_in_bytes
报告 cgroup 中进程所用的最大内存量(以字节为单位)。
memory.memsw.max_usage_in_bytes
报告该 cgroup 中进程的最大内存用量和最大 swap 空间用量(以字节为单位)。
memory.limit_in_bytes
设定用户内存(包括文件缓存)的最大用量。如果没有指定单位,则该数值将被解读为字节。但是可以使用后缀代表更大的单位 —— k 或者 K 代表千字节,m 或者 M 代表兆字节 ,g 或者 G 代表千兆字节。
您不能使用 memory.limit_in_bytes 限制 root cgroup;您只能对层级中较低的群组应用这些值。
memory.limit_in_bytes 中写入 -1 可以移除全部已有限制。
memory.memsw.limit_in_bytes
设定内存与 swap 用量之和的最大值。如果没有指定单位,则该值将被解读为字节。但是可以使用后缀代表更大的单位 —— k 或者 K 代表千字节,m 或者 M 代表兆字节,g 或者 G 代表千兆字节。
您不能使用 memory.memsw.limit_in_bytes 来限制 root cgroup;您只能对层级中较低的群组应用这些值。
memory.memsw.limit_in_bytes 中写入 -1 可以删除已有限制。

重要

在设定 memory.memsw.limit_in_bytes 参数“之前”设定 memory.limit_in_bytes 参数非常重要:顺序颠倒会导致错误。这是因为 memory.memsw.limit_in_bytes 只有在消耗完所有内存限额(之前在 memory.limit_in_bytes 中设定)后方可用。
请参考下列例子:为某一 cgroup 设定 memory.limit_in_bytes = 2Gmemory.memsw.limit_in_bytes = 4G, 可以让该 cgroup 中的进程分得 2GB 内存,并且一旦用尽,只能再分得 2GB swap。memory.memsw.limit_in_bytes 参数表示内存和 swap 的总和。没有设置 memory.memsw.limit_in_bytes 参数的 cgroup 的进程可以使用全部可用 swap (当限定的内存用尽后),并会因为缺少可用 swap 触发 Out of Memory(内存不足) 状态。
/etc/cgconfig.conf 文件中 memory.limit_in_bytesmemory.memsw.limit_in_bytes 参数的顺序也很重要。以下是正确的配置示例:
memory {
    memory.limit_in_bytes = 1G;
    memory.memsw.limit_in_bytes = 1G;
}
memory.failcnt
报告内存达到 memory.limit_in_bytes 设定的限制值的次数。
memory.memsw.failcnt
报告内存和 swap 空间总和达到 memory.memsw.limit_in_bytes 设定的限制值的次数。
memory.force_empty
当设定为 0 时,该 cgroup 中任务所用的所有页面内存都将被清空。这个接口只可在 cgroup 没有任务时使用。如果无法清空内存,请在可能的情况下将其移动到父 cgroup 中。移除 cgroup 前请使用 memory.force_empty 参数以免将废弃的页面缓存移动到它的父 cgroup 中。
memory.swappiness
将 kernel 倾向设定为换出该 cgroup 中任务所使用的进程内存,而不是从页高速缓冲中再生页面。这与 /proc/sys/vm/swappiness 为整体系统设定的倾向、计算方法相同。默认值为 60。低于 60 会降低 kernel 换出进程内存的倾向;高于 0 会增加 kernel 换出进程内存的倾向。高于 100 时,kernel 将开始换出作为该 cgroup 中进程地址空间一部分的页面。
请注意:值 0 不会阻止进程内存被换出;系统内存不足时,换出仍可能发生,因为全局虚拟内存管理逻辑不读取该 cgroup 值。要完全锁定页面,请使用 mlock() 而不是 cgroup。
您不能更改以下群组的 swappiness:
  • root cgroup,它使用 /proc/sys/vm/swappiness 设定的 swappiness。
  • 有子群组的 cgroup。
memory.use_hierarchy
包含标签(0 或者 1),它可以设定是否将内存用量计入 cgroup 层级的吞吐量中。如果启用(1),内存子系统会从超过其内存限制的子进程中再生内存。默认情况下(0),子系统不从任务的子进程中再生内存。
memory.oom_control
包含标签(0 或者 1),它可以为 cgroup 启用或者禁用“内存不足”(Out of Memory,OOM) 终止程序。如果启用(0),尝试消耗超过其允许内存的任务会被 OOM 终止程序立即终止。默认情况下,所有使用 memory 子系统的 cgroup 都会启用 OOM 终止程序。要禁用它,请在 memory.oom_control 文件中写入 1
~]# echo 1 > /cgroup/memory/lab1/memory.oom_control
禁用 OOM 杀手程序后,尝试使用超过其允许内存的任务会被暂停,直到有额外内存可用。
memory.oom_control 文件也在 under_oom 条目下报告当前 cgroup 的 OOM 状态。如果该 cgroup 缺少内存,则会暂停它里面的任务。under_oom 条目报告值为 1
memory.oom_control 文件可以使用 API 通知来报告 OOM 情况的出现。 。

A.7.1. 示例应用

例 A.3. OOM 控制和通知

以下示例将演示当 cgroup 中任务尝试使用超过其允许的内存时, OOM 终止程序的工作过程,以及通知处理程序是如何报告 OOM 状态的:
  1. 在层级中附加 memory 子系统,并创建一个 cgroup:
    ~]# mount -t memory -o memory memory /cgroup/memory
    ~]# mkdir /cgroup/memory/blue
  2. blue cgroup 中任务可用的内存量设定为 100MB:
    ~]# echo 104857600 > memory.limit_in_bytes
  3. 进入 blue 目录并确定已启用 OOM 终止程序:
    ~]# cd /cgroup/memory/blue
    blue]# cat memory.oom_control
    oom_kill_disable 0
    under_oom 0
    
  4. 将当前 shell 进程移动到 blue cgroup 的 tasks 文件中,以便在这个 shell 中启动的其它所有进程会自动移至 blue cgroup:
    blue]# echo $$ > tasks
  5. 启动测试程序,尝试分配超过您在步骤 2 中设定限额的内存量。blue cgroup 消耗完内存后,OOM 终止程序会终止测试程序,并在标准输出中报告 Killed
    blue]# ~/mem-hog
    Killed
    
    以下是测试程序实例 [1]
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    
    #define KB (1024)
    #define MB (1024 * KB)
    #define GB (1024 * MB)
    
    int main(int argc, char *argv[])
    {
    	char *p;
    
    again:
    	while ((p = (char *)malloc(GB)))
    		memset(p, 0, GB);
    
    	while ((p = (char *)malloc(MB)))
    		memset(p, 0, MB);
    
    	while ((p = (char *)malloc(KB)))
    		memset(p, 0,
    				KB);
    
    	sleep(1);
    
    	goto again;
    
    	return 0;
    }
    
  6. 禁用 OOM 杀手程序,然后重新运行测试程序。这次该测试程序会暂停并等待额外的内存释放:
    blue]# echo 1 > memory.oom_control
    blue]# ~/mem-hog
  7. 虽然测试程序处于暂停状态,但请注意该 cgroup 的 under_oom 状态已更改,表示该 cgroup 缺少可用内存:
    ~]# cat /cgroup/memory/blue/memory.oom_control
    oom_kill_disable 1
    under_oom 1
    
    重启 OOM 终止程序可以立即终止该测试程序。
  8. 如要收到关于每一个 OOM 的通知,请创建一个 指定的程序。 例如 [2]
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/eventfd.h>
    #include <errno.h>
    #include <string.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    static inline void die(const char *msg)
    {
    	fprintf(stderr, "error: %s: %s(%d)\n", msg, strerror(errno), errno);
    	exit(EXIT_FAILURE);
    }
    
    static inline void usage(void)
    {
    	fprintf(stderr, "usage: oom_eventfd_test <cgroup.event_control> <memory.oom_control>\n");
    	exit(EXIT_FAILURE);
    }
    
    #define BUFSIZE 256
    
    int main(int argc, char *argv[])
    {
    	char buf[BUFSIZE];
    	int efd, cfd, ofd, rb, wb;
    	uint64_t u;
    
    	if (argc != 3)
    		usage();
    
    	if ((efd = eventfd(0, 0)) == -1)
    		die("eventfd");
    
    	if ((cfd = open(argv[1], O_WRONLY)) == -1)
    		die("cgroup.event_control");
    
    	if ((ofd = open(argv[2], O_RDONLY)) == -1)
    		die("memory.oom_control");
    
    	if ((wb = snprintf(buf, BUFSIZE, "%d %d", efd, ofd)) >= BUFSIZE)
    		die("buffer too small");
    
    	if (write(cfd, buf, wb) == -1)
    		die("write cgroup.event_control");
    
    	if (close(cfd) == -1)
    		die("close cgroup.event_control");
    
    	for (;;) {
    		if (read(efd, &u, sizeof(uint64_t)) != sizeof(uint64_t))
    			die("read eventfd");
    
    		printf("mem_cgroup oom event received\n");
    	}
    
    	return 0;
    }
    
    上述程序会探测 OOM 状态(从被命令列指定为参数的 cgroup 中),并使用 mem_cgroup oom event received 字符串在标准输出中报告。
  9. 在一个独立的控制台中运行上述通知处理程序,并将 blue cgroup 的控制文件指定为参数:
    ~]$ ./oom_notification /cgroup/memory/blue/cgroup.event_control /cgroup/memory/blue/memory.oom_control
  10. 在另一个控制台中运行 mem_hog 测试程序,以便生成 OOM 状态,并查看 oom_notification 程序在标准输出中的报告:
    blue]# ~/mem-hog


[1] 源代码由 Red Hat 的工程师 František Hrbata 提供。
[2] 源代码由 Red Hat 的工程师 František Hrbata 提供。