前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS

Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS

作者头像
sunsky
发布2020-08-20 17:52:02
5.2K0
发布2020-08-20 17:52:02
举报
文章被收录于专栏:sunskysunskysunsky

关键词:VSS、RSS、PSS、USS、_mapcount、pte_present、mem_size_stats。

在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/USS四种形式,这四种形式首字母分别是Virtual/Resident/Proportional/Unique的意思。

VSS是单个进程全部可访问的地址空间,其大小可能包括还尚未在内存中驻留的部分。对于确定单个进程实际内存使用大小,VSS用处不大。

RSS是单个进程实际占用的内存大小,RSS不太准确的地方在于它包括该进程所使用共享库全部内存大小。对于一个共享库,可能被多个进程使用,实际该共享库只会被装入内存一次。

进而引出了PSS,PSS相对于RSS计算共享库内存大小是按比例的。N个进程共享,该库对PSS大小的贡献只有1/N。

USS是单个进程私有的内存大小,即该进程独占的内存部分。USS揭示了运行一个特定进程在的真实内存增量大小。如果进程终止,USS就是实际被返还给系统的内存大小。

综上所属,VSS>=RSS>=PSS>=USS。

给你画张图来给你看:

1. 创建一个共享库

创建一个test.c文件和test.h文件。

#include "test.h"
 
void itoa1(int *num)
{
    if(*num>=65&&*num<=88)
    {
        *num=*num - 65+'a';
    }
}

编译libtest.so库文件,将libtest.so拷贝到/lib/x86_64-linux-gnu/。这样程序在运行时就可以找到此库文件。

gcc test.c -fPIC -shared -o libtest.so

头文件放在sleep.c同一个目录。

#ifndef __TEST_H_
#define __TEST_H_
 
extern void itoa1(int *); 
 
#endif

编译sleep.c连接到libtest.so库“gcc sleep.c -ltest -o sleep”。

#include<stdio.h>
#include<unistd.h>
#include"test.h"

void main()
{
    int num = 66;
    itoa1(&num);
    sleep(1000);
}

2. procrank

procrank是Android下的工具,通过工具可以看到进程内存的不同形式占用。

procrank_linux.git下载代码,然后make编译。

sudo procrank查看各进成的VSS/RSS/PSS/USS占用情况。

procrank通过解析/proc/kpagecount来计算每个进程占用的内存。通过如下的代码可以看出VSS/RSS/PSS/USS都是怎么来的。

这也就不难明白vss>=rss>=pss>=uss。

int pm_map_usage_flags(pm_map_t *map, pm_memusage_t *usage_out,
                        uint64_t flags_mask, uint64_t required_flags) {
    uint64_t *pagemap;
    size_t len, i;
    uint64_t count;
    pm_memusage_t usage;
    int error;

    if (!map || !usage_out)
        return -1;

    error = pm_map_pagemap(map, &pagemap, &len);-----------------------------------len是一个vma区域的页面数量。
    if (error) return error;

    pm_memusage_zero(&usage);

    for (i = 0; i < len; i++) {
        usage.vss += map->proc->ker->pagesize;--------------------------------------vss会一直累加len个pagesize。

        if (!PM_PAGEMAP_PRESENT(pagemap[i]))----------------------------------------判断对应的物理页面是否存在。
            continue;

        if (!PM_PAGEMAP_SWAPPED(pagemap[i])) {
...
            error = pm_kernel_count(map->proc->ker, PM_PAGEMAP_PFN(pagemap[i]),
                                    &count);---------------------------------------count是对应物理页面的使用者。
            if (error) goto out;

            usage.rss += (count >= 1) ? map->proc->ker->pagesize : (0);------------只要有人使用,增加pagesize。
            usage.pss += (count >= 1) ? (map->proc->ker->pagesize / count) : (0);--如果多人使用,取1/count的pagesize;如果单人使用,取整个pagesize。
            usage.uss += (count == 1) ? (map->proc->ker->pagesize) : (0);----------如果只有一个人使用那么,增加pagesize到uss。
        } else {
            usage.swap += map->proc->ker->pagesize;
        }
    }

    memcpy(usage_out, &usage, sizeof(usage));

    error = 0;

out:    
    free(pagemap);

    return error;
}

3. /proc/xxx/smaps解析

smem分析系统内存使用是通过smaps的,procrank是通过分析/proc/kpagemap。

smaps的一个核心数据结构是,

