关键词: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>=&&*num<=)
{
*num=*num - +'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 = ;
itoa1(&num);
sleep();
}

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 -; error = pm_map_pagemap(map, &pagemap, &len);-----------------------------------len是一个vma区域的页面数量。
if (error) return error; pm_memusage_zero(&usage); for (i = ; 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 >= ) ? map->proc->ker->pagesize : ();------------只要有人使用,增加pagesize。
usage.pss += (count >= ) ? (map->proc->ker->pagesize / count) : ();--如果多人使用,取1/count的pagesize;如果单人使用,取整个pagesize。
usage.uss += (count == ) ? (map->proc->ker->pagesize) : ();----------如果只有一个人使用那么,增加pagesize到uss。
} else {
usage.swap += map->proc->ker->pagesize;
}
} memcpy(usage_out, &usage, sizeof(usage)); error = ; 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, );
} 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, , 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) >> ,--------------------本vma占用的虚拟地址空间
mss.resident >> ,-------------------------------------实际在内存中占用的空间
(unsigned long)(mss.pss >> ( + PSS_SHIFT)),-----------实际上包含下面private_clean+private_dirty,和按比例均分的shared_clean、shared_dirty。
mss.shared_clean >> ,--------------------------------共享的干净页面
mss.shared_dirty >> ,--------------------------------共享的脏页
mss.private_clean >> ,--------------------------------独占的干净页面
mss.private_dirty >> ,--------------------------------独占的脏页
mss.referenced >> ,-----------------------------------
mss.anonymous >> ,------------------------------------匿名页面大小
mss.anonymous_thp >> ,
mss.shared_hugetlb >> ,
mss.private_hugetlb >> ,
mss.swap >> ,
(unsigned long)(mss.swap_pss >> ( + PSS_SHIFT)),
vma_kernel_pagesize(vma) >> ,
vma_mmu_pagesize(vma) >> ,
(vma->vm_flags & VM_LOCKED) ?
(unsigned long)(mss.pss >> ( + PSS_SHIFT)) : ); show_smap_vma_flags(m, vma);
m_cache_vma(m, vma);
return ;
}

