Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS
关键词: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在计算一个新加入的进程导致系统内存增量很有用处,因为共享部分已经存在,并不是由其导致的。
参考文档:
《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的更多相关文章
- 【Android手机测试】linux内存管理 -- 一个进程占多少内存?四种计算方法:VSS/RSS/PSS/USS
在Linux里面,一个进程占用的内存有不同种说法,可以是VSS/RSS/PSS/USS四种形式,这四种形式首字母分别是Virtual/Resident/Proportional/Unique的意思. ...
- 内存VSS/RSS/PSS/USS名词解释
VSS(virtual set size)虚拟耗用内存(包含共享库占用的内存) RSS(Resident set size)实际使用物理内存(包含共享库占用的内存) RSS是进程实际驻存在物理内存的部 ...
- Android内存之VSS/RSS/PSS/USS
Terms VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存) PSS - P ...
- 内存耗用:VSS/RSS/PSS/USS
Terms VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存) PSS - P ...
- 【转】内存耗用:VSS/RSS/PSS/USS
Terms VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存) PSS- Prop ...
- android内存耗用:VSS/RSS/PSS/USS
VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存) 不是真实当前应用进程所占用的内存. 内存分配的原理 从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完 ...
- Linux内存管理 (1)物理内存初始化
专题:Linux内存管理专题 关键词:用户内核空间划分.Node/Zone/Page.memblock.PGD/PUD/PMD/PTE.lowmem/highmem.ZONE_DMA/ZONE_NOR ...
- linux内存管理-内核用户空间 【转】
转自:http://blog.chinaunix.net/uid-25909619-id-4491362.html 1,linux内存管理中几个重要的结构体和数组 page unsigned long ...
- Linux内存管理 (23)一个内存Oops解析
专题:Linux内存管理专题 关键词:DataAbort.fsr.pte.backtrace.stack. 在内存相关实际应用中,内存异常访问是一种常见的问题. 本文结合异常T32栈回溯.Oops ...
随机推荐
- Redis 保护模式
默认 redis 启用了保护模式,即如果是远程链接不能进行 CRUD 等操作,如果进行该操作报错如下 (error) DENIED Redis is running in protected mode ...
- 30分钟彻底弄懂flex布局
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由elson发表于云+社区专栏 目前在不考虑IE以及低端安卓机(4.3-)的兼容下,已经可以放心使用flex进行布局了.什么是flex布 ...
- Java虚拟机(五)Java的四种引用级别
1.前言 HotSpot采取了可达性分析算法用来判断对象是否被能被GC,无论是引用计算法还是可达性分析算法都是判断对象是否存在引用来判断对象是否存活.如果reference类型的数据中存储的数值代表的 ...
- DAY14(PYTHONS)生成器进阶
def average(): sum = 0 count = 0 avg = 0 while True: #循环 num = yield avg #遇到yield就停止,防止一开始除数为0导致崩溃 s ...
- JAVA项目从运维部署到项目开发(一.Jenkins)
一.Jenkins的介绍 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作, 旨在提供一个开放易用的软件平台,使软件的持续集成变成可能. 二.功能 Jen ...
- LeetCode题解之Find Bottom Left Tree Value
1.题目描述 2.问题分析 使用层序遍历思想 3.代码 int findBottomLeftValue(TreeNode* root) { if (root == NULL) ; queue<T ...
- svn状态与常见错误
TortoiseSVN 1.6.16是最后一个目录独立管理自身cache的svn版本(每个目录下都有一个隐藏的.svn文件夹) 之后的版本会则会根目录上统一进行管理(只有根目录下有一个隐藏的.svn文 ...
- java----自动类型转换
- mysql 数据库 命令行的操作——对库的操作
1.查看所有数据库 show databaese; 2.查看当前所用的数据库 show databases(): 3.切换数据库 use(数据库名): 4.创建数据库 create database ...
- JAVA初识,JAVA是什么?
一.什么是JAVA Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. Java语言作为静态 ...