struct mem_size_stats {
    unsigned long resident;----------RSS,有对应的物理页面。
    unsigned long shared_clean;------多个进程共享,是干净页面
    unsigned long shared_dirty;------多个进程共享,是脏页
    unsigned long private_clean;-----进程独占,是干净页面
    unsigned long private_dirty;-----进程独占,是脏页
    unsigned long referenced;
    unsigned long anonymous;---------匿名页面
    unsigned long anonymous_thp;
    unsigned long swap;--------------换出页面
    unsigned long shared_hugetlb;
    unsigned long private_hugetlb;
    u64 pss;-------------------------PSS部分,但是左移了PSS_SHIFT。
    u64 swap_pss;
};

核心函数是show_smap(),他处理一个vma的内容,整个进程可能需要调用多次show_smap()。

/*
 * Tasks
 */
static const struct pid_entry tid_base_stuff[] = {
...
    REG("smaps",     S_IRUGO, proc_tid_smaps_operations),
...
};

const struct file_operations proc_tid_smaps_operations = {
    .open        = tid_smaps_open,
    .read        = seq_read,
    .llseek        = seq_lseek,
    .release    = proc_map_release,
};

static int tid_smaps_open(struct inode *inode, struct file *file)
{
    return do_maps_open(inode, file, &proc_tid_smaps_op);
}

static const struct seq_operations proc_tid_smaps_op = {
    .start    = m_start,
    .next    = m_next,
    .stop    = m_stop,
    .show    = show_tid_smap
};

static int show_tid_smap(struct seq_file *m, void *v)
{
    return show_smap(m, v, 0);
}

static int show_smap(struct seq_file *m, void *v, int is_pid)
{
    struct vm_area_struct *vma = v;
    struct mem_size_stats mss;
    struct mm_walk smaps_walk = {
        .pmd_entry = smaps_pte_range,-------------------------------核心函数,用于便利整个vma区域更新mem_size_stats,也即下面的mss。
#ifdef CONFIG_HUGETLB_PAGE
        .hugetlb_entry = smaps_hugetlb_range,
#endif
        .mm = vma->vm_mm,
        .private = &mss,
    };

    memset(&mss, 0, sizeof mss);
    /* mmap_sem is held in m_start */
    walk_page_vma(vma, &smaps_walk);

    show_map_vma(m, vma, is_pid);

    seq_printf(m,
           "Size:           %8lu kB\n"
           "Rss:            %8lu kB\n"
           "Pss:            %8lu kB\n"
           "Shared_Clean:   %8lu kB\n"
           "Shared_Dirty:   %8lu kB\n"
           "Private_Clean:  %8lu kB\n"
           "Private_Dirty:  %8lu kB\n"
           "Referenced:     %8lu kB\n"
           "Anonymous:      %8lu kB\n"
           "AnonHugePages:  %8lu kB\n"
           "Shared_Hugetlb: %8lu kB\n"
           "Private_Hugetlb: %7lu kB\n"
           "Swap:           %8lu kB\n"
           "SwapPss:        %8lu kB\n"
           "KernelPageSize: %8lu kB\n"
           "MMUPageSize:    %8lu kB\n"
           "Locked:         %8lu kB\n",
           (vma->vm_end - vma->vm_start) >> 10,--------------------本vma占用的虚拟地址空间
           mss.resident >> 10,-------------------------------------实际在内存中占用的空间
           (unsigned long)(mss.pss >> (10 + PSS_SHIFT)),-----------实际上包含下面private_clean+private_dirty,和按比例均分的shared_clean、shared_dirty。
           mss.shared_clean  >> 10,--------------------------------共享的干净页面
           mss.shared_dirty  >> 10,--------------------------------共享的脏页
           mss.private_clean >> 10,--------------------------------独占的干净页面
           mss.private_dirty >> 10,--------------------------------独占的脏页
           mss.referenced >> 10,-----------------------------------
           mss.anonymous >> 10,------------------------------------匿名页面大小
           mss.anonymous_thp >> 10,
           mss.shared_hugetlb >> 10,
           mss.private_hugetlb >> 10,
           mss.swap >> 10,
           (unsigned long)(mss.swap_pss >> (10 + PSS_SHIFT)),
           vma_kernel_pagesize(vma) >> 10,
           vma_mmu_pagesize(vma) >> 10,
           (vma->vm_flags & VM_LOCKED) ?
            (unsigned long)(mss.pss >> (10 + PSS_SHIFT)) : 0);

    show_smap_vma_flags(m, vma);
    m_cache_vma(m, vma);
    return 0;
}

下面来看看是如何更新一个vma区域的vss/rss/pss/uss的。

