Linux中进程内存RSS与cgroup内存的RSS统计 - 差异

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
简介:

转载一篇关于进程内存计算和CGROUP内存计算差异的文章
http://hustcat.github.io/memory-usage-in-process-and-cgroup/

在Linux内核,对于进程的内存使用与Cgroup的内存使用统计有一些相同和不同的地方。
进程的内存统计
一般来说,业务进程使用的内存主要有以下几种情况:
(1)用户空间的匿名映射页(Anonymous pages in User Mode address spaces),比如调用malloc分配的内存,以及使用MAP_ANONYMOUS的mmap;当系统内存不够时,内核可以将这部分内存交换出去;
(2)用户空间的文件映射页(Mapped pages in User Mode address spaces),包含map file和map tmpfs;前者比如指定文件的mmap,后者比如IPC共享内存;当系统内存不够时,内核可以回收这些页,但回收之前可能需要与文件同步数据;
(3)文件缓存(page in page cache of disk file);发生在程序通过普通的read/write读写文件时,当系统内存不够时,内核可以回收这些页,但回收之前可能需要与文件同步数据;
(4)buffer pages,属于page cache;比如读取块设备文件。

其中(1)和(2)是算作进程的RSS,(3)和(4)属于page cache。

进程的内存统计

与进程内存统计相关的几个文件:

/proc/[pid]/stat
(23) vsize  %lu
        Virtual memory size in bytes.
(24) rss  %ld
        Resident Set Size: number of pages the process has
        in real memory.  This is just the pages which count
        toward text, data, or stack space.  This does not
        include pages which have not been demand-loaded in,
        or which are swapped out.

RSS的计算:
对应top的RSS列,do_task_stat源码

#define get_mm_rss(mm)                    \
    (get_mm_counter(mm, file_rss) + get_mm_counter(mm, anon_rss))

即RSS=file_rss + anon_rss
statm的介绍

/proc/[pid]/statm
Provides information about memory usage, measured in pages.
The columns are:

  size       (1) total program size
             (same as VmSize in /proc/[pid]/status)
  resident   (2) resident set size
             (same as VmRSS in /proc/[pid]/status)
  share      (3) shared pages (i.e., backed by a file)
  text       (4) text (code)
  lib        (5) library (unused in Linux 2.6)
  data       (6) data + stack
  dt         (7) dirty pages (unused in Linux 2.6)

statm统计信息相关源码见函数proc_pid_statm

int task_statm(struct mm_struct *mm, int *shared, int *text,
           int *data, int *resident)
{
    *shared = get_mm_counter(mm, file_rss);
    *text = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK))
                                >> PAGE_SHIFT;
    *data = mm->total_vm - mm->shared_vm;
    *resident = *shared + get_mm_counter(mm, anon_rss);
    return mm->total_vm;
}

top的SHR=file_rss。
实际上,进程使用的共享内存,也是算到file_rss的,因为共享内存基于tmpfs。
anon_rss与file_rss的计算源码

