轉自:https://www.cnblogs.com/arnoldlu/p/9375377.html
在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在計算一個新加入的進程導致系統內存增量很有用處,因爲共享部分已經存在,並不是由其導致的。
參考文檔:
《Using procrank to measure memory usage on embedded Linux》
https://unix.stackexchange.com/questions/116327/loading-of-shared-libraries-and-ram-usage