Linux关于memory cgroup的几个要点

2023-10-29

概述

本文讲述memory cgroup比较容易误解的一些逻辑,如果不太经常使用和解决问题的话,对于memory cgroup的认知会比较浅显:cgroup memory用来限制进程的内存使用,但是我们进一步想如下的问题:

  1. 进程的内存可以分很多类型,比如page cache,slab,anon memory等,到底是限制的哪些内存?
  2. 如果进程A已经运行起来占用了一些内存,之后,再将A加入memory cgroup限制,原来占用的内存会统计入新的memory cgroup?
  3. memory cgroup有memory.soft_limit_in_bytes和memory.limit_in_bytes,假设进程使用内存超过这两个限制,内存回收时机和路径是怎么样的?
  4. 我们知道内核回收页面采用lru算法,同时memcg也有per node lru,这两个lru是什么关系?

被误解的cgroup内存限制

结论:Cgroup 内存范围包括进程RSS 及该进程首次触发加载进Page Cache 所占用的内存,但不包括Slab 部分。

我们怎么从源码确认page cache是被cgroup限制的呢?charge逻辑:我们知道新页面产生的时候,内核会charge增加cgroup内存使用的统计,所以最直接的方式我们看下read或者write产生page cache是否存在charge逻辑,如果存在说明进程pagecache也是被cgroup限制的。

上述函数的调用栈:

#0  try_to_free_mem_cgroup_pages (memcg=0xffff8880009ba000, nr_pages=1, gfp_mask=1125578, may_swap=true) at mm/vmscan.c:3326
#1  0xffffffff81422729 in try_charge (memcg=0xffff8880009ba000, gfp_mask=<optimized out>, nr_pages=<optimized out>) at mm/memcontrol.c:2703
#2  0xffffffff81425f56 in mem_cgroup_charge (page=0xffffea0000019240, mm=<optimized out>, gfp_mask=<optimized out>) at mm/memcontrol.c:6718
#3  0xffffffff8132bed0 in __add_to_page_cache_locked (page=0xffffea0000019240, mapping=0xffff888002826330, offset=65, gfp_mask=1125578, shadowp=0xffff888000b27650) at ./arch/x86/include/asm/current.h:15
#4  0xffffffff8132c224 in add_to_page_cache_lru (page=0xffffea0000019240, mapping=0xffff888002826330, offset=65, gfp_mask=1125578) at mm/filemap.c:922
#5  0xffffffff81344d9b in page_cache_readahead_unbounded (mapping=<optimized out>, file=<optimized out>, index=65, nr_to_read=<optimized out>, lookahead_size=<optimized out>) at mm/readahead.c:228
#6  0xffffffff81344eeb in __do_page_cache_readahead (mapping=0xffff888002826330, file=0xffff8880054fca00, index=<optimized out>, nr_to_read=32, lookahead_size=32) at mm/readahead.c:273
#7  0xffffffff8134518f in ra_submit (filp=<optimized out>, mapping=<optimized out>, ra=<optimized out>) at mm/internal.h:64
#8  ondemand_readahead (mapping=0xffff888002826330, ra=0xffff8880054fca98, filp=<optimized out>, hit_readahead_marker=<optimized out>, index=64, req_size=<optimized out>) at mm/readahead.c:551
#9  0xffffffff813454cd in page_cache_async_readahead (page=<optimized out>, req_count=<optimized out>, index=<optimized out>, filp=<optimized out>, ra=<optimized out>, mapping=<optimized out>)
    at mm/readahead.c:631
#10 page_cache_async_readahead (mapping=0xffff888002826330, ra=0xffff8880054fca98, filp=<optimized out>, page=0xffffffff8332f0b0 <cgrp_dfl_root+16>, index=<optimized out>, req_count=<optimized out>)
    at mm/readahead.c:604
#11 0xffffffff8132eba7 in generic_file_buffered_read (iocb=0xffff888000b27ad8, iter=0xffff888000b27a78, written=0) at mm/filemap.c:2220
#12 0xffffffff8132f674 in generic_file_read_iter (iocb=0xffff888000b27ad8, iter=0xffff888000b27a78) at mm/filemap.c:2520