static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma,
        unsigned long address, pmd_t *pmd,
        pgoff_t pgoff, unsigned int flags, pte_t orig_pte)
{
    if (flags & FAULT_FLAG_WRITE) {
        if (!(vma->vm_flags & VM_SHARED)) {
            anon = 1;///anon page
...
        if (anon) {
            inc_mm_counter(mm, anon_rss);
            page_add_new_anon_rmap(page, vma, address);
        } else {
            inc_mm_counter(mm, file_rss);
            page_add_file_rmap(page);

cgroup 的内存统计

stat file
memory.stat file includes following statistics

# per-memory cgroup local status

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).

相关代码

static void
mem_cgroup_get_local_stat(struct mem_cgroup *mem, struct mcs_total_stat *s)
{
    s64 val;

    /* per cpu stat */
    val = mem_cgroup_read_stat(&mem->stat, MEM_CGROUP_STAT_CACHE);
    s->stat[MCS_CACHE] += val * PAGE_SIZE;
    val = mem_cgroup_read_stat(&mem->stat, MEM_CGROUP_STAT_RSS);
    s->stat[MCS_RSS] += val * PAGE_SIZE;
    val = mem_cgroup_read_stat(&mem->stat, MEM_CGROUP_STAT_FILE_MAPPED);
    s->stat[MCS_FILE_MAPPED] += val * PAGE_SIZE;
    val = mem_cgroup_read_stat(&mem->stat, MEM_CGROUP_STAT_PGPGIN_COUNT);
    s->stat[MCS_PGPGIN] += val;
    val = mem_cgroup_read_stat(&mem->stat, MEM_CGROUP_STAT_PGPGOUT_COUNT);
    s->stat[MCS_PGPGOUT] += val;
    if (do_swap_account) {
        val = mem_cgroup_read_stat(&mem->stat, MEM_CGROUP_STAT_SWAPOUT);
        s->stat[MCS_SWAP] += val * PAGE_SIZE;
    }

    /* per zone stat */
    val = mem_cgroup_get_local_zonestat(mem, LRU_INACTIVE_ANON);
    s->stat[MCS_INACTIVE_ANON] += val * PAGE_SIZE;
    val = mem_cgroup_get_local_zonestat(mem, LRU_ACTIVE_ANON);
    s->stat[MCS_ACTIVE_ANON] += val * PAGE_SIZE;
    val = mem_cgroup_get_local_zonestat(mem, LRU_INACTIVE_FILE);
    s->stat[MCS_INACTIVE_FILE] += val * PAGE_SIZE;
    val = mem_cgroup_get_local_zonestat(mem, LRU_ACTIVE_FILE);
    s->stat[MCS_ACTIVE_FILE] += val * PAGE_SIZE;
    val = mem_cgroup_get_local_zonestat(mem, LRU_UNEVICTABLE);
    s->stat[MCS_UNEVICTABLE] += val * PAGE_SIZE;
}



数据结构

struct mem_cgroup {
...
    /*
     * statistics. This must be placed at the end of memcg.
     */
    struct mem_cgroup_stat stat;   //  统计数据
};

/* memory cgroup 统计值  
 * Statistics for memory cgroup.
 */
enum mem_cgroup_stat_index {
    /*
     * For MEM_CONTAINER_TYPE_ALL, usage = pagecache + rss.
     */
    MEM_CGROUP_STAT_CACHE,        /* # of pages charged as cache */
    MEM_CGROUP_STAT_RSS,       /* # of pages charged as anon rss */
    MEM_CGROUP_STAT_FILE_MAPPED,  /* # of pages charged as file rss */
    MEM_CGROUP_STAT_PGPGIN_COUNT,    /* # of pages paged in */
    MEM_CGROUP_STAT_PGPGOUT_COUNT,    /* # of pages paged out */
    MEM_CGROUP_STAT_EVENTS,    /* sum of pagein + pageout for internal use */
    MEM_CGROUP_STAT_SWAPOUT, /* # of pages, swapped out */

    MEM_CGROUP_STAT_NSTATS,
};

struct mem_cgroup_stat_cpu {
    s64 count[MEM_CGROUP_STAT_NSTATS];
} ____cacheline_aligned_in_smp;

struct mem_cgroup_stat {
    struct mem_cgroup_stat_cpu cpustat[0];
};



rss and cache

cache    - # of bytes of page cache memory. rss    - # of bytes of anonymous and swap cache memory (includes transparent hugepages).


static void mem_cgroup_charge_statistics(struct mem_cgroup *mem,
                     struct page_cgroup *pc,
                     long size)
{
...
    cpustat = &stat->cpustat[cpu];
    if (PageCgroupCache(pc))
        __mem_cgroup_stat_add_safe(cpustat,
            MEM_CGROUP_STAT_CACHE, numpages);
    else
        __mem_cgroup_stat_add_safe(cpustat, MEM_CGROUP_STAT_RSS,
            numpages);

static void __mem_cgroup_commit_charge(struct mem_cgroup *mem,
                       struct page_cgroup *pc,
                       enum charge_type ctype,
                       int page_size)
{

    switch (ctype) {
    case MEM_CGROUP_CHARGE_TYPE_CACHE:
    case MEM_CGROUP_CHARGE_TYPE_SHMEM: //file cache + shm
        SetPageCgroupCache(pc);
        SetPageCgroupUsed(pc);
        break;
    case MEM_CGROUP_CHARGE_TYPE_MAPPED:
        ClearPageCgroupCache(pc);
        SetPageCgroupUsed(pc);
        break;
    default:
        break;
    }
    //  更新统计值
    mem_cgroup_charge_statistics(mem, pc, page_size);


int mem_cgroup_cache_charge(struct page *page, struct mm_struct *mm,
                gfp_t gfp_mask)
{
...
    if (page_is_file_cache(page))
        return mem_cgroup_charge_common(page, mm, gfp_mask,
                MEM_CGROUP_CHARGE_TYPE_CACHE, NULL); ///file cache

    /* shmem */
    if (PageSwapCache(page)) {
        ret = mem_cgroup_try_charge_swapin(mm, page, gfp_mask, &mem);
        if (!ret)
            __mem_cgroup_commit_charge_swapin(page, mem,
                    MEM_CGROUP_CHARGE_TYPE_SHMEM);
    } else
        ret = mem_cgroup_charge_common(page, mm, gfp_mask, ///shm memory
                    MEM_CGROUP_CHARGE_TYPE_SHMEM, mem);

可以看到,cache包含共享内存和file cache


mapped_file
mapped_file - # of bytes of mapped file (includes tmpfs/shmem)

void mem_cgroup_update_file_mapped(struct page *page, int val)
{
...    __mem_cgroup_stat_add_safe(cpustat, MEM_CGROUP_STAT_FILE_MAPPED, val);
__do_fault –> page_add_file_rmap –> mem_cgroup_update_file_mapped。
inactive_anon
inactive_anon    - # of bytes of anonymous and swap cache memory on inactive LRU list.
static int shmem_getpage_gfp(struct inode *inode, pgoff_t index,
    struct page **pagep, enum sgp_type sgp, gfp_t gfp, int *fault_type)
{
...
        lru_cache_add_anon(page);

/**
 * lru_cache_add: add a page to the page lists
 * @page: the page to add
 */
static inline void lru_cache_add_anon(struct page *page)
{
    __lru_cache_add(page, LRU_INACTIVE_ANON);
}

从这里可以看到,共享内存会增加到INACTIVE_ANON。


inactive_file
inactive_file - # of bytes of file-backed memory on inactive LRU list.文件使用的page cache(不包含共享内存)

static inline void lru_cache_add_file(struct page *page)
{
    __lru_cache_add(page, LRU_INACTIVE_FILE);
}
add_to_page_cache_lru –> lru_cache_add_file.

示例程序

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUF_SIZE 4000000000   
#define MYKEY 26 

int main(int argc,char **argv){
    int shmid;
    char *shmptr;

    if((shmid = shmget(MYKEY,BUF_SIZE,IPC_CREAT)) ==-1){
        fprintf(stderr,"Create Share Memory Error0m~Z%s\n\a",strerror(errno));
        exit(1);
    }

    if((shmptr =shmat(shmid,0,0))==(void *)-1){
        printf("shmat error!\n");
        exit(1);
    }

    memset(shmptr,'\0',1000000000);

    printf("sleep...\n");
    while(1)
        sleep(1);

    exit(0);
}

执行程序前后,cgroup memory.stat的值:
执行前:*

# cat memory.stat 
cache 1121185792
rss 23678976
rss_huge 0
mapped_file 14118912
inactive_anon 1002643456
active_anon 23687168
inactive_file 46252032
active_file 72282112

执行后:*

# cat memory.stat 
cache 2121187328
rss 23760896
rss_huge 0
mapped_file 1014124544
inactive_anon 2002608128
active_anon 23736320
inactive_file 46247936
active_file 72286208

#ipcs -m
0x0000001a 229380     root       0          4000000000 1

可以看到cgroup中,共享内存计算在cache、mapped_file、inactive_anon中。

小结

(1)进程rss与cgroup rss的区别
进程的RSS为进程使用的所有物理内存(file_rss+anon_rss),即Anonymous pages+Mapped apges(包含共享内存)。
cgroup RSS为(anonymous and swap cache memory),不包含共享内存。
两者都不包含file cache。
(2)cgroup cache包含file cache和共享内存。

参考

http://man7.org/linux/man-pages/man5/proc.5.html
https://www.kernel.org/doc/Documentation/cgroups/memory.txt

目录
相关文章
|
6月前
|
存储 缓存 NoSQL
工作 10 年!Redis 内存淘汰策略 LRU 和传统 LRU 差异,还傻傻分不清
小富带你深入解析Redis内存淘汰机制:LRU与LFU算法原理、实现方式及核心区别。揭秘Redis为何采用“近似LRU”,LFU如何解决频率老化问题,并结合实际场景教你如何选择合适策略,提升缓存命中率。
720 3
|
6月前
|
缓存 监控 Linux
Linux内存问题排查命令详解
Linux服务器卡顿?可能是内存问题。掌握free、vmstat、sar三大命令,快速排查内存使用情况。free查看实时内存,vmstat诊断系统整体性能瓶颈,sar实现长期监控,三者结合,高效定位并解决内存问题。
523 0
Linux内存问题排查命令详解
|
10月前
|
缓存 Linux 数据安全/隐私保护
Linux环境下如何通过手动调用drop_caches命令释放内存
总的来说,记录住“drop_caches” 命令并理解其含义,可以让你在日常使用Linux的过程中更加娴熟和自如。
1546 23
|
12月前
|
监控 Linux Python
Linux系统资源管理:多角度查看内存使用情况。
要知道,透过内存管理的窗口,我们可以洞察到Linux系统运行的真实身姿,如同解剖学家透过微观镜,洞察生命的奥秘。记住,不要惧怕那些高深的命令和参数,他们只是你掌握系统"魔法棒"的钥匙,熟练掌握后,你就可以骄傲地说:Linux,我来了!
399 27
|
缓存 NoSQL Linux
Linux系统内存使用优化技巧
交换空间(Swap)的优化 禁用 Swap sudo swapoff -a 作用:这个命令会禁用系统中所有的 Swap 空间。swapoff 命令用于关闭 Swap 空间,-a 参数表示关闭 /etc/fstab 文件中配置的所有 Swap 空间。 使用场景:在高性能应用场景下,比如数据库服务器或高性能计算服务器,禁用 Swap 可以减少磁盘 I/O,提高系统性能。
494 3
|
缓存 Linux
Linux查看内存命令
1. free free命令是最常用的查看内存使用情况的命令。它显示系统的总内存、已使用内存、空闲内存和交换内存的总量。 free -h • -h 选项:以易读的格式(如GB、MB)显示内存大小。 输出示例: total used free shared buff/cache available Mem: 15Gi 4.7Gi 4.1Gi 288Mi 6.6Gi 9.9Gi Swap: 2.0Gi 0B 2.0Gi • to
2139 2
|
6月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
685 1
二、Linux文本处理与文件操作核心命令
|
6月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
419 137
|
6月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1023 58
|
5月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
954 2