天天看點

VSS/USS/PSS/RSS的計算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。

繼續閱讀