Linux內存管理 一個進程究竟占用多少空間?-VSS/RSS/PSS/USS


關鍵詞: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 

 

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


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com