09 - Page Cache:为什么我的容器内存使用量总是在临界点-

09 - Page Cache:为什么我的容器内存使用量总是在临界点-本文仅作为学习记录 非商业用途 侵删 如需转载需作者同意 上一节我们知道 如果容器使用的物理内存超过了 memory limit in bytes 容器中的进程会被 OOM Killer 杀死 不过在有一些容器使用场景中 容器应用有很多文件读写

大家好,我是讯享网,很高兴认识大家。

本文仅作为学习记录,非商业用途,侵删,如需转载需作者同意。

上一节我们知道,如果容器使用的物理内存超过了 memory.limit_in_bytes,容器中的进程会被 OOM Killer 杀死。

不过在有一些容器使用场景中,容器应用有很多文件读写,你会发现整个容器的内存使用量已经接近 Memory Cgroup 上限值了,但是在容器中再申请内存,还是可以申请出来,并没有发生OOM。

一、问题再现

测试使用的代码:https://github.com/chengyli/training/tree/main/memory/page_cache

启动一个容器,并且给容器的Memory Cgroup 里的内存上限设置为100MB(bytes)

 #!/bin/bash docker stop page_cache;docker rm page_cache if [ ! -f ./test.file ] then dd if=/dev/zero of=./test.file bs=4096 count=30000 echo "Please run start_container.sh again " exit 0 fi echo 3 > /proc/sys/vm/drop_caches sleep 10 docker run -d --init --name page_cache -v $(pwd):/mnt registry/page_cache_test:v1 CONTAINER_ID=$(sudo docker ps --format "{ 
    {.ID}}\t{ 
    {.Names}}" | grep -i page_cache | awk '{print $1}') echo $CONTAINER_ID CGROUP_CONTAINER_PATH=$(find /sys/fs/cgroup/memory/ -name "*$CONTAINER_ID*") echo  > $CGROUP_CONTAINER_PATH/memory.limit_in_bytes cat $CGROUP_CONTAINER_PATH/memory.limit_in_bytes 

讯享网

查看运行中的容器的Memory Cgroup 中的 memory.limit_in_bytes 和 memory.usage_in_bytes 这两个值。

以下为作者测试的结果图:
在这里插入图片描述
讯享网

我们把容器内存上限值和已使用的内存数值做个减法,–= 90112bytes,只差大概 90KB 左右的大小。

但是,如果这时候我们继续启动一个程序,让这个程序申请并使用 50MB 的物理内存,就会发现这个程序还是可以运行成功,这时候容器并没有发生 OOM 的情况。这时我们再去查看参数 memory.usage_in_bytes,就会发现它的值变成了 bytes,比之前还少了一些。那这是怎么回事呢

在这里插入图片描述

二、知识详解:Linux系统有哪些内存类型

要解决上面的问题,需要理解Linux 系统中有哪几种操作类型,不同类型的内存在容器内存增高到最高限制的时候,处理的方式也不同。

2.1、Linux内存类型

Linux各个模块都需要内存:

  • 内核:需要分配内存给页表,内核栈,slab,也就是内核各种数据结构的 cache pool ;
  • 用户态:堆内存,栈内存,共享库内存,还有文件读写的 page cache

Memory Cgroup 不会对内核的内存做限制(比如页表,slab)
今天主要讨论的是用户态相关的两个内存类型:RSS,Page Cache

2.2、RSS

RSS :Resident Set Size 缩写(常驻内存),指进程真正申请到物理页面的内存大小。

当进程对这块内存地址开始做真正读写操作的时候,系统才会把实际需要的物理内存分配给进程。
而这个过程中,进程真正得到的物理内存就是 RSS

下面为作者测试验证的过程:

比如下面的这段代码,我们先用 malloc 申请 100MB 的内存。

讯享网 p = malloc(100 * MB); if (p == NULL) return 0; 

然后top 查看内存占用情况。
看到mem_alloc 程序的虚拟机地址空间(VIRT) 已经有了 KB(~100MB)
但是实际的物理内存 RSS (top命令中的是RES 就是 Resident 的简写,和RSS 是一个意思) 在这里只有 688KB

在这里插入图片描述

