从MAP_SHARED谈RSS与PSS

  近期遇到一个问题:统计一个进程使用的物理内存居然远远超过了系统总内存大小。咋一看这个现象被下了一跳,怎么会这样呢?后来慢慢分析,终于了解到其中的奥秘.....

  我的 "进程占用物理内存统计" 是通过累加 /proc/pid/smaps中的Rss项来计算的。以往对Rss"Resident Set Size"的理解就是实际占用的内存,但是并没有真正去区分和Pss的区别。

  随后我仔细观察,才发现这次出问题的这个进程的Pss和Rss是不一样的:Pss累计统计出来的内存量是比较合理的。这是怎么回事呢?

  网上又查找了一番Rss和Pss的区别,都谈到了Rss是包含了共享库而Pss则比例分配共享库占用的内存。带着这些信息线索我又分析了两个方面: 1) 出问题的进程是如何消耗/分配内存的 ; 2) 内核中究竟是如何统计Rss和Pss的。

  方向1

  代码中主要内存消耗的代码为:

mmap(NULL, LENTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

即通过mmap()函数对一个文件进行LENTH大小的多次映射,并最终写这段映射出来的地址来消耗内存。

通过实验发现,如果mmap()函数不使用MAP_SHARED标志作为参数,则Rss统计值和Pss值一致,同时其Rss的统计值不会超过系统内存总量。

方向2

内核统计smaps中Rss和Pss的主要代码逻辑:
 

for 遍历进程的vma

  通过vma中各个的页的虚拟地址分解为对应的页表项

  smaps_pte_entry()-->

    smaps_account(mss, page, PAGE_SIZE, pte_young(*pte), pte_dirty(*pte))

smaps_pte_entry()函数首先找到pte对应物理页框的page结构,然后调用smaps_account()函数将此page统计到Rss和Pss中。具体一个page的统计方式如下:

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;

        mss->resident += size;        /* 将page size 统计到Rss */
        /* Accumulate the size in pages that have been accessed. */
        if (young || page_is_young(page) || PageReferenced(page))
                mss->referenced += size;
        mapcount = page_mapcount(page);
        if (mapcount >= 2) {
                u64 pss_delta;

                if (dirty || PageDirty(page))
                        mss->shared_dirty += size;
                else
                        mss->shared_clean += size;
                pss_delta = (u64)size << PSS_SHIFT;
                do_div(pss_delta, mapcount); /* 如果一个物理页映射多次则Pss均分 */
                mss->pss += pss_delta;
        } else {
                if (dirty || PageDirty(page))
                        mss->private_dirty += size;
                else
                        mss->private_clean += size;
                mss->pss += (u64)size << PSS_SHIFT;
        }
}

从上面代码可以看出:(1)只要进程虚拟地址映射到物理页就会统计到Rss中,不论这些物理页是否被多次映射; (2) Pss在统计进程虚拟地址空间每个映射的物理页时会div物理页映射的counts,即会均分多次映射的page。

而出问题的进程正好是以MAP_SHARED的方式多次映射同一个文件,这样多次映射的虚拟地址实际上只会对应到相同的物理页,因而也就有上述问题。

所以,以后再计算进程占用物理内存时,Rss和Pss如果不一样就需要考虑是否是有多次映射或者和共享映射的情况。

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值