其中smaps_account()和procrank的pm_map_usage_flags()有着相近的逻辑。

对PSS和USS最重要的区分参数是page->_mapcount。

static int smaps_pte_range(pmd_t *pmd, unsigned long addr, unsigned long end,
               struct mm_walk *walk)
{
    struct vm_area_struct *vma = walk->vma;
    pte_t *pte;
    spinlock_t *ptl;

    if (pmd_trans_huge_lock(pmd, vma, &ptl) == 1) {
        smaps_pmd_entry(pmd, addr, walk);
        spin_unlock(ptl);
        return 0;
    }

    if (pmd_trans_unstable(pmd))
        return 0;
    /*
     * The mmap_sem held all the way back in m_start() is what
     * keeps khugepaged out of here and from collapsing things
     * in here.
     */
    pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
    for (; addr != end; pte++, addr += PAGE_SIZE)
        smaps_pte_entry(pte, addr, walk);
    pte_unmap_unlock(pte - 1, ptl);
    cond_resched();
    return 0;
}


static void smaps_pte_entry(pte_t *pte, unsigned long addr,
        struct mm_walk *walk)
{
    struct mem_size_stats *mss = walk->private;
    struct vm_area_struct *vma = walk->vma;
    struct page *page = NULL;

    if (pte_present(*pte)) {----------------------------------页面在内存中
        page = vm_normal_page(vma, addr, *pte);
    } else if (is_swap_pte(*pte)) {---------------------------页面被swap出
        swp_entry_t swpent = pte_to_swp_entry(*pte);

        if (!non_swap_entry(swpent)) {
            int mapcount;

            mss->swap += PAGE_SIZE;
            mapcount = swp_swapcount(swpent);
            if (mapcount >= 2) {
                u64 pss_delta = (u64)PAGE_SIZE << PSS_SHIFT;

                do_div(pss_delta, mapcount);
                mss->swap_pss += pss_delta;
            } else {
                mss->swap_pss += (u64)PAGE_SIZE << PSS_SHIFT;
            }
        } else if (is_migration_entry(swpent))
            page = migration_entry_to_page(swpent);
    }

    if (!page)----------------------------------------------如果页面不存在,就不用更新mss其他信息了;如果存在,调用smaps_account()更新mss。
        return;
    smaps_account(mss, page, PAGE_SIZE, pte_young(*pte), pte_dirty(*pte));
}


static void smaps_account(struct mem_size_stats *mss, struct page *page,
        unsigned long size, bool young, bool dirty)
{
    int mapcount;

    if (PageAnon(page))
        mss->anonymous += size;------------------------匿名页面对anonymous做出贡献。

    mss->resident += size;
    /* Accumulate the size in pages that have been accessed. */
    if (young || page_is_young(page) || PageReferenced(page))
        mss->referenced += size;
    mapcount = page_mapcount(page);--------------------page->_mapcount
    if (mapcount >= 2) {-------------------------------mapcount大于1的情况,共享映射。对PSS做出1/mapcount贡献。
        u64 pss_delta;

        if (dirty || PageDirty(page))
            mss->shared_dirty += size;
        else
            mss->shared_clean += size;
        pss_delta = (u64)size << PSS_SHIFT;------------这里pss采用PSS_SHIFT是为了降低误差。
        do_div(pss_delta, mapcount);-------------------根据mapcount取部分值。
        mss->pss += pss_delta;
    } else {-------------------------------------------mapcount为1的情况,都是独占。对USS做出贡献。
        if (dirty || PageDirty(page))
            mss->private_dirty += size;
        else
            mss->private_clean += size;
        mss->pss += (u64)size << PSS_SHIFT;------------当count为1,对PSS的贡献是100%。
    }
}

可以看出:

USS = Private_Clean + Private_Dirty

PSS = USS + (Shared_Clean + Shared_Dirty)/n

RSS = Private_Clean + Private_Dirty + Shared_Clean + Shared_Dirty

4. 使用procrank和smaps验证

首先启动一个sleep,然后启动同一sleep的另一个实例,使用procrank记录其内存使用情况如下。

可以看出sleep-23693的VSS和RSS前后没有变化,但是PSS减少了5K,USS减少了8K。

  PID       Vss      Rss      Pss      Uss  cmdline
...23693     6444K    1200K      98K      88K  ./sleep
                           ------   ------  ------
                          2278152K  2055080K  TOTAL

RAM: 8054884K total, 603152K free, 112804K buffers, 5333808K cached, 615288K shmem, 358960K slab


  PID       Vss      Rss      Pss      Uss  cmdline
