栈和函数调用惯例与参数入栈顺序与虚地址空间的理解

Published: by Creative Commons Licence

  • Tags:

NEED TO KNOW

  1. 32为系统下,每个进程拥有4G的地址空间

  2. 本文所讨论的系统为32为Linux系统

Linux中虚地址空间的布局

分布情况如图:

linux-vram.jpg

让我们从图片由上到下分析:

1). Kernel Space(内核空间)

内核空间存储着内核的函数,并且总是驻留在内存中的,是操作系统的一部分,普通应用程序没有权限访问该空间内的数据。内核空间位于高地址处,32为Linux系统默认分配1G的空间给内核,windows默认是2G空间。

2). Stack(栈)

栈是用来传递数据(参数)的,并且数据是先出后进,先进入的数据被压入栈底,最后的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后一个数据被第一个读出来),相当于我们写程序,当需要交换两个变量的值的时候,就需要引入第三个变量来临时存放数据。栈为了速度快,使用CPU的寄存器传递参数。目前也就理解到这里。。

3). Dynamic library(共享库)

可执行文件执行所需要的库函数,例如一些DLL文件(动态链接库)。以下是网上找到的资料:该区域用于映射可执行文件用到的动态链接库。在Linux 2.4版本中,若可执行文件依赖共享库,则系统会为这些动态库在从0x40000000开始的地址分配相应空间,并在程序装载时将其载入到该空间。在Linux 2.6内核中,共享库的起始地址被往上移动至更靠近栈区的位置

4). Heap(堆)

通俗的讲,就是系统支持的内存空间,堆可以在运行时扩展或收缩。在程序中调用malloc/free,new/delete这些函数,所操作的内存区域就是堆。堆是内存中占的区域较大的分块,在32为系统中通常可以达到2.9GB(理论),也就是我们通常所说的32为系统只支持4G一下的内存,如果32为机器安装的内存大于4G,现实的空间就为2.9GB,几年前这种情况还是很常见的。

5). read/write sections(.data/.bass)(可读/可写数据区)

用来存放运行时文件中的数据。连个区域的区别:一个程序本质上都是由 bss段、data段、text段三个组成的。这样的概念,不知道最初来源于哪里的规定,但 在当前的计算机程序设计中是很重要的一个基本概念。而且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统运行时的内存大小分配,存储单元占用空间大小的 问题。在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。在《Programming ground up》里对.bss的解释为:There is another section called the .bss. This section is like the data section, except that it doesn’t take up space in the executable. text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。而在.bss段中声明未初始化的存储空间,使用指令resb(NASM)。

6). Readonly sections(.init/.rodata/.text)(代码和只读区域)

代码和只读区域通常是紧接着系统保留区的,通常有固定的位置,如Linux系统通常从0x08048000开始代码段,_代码及只读数据区是直接按照可执行目标文件的内容初始化的,与目标文件中的代码段(.text)、初始化段(.init)及只读数据段(.rodata)相对应,_应该是为了保护数据。

7). Reserved(保留区)

大多数位于低地址处,该区域收到操作系统的保护,禁止用户进程访问,这样的区域就叫做保留区,它并不是一个单一的内存区域,极小的地址通常都是不允许访问的,如NULL。C语言将无效指针赋值为0也是出于这种考虑,因为0地址上正常情况下不会存放有效的可访问数据。

32位平台中,进程虚拟地址范围为0x00000000-0xFFFFFFFF(共4GB),其中0x00000000-0xBFFFFFFF(共3GB)为用户空间,位于高地址部分的1GB为内核空间,范围为0xC0000000-0xFFFFFFFF。整个进程虚拟地址可分为以上几个部分。

**需要特别说明的问题:

从进程地址空间的布局可以看到,在有共享库的情况下,留给堆的可用空间还有两处:一处是从.bss段到0x40000000,约不到1GB的空间;另一处是从共享库到栈之间的空间,约不到2GB。这两块空间大小取决于栈、共享库的大小和数量。这样来看,是否应用程序可申请的最大堆空间只有2GB?事实上,这与Linux内核版本有关。在上面给出的进程地址空间经典布局图中,共享库的装载地址为0x40000000,这实际上是Linux kernel 2.6版本之前的情况了,在2.6版本里,共享库的装载地址已经被挪到靠近栈的位置,即位于0xBFxxxxxx附近,因此,此时的堆范围就不会被共享库分割成2个“碎片”,故kernel 2.6的32位Linux系统中,malloc申请的最大内存理论值在2.9GB左右。**

IA32的通用寄存器组

未完待续…