VSS/USS/PSS/RSS的计算

VSS/USS/PSS/RSS的计算

VSS/USS/PSS/RSS是什么

VSS、USS、PSS、RSS是衡量内存占用的四个指标:

  • VSS:Virtual Set Size, 虚拟内存占用,包括共享库等。
  • RSS:Resident Set Size,实际物理内存占用,包括共享库等。
  • PSS:Proportion Set Size,实际使用的物理内存,共享库等按比例分配。
  • USS:Unique Set Size,进程独占的物理内存,不计算共享库等的内存占用。

一般我们有VSS >= RSS >= PSS >= USS。

pagemap

以下内容主要摘录自内核文档(Documentation/vm/pagemap.txt)。
pagemap是内核自2.5.25引入的一组接口,使得用户空间的程序可以通过读取/proc文件来获取页表等相关信息。
pagemap由4个部分组成。

/proc/pid/pagemap

这个文件使得用户进程可以获得每个虚拟内存页和实际内存的映射关系。对于每一个虚拟页
* Bits 0-54 page frame number (PFN) if present
* Bits 0-4 swap type if swapped
* Bits 5-54 swap offset if swapped
* Bit 55 pte is soft-dirty (see Documentation/vm/soft-dirty.txt)
* Bit 56 page exclusively mapped (since 4.2)
* Bits 57-60 zero
* Bit 61 page is file-page or shared-anon (since 3.5)
* Bit 62 page swapped
* Bit 63 page present

如果一个页不在内存中,而被交换到swap空间,那么PFN中将包含交换文件好和偏移的编码。未映射的页将返回NULL。

使用/proc/pid/maps可以确定哪些页是真正映射的,然后通过llseek来跳过未映射的页。

/proc/kpagecount

该文件包含了每一个页面映射次数,使用64位表示,通过PFN索引。

/proc/kpageflags

该文件记录了每一个页面的flags,使用64位表示,通过PFN索引。
这些标记是(fs/proc/page.c):

 0. LOCKED
 1. ERROR
 2. REFERENCED
 3. UPTODATE
 4. DIRTY
 5. LRU
 6. ACTIVE
 7. SLAB
 8. WRITEBACK
 9. RECLAIM
10. BUDDY
11. MMAP
12. ANON
13. SWAPCACHE
14. SWAPBACKED
15. COMPOUND_HEAD
16. COMPOUND_TAIL
17. HUGE
18. UNEVICTABLE
19. HWPOISON
20. NOPAGE
21. KSM
22. THP
23. BALLOON
24. ZERO_PAGE
25. IDLE

/proc/kpagegroup

只有在配置CONFIG_MEMCG时,才包含该文件。该文件包含一个通过PFN索引的64位值,表明该页所归属的memory cgroup。

pagemap接口的使用

通过pagemap计算进程的内存使用一般流程如下:
1、通过读取/proc/pid/maps来确定哪一部分内存空间映射到哪里。
2、选取你感兴趣的内存部分-所有?特定的库?还是堆、栈等等。
3、打开/proc/pid/pagemap,跳转到你需要检查的部分。
4、读取每一个page的u64值。
5、通过page的u64位值中的PFN,在/proc/kpagecount和/proc/kpageflags中获取你需要的信息。

计算过程

通过前面VSS、RSS、PSS、USS的定义可以看出:
1、VSS,计算所有的页,无论该页是否被映射到物理内存。
2、RSS,只计算映射到物理内存或swap的页。
3、PSS,在RSS基础上,若一个页被映射n次,只计算n分之一。
4、USS,在RSS基础上,只计算被映射一次的页。

下面计算VSS、RSS、PSS、USS的源码摘录自Android两个工具procmem、procrank以及libpagemap库。

1、打开/proc/pid/maps获取进程的多个映射区间。

2、对于某一段映射,从/proc/pid/pagemap中的获取映页的映射数组。