下面来看看是如何更新一个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) == ) {
smaps_pmd_entry(pmd, addr, walk);
spin_unlock(ptl);
return ;
} if (pmd_trans_unstable(pmd))
return ;
/*
* 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 - , ptl);
cond_resched();
return ;
} 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 >= ) {
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 >= ) {-------------------------------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
... 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
...
6444K 1172K 103K 88K ./sleep
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。

- r-xp  :                             /home/al/sharedlib/sleep
Size: kB
KernelPageSize: kB
MMUPageSize: kB
Rss: kB
Pss: kB---------------------------------------------------2 KB,因为要和sleep-23736均分4/2=2KB。
Shared_Clean: kB---------------------------------------------------4 KB,本来独占内存变成共享内存,两个共享者。
Shared_Dirty: kB
Private_Clean: kB---------------------------------------------------0 KB
Private_Dirty: kB
Referenced: kB
Anonymous: kB
LazyFree: kB
AnonHugePages: kB
ShmemPmdMapped: kB
Shared_Hugetlb: kB
Private_Hugetlb: kB
Swap: kB
SwapPss: kB
Locked: kB---------------------------------------------------2 KB
VmFlags: rd ex mr mw me dw sd
...
7ffba85b2000-7ffba8799000 r-xp : /lib/x86_64-linux-gnu/libc-2.27.so
Size: kB
KernelPageSize: kB
MMUPageSize: kB
Rss: kB
Pss: kB---------------------------------------------------8 KB,使用此库者太多,无法统计。
Shared_Clean: kB
Shared_Dirty: kB
Private_Clean: kB
Private_Dirty: kB
Referenced: kB
Anonymous: kB
LazyFree: kB
AnonHugePages: kB
ShmemPmdMapped: kB
Shared_Hugetlb: kB
Private_Hugetlb: kB
Swap: kB
SwapPss: kB
Locked: kB---------------------------------------------------8 KB
VmFlags: rd ex mr mw me sd
...
7ffba89a3000-7ffba89a4000 r-xp : /lib/x86_64-linux-gnu/libtest.so
Size: kB
KernelPageSize: kB
MMUPageSize: kB
Rss: kB
Pss: kB---------------------------------------------------2 KB,因为原来独占4KB,变成均分后2KB。
Shared_Clean: kB---------------------------------------------------4 KB
Shared_Dirty: kB
Private_Clean: kB---------------------------------------------------0 KB
Private_Dirty: kB
Referenced: kB
Anonymous: kB
LazyFree: kB
AnonHugePages: kB
ShmemPmdMapped: kB
Shared_Hugetlb: kB
Private_Hugetlb: kB
Swap: kB
SwapPss: kB
Locked: 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

Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS的更多相关文章

  1. 【Android手机测试】linux内存管理 -- 一个进程占多少内存?四种计算方法:VSS/RSS/PSS/USS

    在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/USS四种形式,这四种形式首字母分别是Virtual/Resident/Proportional/Unique的意思. ...

  2. 内存VSS/RSS/PSS/USS名词解释

    VSS(virtual set size)虚拟耗用内存(包含共享库占用的内存) RSS(Resident set size)实际使用物理内存(包含共享库占用的内存) RSS是进程实际驻存在物理内存的部 ...

  3. Android内存之VSS/RSS/PSS/USS

    Terms VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存) PSS - P ...

  4. 内存耗用:VSS/RSS/PSS/USS

    Terms VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存) PSS - P ...

  5. 【转】内存耗用:VSS/RSS/PSS/USS

    Terms VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存) PSS- Prop ...

  6. android内存耗用:VSS/RSS/PSS/USS

    VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)  不是真实当前应用进程所占用的内存. 内存分配的原理 从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完 ...

  7. Linux内存管理 (1)物理内存初始化

    专题:Linux内存管理专题 关键词:用户内核空间划分.Node/Zone/Page.memblock.PGD/PUD/PMD/PTE.lowmem/highmem.ZONE_DMA/ZONE_NOR ...

  8. linux内存管理-内核用户空间 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-4491362.html 1,linux内存管理中几个重要的结构体和数组 page unsigned long ...

  9. Linux内存管理 (23)一个内存Oops解析

    专题:Linux内存管理专题 关键词:DataAbort.fsr.pte.backtrace.stack.   在内存相关实际应用中,内存异常访问是一种常见的问题. 本文结合异常T32栈回溯.Oops ...

随机推荐

  1. Kotlin入门(22)适配器的简单优化

    列表视图 为实现各种排列组合类的视图(包括但不限于Spinner.ListView.GridView等等),Android提供了五花八门的适配器用于组装某个规格的数据,常见的适配器有:数组适配器Arr ...

  2. button改变某div内文字内容的显示

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. git多个远程仓库

    1. 前言   用GitHub管理自己的开源项目有几年了,最近一年更新得比较多,仓库也越来越多越来越大.有时候感觉GitHub太慢,尤其是最近感觉更为明显,于是萌生了再找个国内类似GitHub的代码托 ...

  4. Ext 日期格式化

    //日期格式化 Date.prototype.Format = function (fmt) { var o = { , //月份 "d+": this.getDate(), // ...

  5. Android 闪烁动画

    import android.view.View; import android.view.animation.AlphaAnimation; import android.view.animatio ...

  6. Python爬虫之Urllib库的基本使用

    # get请求 import urllib.request response = urllib.request.urlopen("http://www.baidu.com") pr ...

  7. Python: 内置私有方法

    ################## __new__ ##################@staticmethod__new__(cls [, ...])类的构造器,创建某个类的实例,返回值应该是c ...

  8. February 11th, 2018 Week 7th Sunday

    Grasp all, lose all. 欲尽得,必尽失. Not to be greedy and not to try to get everything. Our time, energy an ...

  9. S/4 HANA中的MATDOC和MATDOC_EXTRACT

    最近做了销售和物料管理方面的一些需求,因此对S/4中的MM的数据模型有了一定的了解.目前网络已经有一些介绍物料凭证表MATDOC的文章,内容好像不是很详细,另外也没发现介绍库存变更数据表MATDOC_ ...

  10. python3编写网络爬虫16-使用selenium 爬取淘宝商品信息

    一.使用selenium 模拟浏览器操作爬取淘宝商品信息 之前我们已经成功尝试分析Ajax来抓取相关数据,但是并不是所有页面都可以通过分析Ajax来完成抓取.比如,淘宝,它的整个页面数据确实也是通过A ...