linux 进程内存解析【转】
转自:http://blog.csdn.net/lile269/article/details/6460807
之前我所了解的linux下进程的地址空间的布局的知识,是从APUE第2版的P430得来的,之后上网查了一些资料,大概弄了明白。一个linux进程分为几个部分(从一个进程的地址空间的低地址向高地址增长):
1.text段,就是存放代码,可读可执行不可写,也称为正文段,代码段。
2.data段,存放已初始化的全局变量和已初始化的static变量(不管是局部static变量还是全局static变量)
3.bss段,存放全局未初始化变量和未初始化的static变量(也是不区分局部还是全局static变量)
以上这3部分是确定的,也就是不同的程序,以上3部分的大小都各不相同,因程序而异,若未初始化的全局变量定义的多了,那么bss区就大点,反之则小点。
4.heap,也就是堆,堆在进程空间中是自低地址向高地址增长,你在程序中通过动态申请得到的内存空间(c中一般为malloc/free,c++中一般为new/delete),就是在堆中动态分配的。
5.stack,栈,程序中每个函数中的局部变量,都是存放在栈中,栈是自高地址向低地址增长的。起初,堆和栈之间有很大一段空间,然后随着,程序的运行,堆不断向高地址增长,栈不断向高地址增长,这样,堆跟栈之间的空间总有一个最大界限,超过这个最大界限,就会出现堆跟栈重叠,就会出错,所以一般来说,Linux下的进程都有其最大空间的。
6.再往上,也就是一个进程地址空间的顶部,存放了命令行参数和环境变量。
整个进程地址空间布局如下图所示:
程序运行开始,由系统为进程地址空间中的text/data/bss段进行映射,由系统的缺页异常处理程序按需将磁盘上程序文件中的真正代码、数据写入进程。此外,bss区域中的所有变量都会被清零。
通过上面的讲述,我们知道了程序的代码,全局的变量,static变量是怎么在进程空间中分配空间的,接下来讲一下局部变量是怎么分配空间,函数是怎么调用的。其实也就是讲解栈区的具体使用过程。
首先,我们要知道,栈中存放的是一个个被调函数所对应的堆栈帧,当函数fun1被调用,则fun1的堆栈帧入栈,fun1返回时,fun1的堆栈帧出栈。什么是堆栈帧呢,堆栈帧其实就是保存被调函数返回时下一条执行指令的指针、主调函数的堆栈帧的指针、主调函数传递给被调函数的实参(如果有的话)、被调函数的局部变量等信息的一个结构。
堆栈帧结构如图所示:
首先,我们要说明的是如何区分每个堆栈帧,或者说,如何知道我现在在使用哪个堆栈帧。和栈密切相关的有2个寄存器,一个是ebp,一个是esp,前者可以叫作栈基址指针,后者可以叫栈顶指针。对于一个堆栈帧来说,ebp也叫堆栈帧指针,它永远指向这个堆栈帧的某个固定位置(见上图),所以可以根据ebp来表示一个堆栈帧,可以通过对ebp的偏移加减,来在堆栈帧中来来回回的访问。esp则是随着push和pop而不断移动。因此根据esp来对堆栈帧进行操作。
再来讲一下上图,一个堆栈帧的最顶部,是实参,然后是return
address,这个值是由主调函数中的call命令在call调用时自动压入的,不需要我们关心,previous frame
pointer,就是主调函数的堆栈帧指针,也就是主调函数的ebp值。ebp偏移为正的都是被调函数的局部变量。
好了,说了这么多,很枯涩,我们通过一个实例来讲下。
- int function(int a, int b, int c)
- {
- char buffer[14];
- int sum;
- sum = a + b + c;
- return sum;
- }
- void main()
- {
- int i;
- i = function(1,2,3);
- }
程序很简单,不解释。
在Linux下,我们通过 gcc -S example1.c 来生成汇编文件,然后我们查看上面这个程序对应的汇编程序:
1 .file "example1.c"
2 .version "01.01"
3 gcc2_compiled.:
4 .text
5 .align 4
6 .globl function
7 .type function,@function
8 function:
9 pushl %ebp //ebp这时指向的还是上一个堆栈帧,这个时候将ebp压入为的是返回的时候能够复原上一个堆栈帧
10 movl %esp,%ebp //上一步中已将ebp(上一个堆栈帧的ebp)压入保存,因此这时ebp能够腾出来指向新的堆栈帧了
11 subl $20,%esp //esp下移20个字节,就是申请20个字节的空间(buffer的16字节,sum的4字节)
12 movl 8(%ebp),%eax //ebp加8后,指向第1个实参,放入eax.
13 addl 12(%ebp),%eax //ebp+12后,指向第2个实参,与上一步中的第一个实参相加,放入eax.
14 movl 16(%ebp),%edx //第3个实参放入edx
15 addl %eax,%edx //第3个实参与上上步中的结果相加,就是3个实参相加,结果存入edx
16 movl %edx,-20(%ebp) //将结果放入刚才申请的sum中
17 movl -20(%ebp),%eax
18 jmp .L1
19 .align 4
20 .L1:
21 leave //leave和ret见下面的解析
22 ret
23 .Lfe1:
24 .size function,.Lfe1-function
25 .align 4
26 .globl main
27 .type main,@function
28 main:
29 pushl %ebp
30 movl %esp,%ebp
31 subl $4,%esp //申请4个字节(给局部变量i)
32 pushl $3 //压入实参3
33 pushl $2 //压入实参2
34 pushl $1 //压入实参1
35 call function //调用function函数,执行这条命令的时候还会将下一条指令的指针压栈
36 addl $12,%esp //加上12就释放了刚才压入的3个实参
37 movl %eax,%eax //function返回值在eax中
38 movl %eax,-4(%ebp) //返回值赋值给了刚才申请的空间(变量i)
39 .L2:
40 leave
41 ret
42 .Lfe2:
43 .size main,.Lfe2-main
44 .ident "GCC: (GNU) 2.7.2.3"
其中函数function的堆栈帧
注释:
1.function中,buffer是14个字节,sum是4个字节,照例应该是申请18个字节,但是第11行,程序申请了20个字节。这是时间效率和空间效率之间的一种折衷,因为Intel
i386是32位的处理器,其每次内存访问都必须是4字节对齐的,而高30位地址相同的4个字节就构成了一个机器字。因此,如果为了填补
buffer[14]留下的两个字节而将sum分配在两个不同的机器字中,那么每次访问sum就需要两次内存操作,这显然是无法接受的。这些都是跟编译器相关的优化技术。
2.我们再来看一下在函数function中是如何将a、b、c的和赋给sum的。前面已经提过,在函数中访问实参和局部变量时都是以堆栈帧指针为基址,再加上一个偏移,而Intel
i386体系结构下的堆栈帧指针就是ebp,为了清楚起见,我们在图7中标出了堆栈帧中所有成分相对于堆栈帧指针ebp的偏移。这下图6中12至16的计算就一目了然了,8(%ebp)、12(%ebp)、16(%ebp)和-20(%ebp)分别是实参a、b、c和局部变量sum的地址,几个简单的
add指令和mov指令执行后sum中便是a、b、c三者之和了。另外,在gcc编译生成的汇编程序中函数的返回结果是通过eax传递的,因此在图6中第
17行将sum的值拷贝到eax中。
3.我们再来看一下函数function执行完之后与其对应的堆栈帧是如何弹出堆栈的。图6中第21行的leave指令将堆栈帧指针
ebp拷贝到esp中,于是在堆栈帧中为局部变量buffer[14]和sum分配的空间就被释放了;除此之外,leave指令还有一个功能,就是从堆栈中弹出一个机器字并将其存放到ebp中,这样ebp就被恢复为main函数的堆栈帧指针了。第22行的ret指令再次从堆栈中弹出一个机器字并将其存放到指令指针eip中,这样控制就返回到了第36行main函数中的addl指令处。addl指令将栈顶指针esp加上12,于是当初调用函数
function之前压入堆栈的三个实参所占用的堆栈空间也被释放掉了。至此,函数function的堆栈帧就被完全销毁了。前面刚刚提到过,在gcc编译生成的汇编程序中通过eax传递函数的返回结果,因此图6中第38行将函数function的返回结果保存在了main函数的局部变量i中.
Linux下如果知道一个进程究竟占用了多少内存?这是个经常被问道和被答错的问题。进程的内存分配是个比较复杂的话题,这里通过一个例子进行说明。
有这么一个简单程序:
[root@pczou pczou]# cat ./prog.c
#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>#define ONEM (1024*1024)
int func()
{
char s[16*ONEM];
char* p;
p = malloc(32*ONEM);
pause();
return 0;
}int main()
{
printf("pid: %d/n", getpid());
func();
return 0;
}
其中func()这个函数分配了32MB的内存,以及16MB的堆栈。
运行一下,prog会停在pause()的位置,看看ps怎么说:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 4238 0.0 0.0 52396 352 pts/0 S 21:29 0:00 ./prog
VSZ指的是进程内存空间的大小,这里是52396KB;
RSS指的是驻留物理内存中的内存大小,这里是352KB。
一般系统管理员知道VSZ并不代表进程真正用到的内存,因为有些空间会仅在页表中挂个名,也就是说只是虚拟存在着,只有真正用到的时候内核才会把虚拟页面和真正的物理页面映射起来。比如,prog.c中用malloc()分配的32MB内存,由于程序中并没有用到这些内存,所以不应算到进程的帐上。
进程的内存使用情况比较复杂,这是因为:
- 进程申请的内存不一定真正会被用到
- 真正用到的内存也不一定是只有该进程自己在用 (比如动态共享库)
所以酒足饭饱结帐的时候,酒吧打出的帐单中不应该有没有上的菜,也不应该一个菜两份钱。而ps给出的就是这样的“糊涂”帐单,不足为凭。
算清楚帐的唯一办法是把每个菜都仔细过一遍,看看有没有上,有没有重复。下面的帐单要清楚多了:
Virtual memory : 52396 KB
Effective VM : 52120 KB
Mapped : 352 KB
Effective mapped: 76.6 KB
Sole use : 72 KBPer file memory use
ld-2.3.4.so : VM 94208 B, M 90112 B, S 8192 B
prog : VM 8192 B, M 8192 B, S 8192 B
libc-2.3.4.so : VM 1180 KB, M 221184 B, S 16384 B
可以看出,虽然虚拟地址空间是52396KB,实际映射(a.k.a. 分配)的空间是352KB,这和ps给出的结果一致。再看"Effective Mapped"这个值,仅为76.6 KB。这个值的计算方法是:
有效的实际使用内存 = 该进程独占的内存 + 共享的内存A /共享A的进程数目 + 共享的内存B /共享B的进程数目 + ...
虽然并不十分准确,但"Effective Mapped"已经足以说明进程所占用内存的实际大小了。
OK,最后用这个方法给所有进程都“结下帐”:
从上面的统计结果可以看出,
- 虽然firefox的占用虚拟空间是最大的,但其实际占用的内存却比X Server要少。
- firefox的实际占用的内存和其RSS (a.k.a.
mapped)差别不大,只有少了约700;而kontact的实际占用的内存比起RSS足足少了约1.2MB。由此可以看出我用的窗口管理器是KDE而非Gnome,why?
因为Qt之类的共享库被很多KDE进程分担了。 - http://docs.google.com/View?docid=acqszgd2zwsk_18gzv8dk
linux 进程内存解析【转】的更多相关文章
- Linux进程内存用量分析之堆内存篇
https://mp.weixin.qq.com/s/a6mLMDinYQGUSaOsGYCEaA 独家|Linux进程内存用量分析之堆内存篇 姬晨烜 58技术 2019-12-06 导语 本文将介绍 ...
- 转 linux进程内存到底怎么看 剖析top命令显示的VIRT RES SHR值
引 言: top命令作为Linux下最常用的性能分析工具之一,可以监控.收集进程的CPU.IO.内存使用情况.比如我们可以通过top命令获得一个进程使用了多少虚拟内存(VIRT).物理内存(RES). ...
- linux进程内存到底怎么看 剖析top命令显示的VIRT RES SHR值
引 言: top命令作为Linux下最常用的性能分析工具之一,可以监控.收集进程的CPU.IO.内存使用情况.比如我们可以通过top命令获得一个进程使用了多少虚拟内存(VIRT).物理内存(RES). ...
- 查看LINUX进程内存占用情况
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...
- 查看LINUX进程内存占用情况(转)
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1)top top命令是Linux下常用的性能分析 ...
- Linux进程内存分析和内存泄漏定位
在Linux产品开发过程中,通常需要注意系统内存使用量,和评估单一进程的内存使用情况,便于我们选取合适的机器配置,来部署我们的产品. Linux本身提供了一些工具方便我们达成这些需求,查看进程实时资源 ...
- Linux进程内存布局(翻译)
Anatomy of a Program in Memory 在一个多任务OS中,每个进程都运行在它自己的内存沙箱中.这个沙箱就是虚拟地址空间,在32位下就是一块容量为4GB的内存地址.内核将这些虚拟 ...
- 查看LINUX进程内存占用情况及启动时间
可以直接使用top命令后,查看%MEM的内容.可以选择按进程查看或者按用户查看,如想查看oracle用户的进程内存使用情况的话可以使用如下的命令: (1) top top命令是Linux下常用的性能分 ...
- Linux进程内存分析pmap命令(转)
名称: pmap - report memory map of a process(查看进程的内存映像信息)用法 pmap [ -x | -d ] [ -q ] pids... ...
随机推荐
- DAY2-Flask项目
回顾: 1.安装pipenv虚拟运行环境,隔离项目 (启动:pipenv shell) 2.安装flask(pipenv install shell),查看项目依赖(pipenv graph) 3.查 ...
- CIR,CBS,EBS,PIR,PBS傻傻分不清楚?看这里!—-揭秘令牌桶
概述 春暖花开的时候,大家都开着汽车外出旅游欣赏美丽的风景,却被堵在高速公路上,你是否为此感到痛苦?但如果有一种机制可以评估高速公路上的车流量.控制车流情况,确保进入高速公路的汽车都能在路上安全畅行, ...
- 设置debian6源
debian6真的太老了,个人喜欢追新,因为新版本用起来少很多乱七八糟的问题,不过线上有机器跑的这个版本,SO... 1.修改源文件,注释掉不可用的源,添加可用的源 # vim /etc/apt/so ...
- View的setLayerType() , setDrawingCacheEnabled() 方法用法
一.Android开发:用getDrawingCache方法获取ImageView中的图像需要注意的问题http://www.linuxidc.com/Linux/2011-09/43131.htm ...
- 解题:POI 2011 Dynamite
题面 从零开始的DP学习系列之叁 树形DP的基本(常见?)思路:先递归进儿子,然后边回溯边决策,设状态时常设$dp[x]$表示以$x$为根的子树中(具体分析算不算$x$这个点)的情况 显然的二分答案, ...
- POI上传,导入excel文件到服务器1
首先说一下所使用的POI版本3.8,需要用的的Jar包: dom4j-1.6.1.jarpoi-3.8-20120326.jarpoi-ooxml-3.8-20120326.jarpoi-ooxml- ...
- C++ public private protect 继承关系(链接)
基础链接 总结: public继承基类成员访问权限没有变化; protected继承基类public和protected权限变为protected,基类private不变. private继承基类p ...
- shell 中的流程控制关键字
if...else if [ $1x == "ab"x ]; then echo "you had enter ab" elif [ $1x == " ...
- lightoj 1205 数位dp
1205 - Palindromic Numbers PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 3 ...
- centos7 U盘安装卡在 starting dracut initqueue hook解决办法
U盘安装centos7启动过程中出现: [ok] Reached target Basic System 或者 [ok] starting dracut initqueue hook 到下一行就不 ...