...
23736     6444K    1172K     103K      88K  ./sleep
23693     6444K    1200K      93K      80K  ./sleep
                           ------   ------  ------
                          2332373K  2108276K  TOTAL

RAM: 8054884K total, 572488K free, 113088K buffers, 5357752K cached, 613880K shmem, 358968K slab

由上面的分析可知,RSS = Private_Clean + Private_Dirty + Shared_Clean + Shared_Dirty,将sleep-23693的smaps累积也确实是1200KB。同样也可以求出USS的大小为88KB。但是PSS涉及到libc的引用计数一直在变化中,没有计算。

然后查看sleep-23693前后smaps的变化,可以看出Pss部分减少了共2(test)+1(libc)+2(libtest)=5KB,因为可执行文件sleep和libtest.so的大小要和sleep-23736均分。

Uss减少主要是sleep可执行文件和共享库libtest.so,本来都是sleep-23693独占,在执行sleep-23736之后,就不能算独占内存了。所以减去4+4=8。

00400000-00401000 r-xp 00000000 08:08 9452266                            /home/al/sharedlib/sleep
Size:                  4 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   4 kB
Pss:                   4 kB---------------------------------------------------2 KB,因为要和sleep-23736均分4/2=2KB。
Shared_Clean:          0 kB---------------------------------------------------4 KB,本来独占内存变成共享内存,两个共享者。
Shared_Dirty:          0 kB
Private_Clean:         4 kB---------------------------------------------------0 KB
Private_Dirty:         0 kB
Referenced:            4 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                4 kB---------------------------------------------------2 KB
VmFlags: rd ex mr mw me dw sd 
...
7ffba85b2000-7ffba8799000 r-xp 00000000 08:06 136724                     /lib/x86_64-linux-gnu/libc-2.27.so
Size:               1948 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                 952 kB
Pss:                   9 kB---------------------------------------------------8 KB,使用此库者太多,无法统计。
Shared_Clean:        952 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         0 kB
Referenced:          952 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                9 kB---------------------------------------------------8 KB
VmFlags: rd ex mr mw me sd 
...
7ffba89a3000-7ffba89a4000 r-xp 00000000 08:06 142918                     /lib/x86_64-linux-gnu/libtest.so
Size:                  4 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   4 kB
Pss:                   4 kB---------------------------------------------------2 KB,因为原来独占4KB,变成均分后2KB。
Shared_Clean:          0 kB---------------------------------------------------4 KB
Shared_Dirty:          0 kB
Private_Clean:         4 kB---------------------------------------------------0 KB
Private_Dirty:         0 kB
Referenced:            4 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                4 kB---------------------------------------------------2 KB
VmFlags: rd ex mr mw me sd 

top | grep app名称

ps | grep app名称

procrank | grep app名称

dumpsys meminfo app名称

前两个命令只能查到VSS RSS内存占用信息

而后面两个命令可以查出 PSS USS内存占用.

dumpsys meminfo 可以查出native和dalvik分别占用多少内存

linux 上可以用: smem

5. 小结

通过上面的分析,可以看出VSS只是一个虚拟空间大小,对内存实际占用量意义不大。

RSS是对于计算一个进程内存占用量,会有一点误解。因为像libc这种大部头库文件,共享者很多,都算在一个进程头上不科学。

这时候PSS就更加科学了,除了自己独占的内存,再加上分到的共享部分。

USS在计算一个新加入的进程导致系统内存增量很有用处,因为共享部分已经存在,并不是由其导致的。

参考文档:

如何通过Smem命令行检查Ubuntu上的内存使用情况

Memstat -- 查看Linux共享库的内存占用

Using procrank to measure memory usage on embedded Linux

https://unix.stackexchange.com/questions/116327/loading-of-shared-libraries-and-ram-usage

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-08-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 创建一个共享库
  • 2. procrank
  • 3. /proc/xxx/smaps解析
  • 4. 使用procrank和smaps验证
  • 5. 小结
相关产品与服务
轻量应用服务器
轻量应用服务器(TencentCloud Lighthouse)是新一代开箱即用、面向轻量应用场景的云服务器产品,助力中小企业和开发者便捷高效的在云端构建网站、Web应用、小程序/小游戏、游戏服、电商应用、云盘/图床和开发测试环境,相比普通云服务器更加简单易用且更贴近应用,以套餐形式整体售卖云资源并提供高带宽流量包,将热门开源软件打包实现一键构建应用,提供极简上云体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档