可以看到read文件产生pagecache,最终要在add_to_page_cache_lru加入到address_space radix_tree和相对应的lru链表中,进而调用到mem_cgroup_charge逻辑,所以确认了我们的结论。

进程已运行后,加入Cgroup A中,已经使用的内存是否迁移统计入A 

当一个进程从一个cgroup移动到另一个cgroup时,默认情况下,该进程已经占用的内存还是统计在原来的cgroup里面,不会占用新cgroup的配额,但新分配的内存会统计到新的cgroup中(包括swap out到交换空间后再swap in到物理内存中的部分)。

我们可以通过设置memory.move_charge_at_immigrate让进程所占用的内存随着进程的迁移一起迁移到新的cgroup中。

enable: echo 1 > memory.move_charge_at_immigrate
disable:echo 0 > memory.move_charge_at_immigrate

注意: 就算设置为1,但如果不是thread group的leader,这个task占用的内存也不能被迁移过去。换句话说,如果以线程为单位进行迁移,必须是进程的第一个线程,如果以进程为单位进行迁移,就没有这个问题。

当memory.move_charge_at_immigrate被设置成1之后,进程占用的内存将会被统计到目的cgroup中,如果目的cgroup没有足够的内存,系统将尝试回收目的cgroup的部分内存(和系统内存紧张时的机制一样,删除不常用的file backed的内存或者swap out到交换空间上,如果回收不成功,那么进程迁移将失败。

memory.soft_limit_in_bytes和memory.limit_in_bytes内存回收时机

有了hard limit(memory.limit_in_bytes),为什么还要soft limit呢?hard limit是一个硬性标准,绝对不能超过这个值,而soft limit可以被超越,既然能被超越,要这个配置还有啥用?先看看它的特点

  1. 当系统内存充裕时,soft limit不起任何作用

  2. 当系统内存吃紧时,系统会尽量的将cgroup的内存限制在soft limit值之下(内核会尽量,但不100%保证)

从它的特点可以看出,它的作用主要发生在系统内存吃紧时,如果没有soft limit,那么所有的cgroup一起竞争内存资源,占用内存多的cgroup不会让着内存占用少的cgroup,这样就会出现某些cgroup内存饥饿的情况。如果配置了soft limit,那么当系统内存吃紧时,系统会让超过soft limit的cgroup释放出超过soft limit的那部分内存(有可能更多),这样其它cgroup就有了更多的机会分配到内存。

从上面的分析看出,这其实是系统内存不足时的一种妥协机制,给次等重要的进程设置soft limit,当系统内存吃紧时,把机会让给其它重要的进程。

注意: 当系统内存吃紧且cgroup达到soft limit时,系统为了把当前cgroup的内存使用量控制在soft limit下,在收到当前cgroup新的内存分配请求时,就会触发回收内存操作,所以一旦到达这个状态,就会频繁的触发对当前cgroup的内存回收操作,会严重影响当前cgroup的性能。

结论: 

soft_limit_in_bytes只有触发kswapd或者direct reclaim时候才会进行顺道的回收

limit_in_bytes:新页面产生时候,charge增加使用计数,如果超过limit_in_bytes就会回收。

全局LRU和memcg LRU的关系

结论:我们经常讨论的全局LRU其实对应root_mem_cgroup的per node LRU。

假设目前系统没有设置任何的cgroup,那么只有root_mem_cgroup这个memcg,只要配置CONFIG_CGROUP,内核初始化的时候就会初始化root cgroup。那么我们read/write产生pagecache情况下,新产生page加入lru的代码,看看到底加入的哪个LRU?

lru_cache_add
    --->__pagevec_lru_add
        --->pagevec_lru_move_fn

static void pagevec_lru_move_fn(struct pagevec *pvec,
    void (*move_fn)(struct page *page, struct lruvec *lruvec, void *arg),
    void *arg)
{
    int i;
    struct pglist_data *pgdat = NULL;
    struct lruvec *lruvec;
    unsigned long flags = 0;

    for (i = 0; i < pagevec_count(pvec); i++) {
        struct page *page = pvec->pages[i];
        struct pglist_data *pagepgdat = page_pgdat(page);

        if (pagepgdat != pgdat) {
            if (pgdat)
                spin_unlock_irqrestore(&pgdat->lru_lock, flags);
            pgdat = pagepgdat;
            spin_lock_irqsave(&pgdat->lru_lock, flags);
        }
        //内核通过mem_cgroup_page_lruvec获取加入的LRU,由于我们没有配置任何cgroup,
        //那么此时产生的page对应的lru就是root_mem_cgroup的pgdat这个node的 lru
        lruvec = mem_cgroup_page_lruvec(page, pgdat);
        (*move_fn)(page, lruvec, arg);
    }
    if (pgdat)
        spin_unlock_irqrestore(&pgdat->lru_lock, flags);
    release_pages(pvec->pages, pvec->nr);
    pagevec_reinit(pvec);
}

如上面代码注释,最终mem_cgroup_page_lruvec获取到page->memcg的pgdat node对应的lruvec,而这里page->memcg又是指向哪里的,由于目前系统没有配置任何的cgroup,这个page->memcg就指向root_mem_cgroup,那么page->memcg赋值的地方在哪里的,针对我们目前read pagecache这种场景,最终是在mm/memcontrol.c :commit_charge里面,调用栈:

remote Thread 1 In: mem_cgroup_charge                                                                                                                                            Line: 6723 PC: 0xffffffff8142f6cd 
#0  commit_charge (page=<optimized out>, memcg=<optimized out>)	at mm/memcontrol.c:6723
#1  mem_cgroup_charge (page=0xffffea00000c7600,	mm=<optimized out>, gfp_mask=<optimized out>) at mm/memcontrol.c:6723
#2  0xffffffff81330f80 in __add_to_page_cache_locked (page=0xffffea00000c7600, mapping=0xffff888006245e80, offset=4, gfp_mask=1125578, shadowp=0xffff888006557650) at ./arch/x86/include/asm/current.h:15
#3  0xffffffff813312d4 in add_to_page_cache_lru (page=0xffffea00000c7600, mapping=0xffff888006245e80, offset=4,	gfp_mask=1125578) at mm/filemap.c:922
#4  0xffffffff8134a0eb in page_cache_readahead_unbounded (mapping=<optimized out>, file=<optimized out>, index=4, nr_to_read=<optimized out>, lookahead_size=<optimized out>) at mm/readahead.c:228
#5  0xffffffff8134a25b in __do_page_cache_readahead (mapping=0xffff888006245e80, file=0xffff88800548b640, index=<optimized out>, nr_to_read=32,	lookahead_size=16) at mm/readahead.c:273
#6  0xffffffff8134a4ff in ra_submit (filp=<optimized out>, mapping=<optimized out>, ra=<optimized out>)	at mm/internal.h:64
#7  ondemand_readahead (mapping=0xffff888006245e80, ra=0xffff88800548b6d8, filp=<optimized out>, hit_readahead_marker=<optimized out>, index=0,	req_size=<optimized out>) at mm/readahead.c:551
#8  0xffffffff8134aac8 in page_cache_sync_readahead (req_count=<optimized out>,	index=<optimized out>, filp=<optimized out>, ra=<optimized out>, mapping=<optimized out>) at mm/readahead.c:585
#9  page_cache_sync_readahead (mapping=<optimized out>,	ra=0xffff88800548b6d8, filp=0xffff88800548b640,	index=<optimized out>, req_count=<optimized out>) at mm/readahead.c:567
#10 0xffffffff81333bf1 in generic_file_buffered_read (iocb=0xffff888006557ad8, iter=0xffff888006557a78,	written=0) at mm/filemap.c:2208
#11 0xffffffff81334776 in generic_file_read_iter (iocb=0xffff888006557ad8, iter=0xffff888006557a78) at mm/filemap.c:2520

commit_charge如下:

参考:

Linux内核mem_cgroup浅析-wzzushx-ChinaUnix博客 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux关于memory cgroup的几个要点 的相关文章

  • 无法连接到 Azure Ubuntu VM - 公钥被拒绝

    我们在 Azure 上使用 Ubuntu VM 一段时间了 很少遇到任何问题 然而 其中一台虚拟机最近出现了问题 出乎意料的是 Ubuntu VM 开始拒绝公钥 ssh i azure key email protected cdn cgi
  • 如何在 Linux 中向热敏打印机发送 ESC/POS 命令

    我正在尝试在热敏打印机上发送 ESC POS 命令 但每当我发送它们时 热敏打印机都会将它们打印为文本 而不是作为命令执行它们 我在 prn 文件中编写这些命令 每当我执行 lp 命令来打印文件时 这些 prn 文件也会被打印 但作为文本
  • 通过名称获取进程ID

    我想在 Linux 下获得一个给定其名称的进程 ID 有没有一种简单的方法可以做到这一点 我还没有在 C 上找到任何可以轻松使用的东西 如果追求 易于使用 char buf 512 FILE cmd pipe popen pidof s p
  • Linux 中的 Windows NAmed Pipes 替代品

    我们正在将现有的 Windows 代码移植到 Linux 我们使用 ACE 作为抽象层 我们使用 Windows 命名管道与多个客户端进行通信并执行重叠操作 linux 下这个相当于什么 我检查了linux命名管道 FIFO 但它们似乎只支
  • 用于 e NetworkManager VPN 连接的 dbus 信号处理程序

    我需要开发一些在建立 VPN 连接时执行的 python 代码 VPN 由 NetworkManager 控制 我试图弄清楚如何为此使用 NM DBUS 事件 使用 dbus monitor system 我能够识别连接信号 signal
  • 每个虚拟主机的错误日志?

    在一台运行 Apache 和 PHP 5 的 Linux 服务器上 我们有多个带有单独日志文件的虚拟主机 我们似乎无法分离 phperror log虚拟主机之间 覆盖此设置
  • 重新链接匿名(未链接但打开)文件

    在 Unix 中 可以创建匿名文件的句柄 例如 使用 creat 创建并打开它 然后使用 unlink 删除目录链接 留下一个带有 inode 和存储的文件 但没有可能的方法重新打开它 此类文件通常用作临时文件 通常这就是 tmpfile
  • 进程如何知道它已收到信号

    如果我错了 请纠正我 以下是我对信号的理解 据我所知 信号生成 和信号传递有2个不同 事物 为了产生信号 操作系统只是在位数组中设置一个位 在过程控制中维护 工艺块 PCB 每一位 对应于特定信号 当设置一个位时 这意味着 该位对应的信号为
  • X 按键/释放事件捕获,与焦点窗口无关

    我想记录所有传入的按键事件 无论哪个窗口处于焦点状态或指针位于何处 我编写了一个示例代码 它应该捕获当前焦点窗口的按键事件 include
  • sudo pip install python-Levenshtein 失败,错误代码 1

    我正在尝试在 Linux 上安装 python Levenshtein 库 但每当我尝试通过以下方式安装它时 sudo pip install python Levenshtein 我收到此错误 命令 usr bin python c 导入
  • 使用 hcitool 扫描低功耗蓝牙?

    当我运行此命令时 BLE 设备扫描仅持续 5 秒 sudo timeout 5s hcitool i hci0 lescan 输出显示在终端屏幕中 但是 当我将输出重定向到文件以保存广告设备的地址时 每次运行该命令时 我都会发现该文件是空的
  • ARM 的内核 Oops 页面错误错误代码

    Oops 之后的错误代码给出了有关 ARM EX 中的恐慌的信息 Oops 17 1 PREEMPT SMP在这种情况下 17 给出了信息 在 x86 中它代表 bit 0 0 no page found 1 protection faul
  • php56 - CentOS - Remi 仓库

    我刚刚在测试盒上安装了 php 5 6 正常的 cli php 解释器似乎不存在 gt php v bash php command not found gt php56 v PHP 5 6 13 cli built Sep 3 2015
  • 路由是否会影响具有绑定源地址的套接字?

    假设我有两个网络接口 eth0有地址10 0 0 1 eth1有地址192 168 0 1 Using route or ip route add我已将其设置为路由 所有地址至eth0 1 2 3 4只为了eth1 所以数据包到1 2 3
  • 我们可以在 Bash 脚本中使用 PHP 吗?

    我有一个 bash 脚本abcd sh bin sh for i in seq 8 do ssh w i uptime ps elf grep httpd wc l free m mpstat done pid sleep 1 kill 9
  • Laravel 内存问题?

    各位 我在 DO 服务器上遇到这样的问题 我已经尝试了一切 整个网站在使用 Homestead 的 Linux 服务器上 100 正常工作 但上传后 它只能工作一次 在重新加载或刷新页面后会多次下降 我尝试增加 apache 服务器的内存
  • 在哪里可以找到所有 C 标准库的源代码?

    我正在寻找所有 C 标准库的完整源代码 也就是说 我正在寻找 stdio h stdlib h string h math h 等的源代码 我想看看它们是如何创建的 我认为这取决于不同的平台 但 Linux 或 Windows 都会受到欢迎
  • 如何在 Linux 中显示进程状态(阻塞、非阻塞)

    有没有办法查询 Linux 进程表中进程的状态 以便能够演示执行查询时进程是正在运行还是被阻止 我的目标是从进程或程序的 外部 执行此操作 因为我希望从操作系统进程的角度来理解这一点 但欢迎任何想法 这是Python代码阻塞的过程 impo
  • 我如何知道用户在使用 ncurses (Linux) 的控制台中按下了 ESC 键?

    I have a problem in detecting whether I just got a plain ESC key just code 27 or whether it was another special key such
  • 如何从python导入路径中删除当前目录

    我想使用 Mercurial 存储库hg本身 也就是说 我克隆了 Mercurialhttps www mercurial scm org repo hg https www mercurial scm org repo hg并想运行一些h

随机推荐

  • 对数器(一种测试算法的技巧)

    当我们有两个算法 一个是暴力算法 一个是好的算法 我们想看是否这个好的算法存在有问题 因为暴力算法一般比较好写并且不会出错 但是会超时 当然暴力也可能错 我们就用一个随机样本产生器 生成数据分别用这两个算法跑 是否结果完全一样 不一样说明其
  • 5 分钟搭建一个简洁优雅的静态博客

    你可能会问 现在写作平台都这么多了 还有必要自己折腾博客么 一开始我遇到这个问题 总觉得自己弄个博客 太麻烦了 也不一定坚持写下去 直接在平台上写得了 于是 CSDN 简书 知乎上开始写 写着写着 我就发现他们有一些缺点 为什么需要博客 C
  • 网络安全——漏洞扫描工具(AWVS的使用)

    一 安全漏洞产生的原因 二 什么是0day漏洞 在安全漏洞生命周期内 从安全漏洞被发现到厂商发布补丁程序用于修复该漏洞之前 三 什么是安全漏洞生命周期 一共分为7个阶段 四 安全漏洞管理 1 2 安全漏洞等级 1 微软设置了4个等级 低危
  • Android 登录处理

    今天整理一下之前在项目中写的关于某些界面需要登录判断处理 这里整理了一个简易的 Demo 模拟一下 登录情况 和 未登录情况 下的界面跳转处理 效果如图 以上分别模拟了 未登录和已登录 情况下的 界面跳转和当前界面事件处理 接下来我们来看一
  • 【程序设计训练】4-5 小希的数表

    问题描述 Gardon 昨天给小希布置了一道作业 即根据一张由不超过 5000 的 N 3 lt N lt 100 个正整数组成的数表两两相加得到 N N 1 2 个和 然后再将它们排序 例如 如果数表里含有四个数 1 3 4 9 那么正确
  • Spring Cloud Eureka源码分析之心跳续约及自我保护机制

    Eureka Server是如何判断一个服务不可用的 Eureka是通过心跳续约的方式来检查各个服务提供者的健康状态 实际上 在判断服务不可用这个部分 会分为两块逻辑 Eureka Server需要定期检查服务提供者的健康状态 Eureka
  • java版工程项目管理系统源码+系统管理+系统设置+项目管理+合同管理+二次开发

    工程项目各模块及其功能点清单 一 系统管理 1 数据字典 实现对数据字典标签的增删改查操作 2 编码管理 实现对系统编码的增删改查操作 3 用户管理 管理和查看用户角色 4 菜单管理 实现对系统菜单的增删改查操作 5 角色管理 管理和查看用
  • 7月7日!GLM大模型技术前沿与应用探索

    点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入 随着AIGC时代的到来 大型语言模型逐渐成为学术界和工业界的关注焦点 近期 各种大语言模型的涌现给自然语言处理领域的研究带来了诸多挑战 也逐渐对计算机视觉和计算机生物等领域产生了
  • KeyError: 'Spider not found:name一样,为何还是找不到spider 多种解决方案,总有一个适合你!

    第一种 运行的main文件中的爬虫名字与爬虫文件中的name不相同 解决方案 令两者名字相同即可 第二种 爬虫文件夹中缺少 init 文件 一开始是直接在spider文件夹下创建的爬虫文件 然后把多个爬虫文件放进stt的文件夹中 运行mai
  • windows快速搭建caffe环境

    下载预编译文件 下载预编译文件 跳转链接 http caffe berkeleyvision org installation html https github com BVLC caffe tree windows 创建虚拟环境 由于本
  • SpringCloud的优势

    1 代码耦合度较低 不会影响其他模块的开发 2 极大的减轻了团队开发成本 可并行开发 不用过多关注其他人怎么开发 3 配置比较简单 基本用注解就能实现 不能使用过多的配置文件 4 微服务操作 实现跨平台的 可以使用不同的语言开发 5 每个微
  • 用Python手撕一个批量填充数据到excel表格的工具,解放双手!

    作者 锋小刀 微信搜索 Python与Excel之交 关注我的公众号查看更多内容 Hi 大家好 今天这篇文章是根据批量填充数据的进阶版 基础版本就一段很简单的代码 虽然简单 但如果这个模板或者数据发生变化 还是要改来改去的 所以本文就在基础
  • 华为OD机试 -表示数字(C++ & Java & JS & Python)

    描述 将一个字符串中所有的整数前后加上符号 其他字符保持不变 连续的数字视为一个整数 数据范围 字符串长度满足 1 100 1 n 100 输入描述 输入一个字符串 输出描述 字符中所有出现的数字前后加上符号 其他字符保持不变 示例1 输入
  • 如何使用 Python 从单词创建首字母缩略词

    在编程和数据处理中 首字母缩略词是句子的缩写版本 Python 是一种有效的语言 用于构造首字母缩略词 简化任务和简单地传达更大的句子 本课展示了如何使用 Python 及其一些潜在的应用程序从单词中制作首字母缩略词 算法 您需要安装任何其
  • 2023前端最新【JavaScript面试题】高频30问

    JavaScript是一种轻量级的编程语言 常被用于网页开发中 它是一种解释型语言 常常被用于实现前端逻辑 在Javascript的面试中 以下是一些常见的问题 1 Javascript的数据类型有哪些 Javascript有七种数据类型
  • 蓝桥杯-2021省赛第一场(部分)

    试题B 卡片 本题总分 5 分 问题描述 小蓝有很多数字卡片 每张卡片上都是数字 0 到 9 小蓝准备用这些卡片来拼一些数 他想从 1 开始拼出正整数 每拼一个 就保存起来 卡片就不能用来拼其它数了 小蓝想知道自己能从 1 拼到多少 例如
  • rj45接口引脚定义_RJ45插座工作原理及接口定义分析

    rj45插座工作原理分析 rj45插座滤波器中poe的工作原理是透过 局域网 网络线提供access point 无线接取点 工作所需之电源 并不需要单独连接电源线缆的全新供电方式 现许多企业已开始应用rj45插座滤波器中poe来降低组建无
  • onTaskRemoved() not getting called in HUAWEI and XIOMI devices

    http stackoverflow com questions 40660216 ontaskremoved not getting called in huawei and xiomi devices 20 down vote favo
  • 持仓盈亏公式

    添加持仓盈亏 卖出数量必须小于等于持仓量 卖出买入数量必须是100的整数倍数且大于0 当前价不影响 摊博成本 持仓成本 持有量 买入价 买入数量 卖出价 卖出数量 都是当前添加数据进行计算 持仓成本 买入价 买入数量 买入价 买入数量 列表
  • Linux关于memory cgroup的几个要点

    概述 本文讲述memory cgroup比较容易误解的一些逻辑 如果不太经常使用和解决问题的话 对于memory cgroup的认知会比较浅显 cgroup memory用来限制进程的内存使用 但是我们进一步想如下的问题 进程的内存可以分很