int pm_process_pagemap_range(pm_process_t *proc,
                             unsigned long low, unsigned long high,
                             uint64_t **range_out, size_t *len) {
    int firstpage, numpages;
    uint64_t *range;
    off_t off;
    int error;
    /*参数检查*/
    if (!proc || (low >= high) || !range_out || !len)
        return -1;

    /*根据low获取第一页序号以及页数*/
    firstpage = low / proc->ker->pagesize;
    numpages = (high - low) / proc->ker->pagesize;

    range = malloc(numpages * sizeof(uint64_t));
    if (!range)
        return errno;
    /*在/proc/pid/pagemap中偏移firstpage*sizeof(u64_t)*/
    off = lseek(proc->pagemap_fd, firstpage * sizeof(uint64_t), SEEK_SET);
    if (off == (off_t)-1) {
        error = errno;
        free(range);
        return error;
    }
    /*读取numpages*sizeof(u64_t)到分配的内存中,并返回*/
    error = read(proc->pagemap_fd, (char*)range, numpages * sizeof(uint64_t));
    if (error == 0) {
        /* EOF, mapping is not in userspace mapping range (probably vectors) */
        *len = 0;
        free(range);
        *range_out = NULL;
        return 0;
    } else if (error < 0 || (error > 0 && error < (int)(numpages * sizeof(uint64_t)))) {
        error = (error < 0) ? errno : -1;
        free(range);
        return error;
    }

    *range_out = range;
    *len = numpages;

    return 0;
}

3、根据前面得到的pagemap数组,从/proc/kpagecount中获取page的map次数,计算uss、rss、vss、pss。

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);
    if (error) return error;

    pm_memusage_zero(&usage);

    for (i = 0; i < len; i++) {
        /*VSS无论该页是否映射,都需要计算*/
        usage.vss += map->proc->ker->pagesize;
       
        /*如果该页未map或者swap,则无需计算*/
        if (!PM_PAGEMAP_PRESENT(pagemap[i]))
            continue;

        if (!PM_PAGEMAP_SWAPPED(pagemap[i])) {
            /*非swap页*/
            if (flags_mask) {
                uint64_t flags;
                error = pm_kernel_flags(map->proc->ker, PM_PAGEMAP_PFN(pagemap[i]),
                                        &flags);
                if (error) goto out;

                if ((flags & flags_mask) != required_flags)
                    continue;
            }

            /*根据PFN,从/proc/kpagecount中获取页的映射次数*/
            error = pm_kernel_count(map->proc->ker, PM_PAGEMAP_PFN(pagemap[i]),
                                    &count);
            if (error) goto out;
            /*rss 计算所有映射到物理内存的页*/
            usage.rss += (count >= 1) ? map->proc->ker->pagesize : (0);
            /*pss 对于多次映射的页,按比例分配*/
            usage.pss += (count >= 1) ? (map->proc->ker->pagesize / count) : (0);
            /*uss 只计算映射一次的页*/
            usage.uss += (count == 1) ? (map->proc->ker->pagesize) : (0);
        } else {
            /*swap页面*/
            usage.swap += map->proc->ker->pagesize;
        }
    }

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

    error = 0;

out:    
    free(pagemap);

    return error;
}

从/proc/kpagecount中读取映射次数的代码页比较简单,lseek到偏移位置,然后直接读取u64即可。

int pm_kernel_count(pm_kernel_t *ker, unsigned long pfn, uint64_t *count_out) {
    off_t off;

    if (!ker || !count_out)
        return -1;

    off = lseek(ker->kpagecount_fd, pfn * sizeof(uint64_t), SEEK_SET);
    if (off == (off_t)-1)
        return errno;
    if (read(ker->kpagecount_fd, count_out, sizeof(uint64_t)) <
        (ssize_t)sizeof(uint64_t))
        return errno;

    return 0;
}

4、重复步骤2和3,将所有映射区间的结果相加,即得到进程的VSS、RSS、PSS、USS。

猜你喜欢

转载自blog.csdn.net/sunao2002002/article/details/84132390
RSS