接着我们在程序里等待 30 秒之后,我们再对这块申请的空间里写入 20MB 的数据。

 sleep(30); memset(p, 0x00, 20 * MB) 

当我们用 memset() 函数对这块地址空间写入20MB 的数据之后,再用top 查看,这个时候可以看到 虚拟地址空间 (VIRT)还是 ,不过物理内存 RSS (RES) 的值变成了 21432(大小约为20MB),这里的单位都是KB

在这里插入图片描述

通过上面的实验,验证 RSS 就是进程里真正获得的物理内存的大小。

对于进程来说,RSS 内存包含了进程的代码段内存,栈内存,堆内存,共享库的内存,这些内存是进程运行必须的,刚才我们通过 malloc/memset 得到的内存,就是属于堆内存。

具体的每一部分的RSS 内存的大小,可以通过 /proc/[pid]/smaps文件查看。

在这里插入图片描述

2.3、Page Cache

Page Cache 的主要作用是提高磁盘文件的读写性能,因为系统调用 read() write()的缺省行为都会把读过或者写过的页面存放在 Page Cache 里。

在这里插入图片描述
在Linux 系统里只要有空闲的内存,系统就会自动的把读写过的磁盘文件页面放入到Page Cache里,那么这些内存都被 Page Cache 占用了,一旦进程需要用到更多的物理内存,执行 malloc() 调用做申请时,就会发现剩余的物理内存不够了怎么办。

内存页面回收机制(page frame reclaim):Linux内存管理中的一种机制,会根据系统里空闲物理内存是否低于某个阈值(wartermark),来决定是否启动内存的回收。

内存回收的算法,根据不同类型的内存以及内存的最近最少使用原则,就是 LRU(Least Recently Used)算法决定哪些内存页面先释放,因为Page Cache的内存页面只是起到了cache的作用,自然会被优先释放的。

Page Cache:为了提高文件磁盘读写性能,而利用空闲内存的机制。同时内存管理中的页面回收机制,又能保证Cache 所占用的页面可以及时释放,这样一来就不会影响程序对内存的真正需求了。

2.4、RSS & Page Cache in Memory Cgroup

下面看下 RSS和 Page Cache 如何影响 Memory Cgroup工作的。

Linux 内核代码中,从mem_cgroup_charge_statistics() 函数里看到,Memory Cgroup 只统计了 RSS和Page Cache 这两部分内存。

RSS :当前Memory Cgroup 控制组里所有进程的RSS的总和;
Page Cache:控制组的进程读写磁盘文件后,被放入到Page Cache 里的物理内存。
在这里插入图片描述

Memory Cgroup 控制组里的 RSS 内存和 Page Cache 内存的和,正好是memory.usage_in_bytes 的值。

当控制组里的进程需要申请新的物理内存,而且memory.usage_in_bytes 里的值超过控制组里的内存上限 memory.limit_in_bytes,这时我们前面说的Linux的内存回收(page frame reclaim)就会被调用起来

控制组里的 Page Cache 的内存会根据心申请的内存大小释放一部分,这样我们还是能成功申请到新的物理内存,整个控制组里总的内存开销 memory.usage_in_bytes 还是不会超过上限值 memory.limit_in_bytes

三、解决问题

容器里肯定有大于 50MB的内存是 Page Cache ,因为作为Page Cache 的内存在系统需要新申请物理内存的时候(作为RSS)是可以被释放的。

在Memory Cgroup 中有一个参数 memory.stat 可以显示在当前控制组里各种内存类型的实际的开销。

https://github.com/chengyli/training/blob/main/memory/page_cache/start_container.sh

启动容器后,这次我们不仅要看 memory.usage_in_bytes 还要看一下 memory.stat ,虽然 memory.stat 里的参数不少,我们目前只需要关注 cache 和 rss 这两个值。

容器启动后,cache值也就是 Page Cache 占的内存是bytes,大概是 99MB,而 RSS 占的内存只有 bytes,也就是 1MB 多一点。

这就意味着,在这个容器的 Memory Cgroup里大部分的内存都被用作了 Page Cache,而这部分内存是可以被回收的。

在这里插入图片描述

https://github.com/chengyli/training/blob/main/memory/page_cache/mem-alloc/mem_alloc.c

