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

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS PostgreSQL Serverless,0.5-4RCU 50GB 3个月
推荐场景:
对影评进行热评分析
云原生数据库 PolarDB 分布式版,标准版 2核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

目录
相关文章
|
1天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
16天前
|
存储 运维 监控
深入Linux基础:文件系统与进程管理详解
深入Linux基础:文件系统与进程管理详解
58 8
|
13天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
25天前
|
算法 Linux 定位技术
Linux内核中的进程调度算法解析####
【10月更文挑战第29天】 本文深入剖析了Linux操作系统的心脏——内核中至关重要的组成部分之一,即进程调度机制。不同于传统的摘要概述,我们将通过一段引人入胜的故事线来揭开进程调度算法的神秘面纱,展现其背后的精妙设计与复杂逻辑,让读者仿佛跟随一位虚拟的“进程侦探”,一步步探索Linux如何高效、公平地管理众多进程,确保系统资源的最优分配与利用。 ####
66 4
|
24天前
|
存储 缓存 Java
结构体和类在内存管理方面的差异对程序性能有何影响?
【10月更文挑战第30天】结构体和类在内存管理方面的差异对程序性能有着重要的影响。在实际编程中,需要根据具体的应用场景和性能要求,合理地选择使用结构体或类,以优化程序的性能和内存使用效率。
|
24天前
|
存储 缓存 算法
结构体和类在内存管理方面有哪些具体差异?
【10月更文挑战第30天】结构体和类在内存管理方面的差异决定了它们在不同的应用场景下各有优劣。在实际编程中,需要根据具体的需求和性能要求来合理选择使用结构体还是类。
|
4月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
394 0
|
2月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
64 1
|
2月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
2月前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。