Unix/Linux进程在内存中的布局

来源:互联网 发布:知乎专栏怎么赚钱 编辑:程序博客网 时间:2024/06/03 20:54

Unix/Linux进程在内存中的布局

对于Linux操作系统之上的程序而言,其运行的进程所使用的内存地址都是虚拟地址,是MMU经过映射后的地址,我们这里所谈及的内存也是虚拟内存,而不是物理内存。

如何得到进程在内存中的布局:

我们将编写好的程序经过GCC编译得到一个可执行的文件,然后将其运行起来,通过查看进程的命令得到进程ID:
ps -aux
在得到进程ID之后,我们通过pmap命令来查看进程在内存中的布局:
pmap -d 进程号
下图是笔者的一个程序程序通过pmap得到的进程内存布局:

以so结尾的是库文件,a.out的有两个,一个具可读可执行权限,也就是代码段,另一个具有可读可写的权限,也就是数据段,接下来的几段我们可能看不明白,但没关系,stack我们知道是栈。到此我们对进程在内存中的布局的分析还未成型,我们需要借助另外一个工具:readelf,一款查看elf文件信息的工具。
命令行下,运行readelf:
readelf -S a.out
得到下面一幅图:

这一次的信息量比上一次的大多了。看到了我们熟悉的.text,rodata,.data,.bss。下面是笔者抽象出来的进程在内存中的布局示意图,其中忽略了一些信息。

高地址在上,低地址在下。如果读者分析过足够多的程序,你会发现,Linux用户进程的开始地址是0x08048000。下面我们对其中的一些段作简单的解释。

栈(stack):

栈,也叫堆栈(这个叫法说实话真不像一个程序员叫出来的),向下生长。数据结构中,我们学习过栈,这里的栈就是数据结构中学习的那种结构,LIFO(后进先出)。C语言要想顺利的运行必须要有栈,栈是存放临时变量、函数调用现场等的内存空间。C语言中定义的普通局部变量就在栈中分配内存。

堆(heap):

堆是程序运行时能够自由分配的一段内存空间,向上生长。正由于其非常自由,所以很多的程序安全问题发生于此。例如臭名昭著的“内存泄漏”,就会发生在这里。当你不断地向获取内存,而不释放,那么堆就会越来越小,最后无法完成分配的工作时,程序也将发生异常。C语言中,我们使用malloc,calloc,realloc函数来申请对内存,请记住,有借一定要有还(free)。

数据段:

数据段分为.bss段和.data段。未初始化的全局变量和静态局部变量存放于bss段。bss段会被初始化为0,这也正是全局变量和静态局部变量不初始化也会是0的原因。已经初始化的全局变量和静态局部变量存放于data段,这个初始值从程序文件中复制而来。

代码段:

代码段分为.rodata,.text和.init。const修饰的全局变量和一些字面值常量存放于rodata段,这段内存是只读的。text段存放的是程序指令。init段则用来给用户程序添加一些“初始化”的代码,包括环境变量的准备,命令行参数的组织和传递等,并将这部分数据之余栈底。