我们可以再来查看一下 memory.stat,这时候 cache 的内存值降到了 bytes,大概 46MB,而 rss 的内存值到了 bytes,54MB 左右吧。总的 memory.usage_in_bytes 值和之前相比,没有太多的变化。

在这里插入图片描述

四、重点总结

Memory Cgroup 在统计每个控制组的内存使用时包含了两部分:RSS和 Page Cache

RSS:每个进程实际占用的物理内存,包括了进程的代码段内存,进程运行时需要的堆和栈的内存,这部分内存是进程运行所必须的。

Page Cache:进程在运行中读写磁盘后,作为cache而继续保留在内存中的,它的目的是为了提高文件磁盘的读写性能。

当节点内存紧张或者 Memory Cgroup 控制组的内存达到上限时,Linux 会对内存做回收操作。这个时候 Page Cache 的内存页面会被释放,这样空出来的内存就可以分配给新的内存申请。

因为在频繁磁盘访问的容器中,因为Page Cache的这种cache特性,我们往往会看到它的内存使用率一直接近容器内存的限制值(memory.limit_in_bytes)。

这个时候不用担心它的内存不够,判断容器的内存状态的时候,可以忽略Page Cache 部分,考虑RSS的内存使用量

五、评论

1、 很重要!!

Memory Cgroup 应该包含了对内核内存的限制,老师给出的例子比较简单,基本没有使用slab,可以试下在容器中打开海量小文件,内核内存inode,dentry等会被计算在内。

Memory Cgroup OOM 不是根据memory.usage_in_bytes 判定的。
而是依据 working set (使用量 减去非活跃 file-backed 内存):
working set = memory.usage_in_bytes - total_inactive_file

2、
问题:
问一个操作系统相关的问题。根据我的理解,操作系统为了性能会在刷盘前将内容放在page cache中(如果可以申请的话),后续合适的时间刷盘。如果是这样的话,在一定条件下,可能还没刷盘,这个内存就需要释放给rss使用。这时必然就会先刷盘。这样会导致 系统 malloc 的停顿,对吗?如果是这样的话,另外一个问题就是 linux 是如何保证 磁盘的数据的 crash safe 的呢?

3、
问题:page_cache是不是会被很多进程共享呢,比如同一个文件需要被多个进程读写,这样的话,page_cache会不会无法被释放呢?

另外,老师能不能讲解下,这里面的page_cache和free中的cache、buffer、shared还有buffer cache的区别呢?

free 里的cache/buffer 就是page cache,早期Linux 文件相关的cache 内存分 buffer cache和page cache ,现在统一成page cache了。 shared内存一般是tmpfs 内存文件系统用到的内存。

4、
问题:
请问老师:这边结合了课程内容以及评论中的一些补充想在确认一下以下几个问题:
1 Memory Cgroup OOM 的依据是working set吗?还是说rss,working set都会进行判断
2 这边看到有评论的大佬给了对于memory.usage_in_bytes 以及working_set ,但是对这两个间的关系有一些疑惑,想请问一下老师是否可以理解为working_set = memory.stat[rss] + memory.kmem.usage_in_bytes+常用的page cache 这样?

回答:
OOM 判断还是根据:
新申请的内存+memory.usage_in_bytes - reclaim memory > memory.limit_in_bytes 来判断的

working_set 应该是cAdvsior 里的一个概念,可以看一下这段代码。它的定义是 memory.usage_in_bytes - inactive_file_memory ,不过在 memory reclaim(回收) 的时候,可以reclaim(回收) 的memory 是大于 inactive_file memory的。

https://github.com/google/cadvisor/blob/master/container/libcontainer/handler.go#L870

5、
问题:
请问文中提到rss包括共享内存的大小,那pss呢,pss和rss的区别不是 是否包括共享内存的大小码?

6
问题:
老师,如果新程序申请的内存大小是大于之前进程的page cache内存大小的;是不是就会发生oom?

六、补充知识

启动一个容器,看看在容器中,cat 一个大文件前后,memory.stat中的rss和cache分别是什么情况、

小讯
上一篇 2025-01-18 09:31
下一篇 2025-01-11 16:53

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/19194.html