c语言sscanf函数的用法是什么
292
2022-10-20
容器内存使用量为什么总是在临界点?
对于OOM Killer想必都有一些了解,如果容器使用的物理内存超过了Memory Cgroup里的 memory.limit_in_bytes值,那么容器进程就会被OOM Killer 杀死。
有个疑问,在一些容器的使用场景中,比如容器里的应用有很多文件读写,就会发现整个容器的内存使用量已经很接近Memory Cgroup的上限值,但是在容器中接着申请内存,还是可以申请出来,并没有发生OOM,那么这是怎么回事?
一、场景复现
执行如下脚本启动容器,设置容器Memory Cgroup内存上限值 100MB(104857600 bytes)。
#!/bin/bashdocker stop page_cache;docker rm page_cacheif [ ! -f ./test.file ]then dd if=/dev/zero of=./test.file bs=4096 count=30000 echo "Please run start_container.sh again " exit 0fiecho 3 > /proc/sys/vm/drop_cachessleep 10docker run -d --init --name page_cache -v $(pwd):/mnt shijuliu/page_cache_test:v1CONTAINER_ID=$(sudo docker ps --format "{{.ID}}\t{{.Names}}" | grep -i page_cache | awk '{print $1}')echo $CONTAINER_IDCGROUP_CONTAINER_PATH=$(find /sys/fs/cgroup/memory/ -name "*$CONTAINER_ID*")echo 104857600 > $CGROUP_CONTAINER_PATH/memory.limit_in_bytescat $CGROUP_CONTAINER_PATH/memory.limit_in_bytes
查看容器内存上限值为104857600bytes(100MB),整个容器已使用内存为104767488bytes,两个值相差大概90KB左右。
再继续启动一个程序,让这个程序申请并使用50MB物理内存,可以发现该程序可以运行成功,容器并没有发生OOM。查看参数memory.usage_in_bytes,它的值变成了103186432bytes。
二、Linux系统内存类型
Linux 的各个模块都需要内存,比如内核需要分配内存给页表,内核栈,还有 slab,也就是内核各种数据结构的 Cache Pool;用户态进程里的堆内存和栈的内存,共享库的内存,还有文件读写的 Page Cache。
2.1、RSS
RSS 是 Resident Set Size 的缩写,简单来说它就是指进程真正申请到物理页面的内存大小。
应用程序在申请内存的时候,比如说,调用 malloc() 来申请 100MB 的内存大小,malloc() 返回成功了,这时候系统其实只是把 100MB 的虚拟地址空间分配给了进程,但是并没有把实际的物理内存页面分配给进程。
当进程对这块内存地址开始做真正读写操作的时候,系统才会把实际需要的物理内存分配给进程。而这个过程中,进程真正得到的物理内存,就是这个 RSS 了。
看如下代码
#include
运行程序,使用top查看程序运行了malloc()之后的内存,可看到程序的虚拟空间(VIRT)有了106728KB(约100MB),但是实际物理内存RSS(top 命令里显示的是 RES,就是 Resident 的简写)只有688KB。
等待程序30秒以后,在申请空间里写入20MB数据,再用top查看,此时虚拟地址空间(VIRT)还是 106728,不过物理内存 RSS(RES)的值变成了 21432(大小约为 20MB), 这里的单位都是 KB。
通过实验,可以验证RSS就是进程里真正获得的物理内存大小。
tips
RSS 内存包含了进程的代码段内存,栈内存,堆内存,共享库的内存, 这些内存是进程运行所必须的。通过 malloc/memset 得到的内存,就是属于堆内存。每一部分RSS内存大小,可以查看/proc/[pid]/smaps文件。
三、Page Cache
每个进程会有各自独立分配到的RSS内存,如果进程对磁盘上的文件做了读写操作,Linux还会分配内存。把磁盘上读写到的页面存放在内存中,这部分的内存就是 Page Cache。
Page Cache 的主要作用是提高磁盘文件的读写性能,因为系统调用 read() 和 write() 的缺省行为都会把读过或者写过的页面存放在 Page Cache 里。
在实验docker实例中代码程序读取100MB文件,在读取之前,系统中 Page Cache 的大小是 388MB,读取后 Page Cache 的大小是 506MB,增长了大约 100MB 左右,多出来的这 100MB,即读取的文件大小。
在 Linux 系统里只要有空闲的内存,系统就会自动地把读写过的磁盘文件页面放入到 Page Cache 里。那么这些内存都被 Page Cache 占用了,一旦进程需要用到更多的物理内存,执行 malloc() 调用做申请时,就会发现剩余的物理内存不够了,此时就要提到Linux的内存管理机制了。
Linux 的内存管理有一种内存页面回收机制(page frame reclaim),会根据系统里空闲物理内存是否低于某个阈值(wartermark),来决定是否启动内存的回收。
内存回收的算法会根据不同类型的内存以及内存的最近最少用原则,就是 LRU(Least Recently Used)算法决定哪些内存页面先被释放。因为 Page Cache 的内存页面只是起到 Cache 作用,是会被优先释放的。
所以,Page Cache 是一种为了提高磁盘文件读写性能而利用空闲物理内存的机制。同时,内存管理中的页面回收机制,又能保证 Cache 所占用的页面可以及时释放,这样一来就不会影响程序对内存的真正需求了。
四、RSS & Page Cache in Memory cgroup
RSS和Page Cache是如何影响Memory Cgroup的工作?从linux内核代码看,mem_cgroup_charge_statistics()函数中,可以看到Memory Cgroup的确统计了RSS和Page Cache两部分内存。
static void mem_cgroup_charge_statistics(struct mem_cgroup *memcg, struct page *page, bool compound, int nr_pages){ /* * Here, RSS means 'mapped anon' and anon's SwapCache. Shmem/tmpfs is * counted as CACHE even if it's on ANON LRU. */ if (PageAnon(page)) __mod_memcg_state(memcg, MEMCG_RSS, nr_pages); else { __mod_memcg_state(memcg, MEMCG_CACHE, nr_pages); if (PageSwapBacked(page)) __mod_memcg_state(memcg, NR_SHMEM, nr_pages); } if (compound) { VM_BUG_ON_PAGE(!PageTransHuge(page), page); __mod_memcg_state(memcg, MEMCG_RSS_HUGE, nr_pages); } /* pagein of a big page is an event. So, ignore page size */ if (nr_pages > 0) __count_memcg_events(memcg, PGPGIN, 1); else { __count_memcg_events(memcg, PGPGOUT, 1); nr_pages = -nr_pages; /* for event */ } __this_cpu_add(memcg->stat_cpu->nr_page_events, nr_pages);}
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。
五、解决问题 & 验证
知道了Memory Cgroup 中内存类型的统计方法,回到开头为什么 memory.usage_in_bytes 与 memory.limit_in_bytes 的值只相差了 90KB,在容器中还是可以申请出 50MB 的物理内存?
容器里肯定有大于 50MB 的内存是 Page Cache,因为作为 Page Cache 的内存在系统需要新申请物理内存的时候(作为 RSS)是可以被释放的。
验证RSS可以被释放:
执行脚本启动容器,设置好容器Memory Cgroup里memory.limit_in_bytes 值为 100MB。查看memory.usage_in_bytes和memory.stat(关注cache和rss)的值
可以看到,容器启动后,cache也就是Page Cche占的内存是99508224bytes,约99MB,而RSS占的内存只有1826816bytes,也就是 1MB 多一点。
这说明,容器的 Memory Cgroup 里大部分的内存都被用作了 Page Cache,而这部分内存是可以被回收的。
再执行下mem_alloc程序,申请50MB物理内存。
查看memory.stat可以看到cache的内存值降到了46632960bytes,大概 46MB,而 rss 的内存值到了 54759424bytes,54MB 左右。总的memory.usage_in_bytes 值和之前相比,没有太多的变化。
从而发现,Page Cache内存对我们判断容器实际内存使用率的影响,目前 Page Cache 完全就是 Linux 内核的一个自动的行为,只要读写磁盘文件,只要有空闲的内存,就会被用作 Page Cache。
因此,判断容器真实的内存使用量,不能用Memory Cgroup里的memory.usage_in_bytes,而需要用 memory.stat 里的 rss 值。这个很像用free命令查看节点的可用内存,不能看”free”字段下的值,而要去除Page Cache之后的“available”字段下的值。
六、总结
RSS 是每个进程实际占用的物理内存,它包括了进程的代码段内存,进程运行时需要的堆和栈的内存,这部分内存是进程运行所必须的。
Page Cache 是进程在运行中读写磁盘文件后,作为 Cache 而继续保留在内存中的,它的目的是为了提高磁盘文件的读写性能。
当节点的内存紧张或者 Memory Cgroup 控制组的内存达到上限的时候,Linux 会对内存做回收操作,这个时候 Page Cache 的内存页面会被释放,这样空出来的内存就可以分配给新的内存申请。
Memory Cgroup OOM 不是真正依据内存使用量 memory.usage_in_bytes,而是依据 working set(使用量减去非活跃 file-backed 内存),working set 计算公式:working_set = memory.usage_in_bytes - total_inactive_file
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~