我们知道,一个可执行文件运行起来的话它就变成了进程。在x86 32bit Linux下,我们的系统会给每个进程分配一个4G大小的虚拟地址空间。你可能会说,我电脑总共就4g内存这每个进程都给4g怎么可能啊。注意虚拟两个字。那么这块空间里都装了什么东西呢?
其中前3G内存是用户空间,最后1G是内核空间。所有的进程有各自的用户空间,但所有的进程都共享一个内核空间。不要着急,我们从上到下挨个来分析。
假设这段虚拟地址空间是从-0x00000000开始的。
那么首先是用户空间:
①128M的不可访问区域
如果我们定义了一个指针为NULL到最后忘记赋值,却使用了strcpy之类的函数就会出错,原因就是你妄图对这块不可访问区域做些什么。即是0x00000000到0x80480000这段空间。其实在128M下面紧接着还有一点空间也是属于不可访问区域,防止.text段的数据越界。所以.text段并不是严格意义上和不可访问区域紧密连接的。
②.text代码段
这段存储的是你编写的代码中的指令。什么是指令?一份代码是由数据和指令组成的,除了数据之外都叫做指令。同时,局部变量也属于指令,但是局部变量存储在栈上。
③.data数据段
这段存储的就是代码中的各种数据了,包括全局变量,静态全局变量,静态局部变量。但是需要注意的是,必须是已经赋值且赋值不为0的数据才存储在该段中!
④.bss段
它存储的也是数据,不过是未赋值或者赋值为0的数据了。这是一个不占文件空间的段,具体在下面.obj文件组成部分有讲解。
⑤空洞
这是给堆预留的空间
⑥栈
所有的局部变量都存储在这里且函数的运行也需要栈,需要注意的是上面是栈底下面是栈顶(当前x8632位系统是小端模式,低字节存储在低地址。关于大小端的判断可以看我的另一篇博客)
⑦命令行参数
我们在写main函数的时候一般都是这么写:
int main()
{
return 0;
}
或
int main(int argc,char **argv)
{
return 0;
}
其实它的原型长这样:
int main (int argc, char** argv, char**environ)
前两个参数大家应当都很熟悉,argc是命令行参数的个数,不指定时默认为1,是当前文件名,char **argv也可以写成char *argv[],其中argv[0]默认保存的就是argc==1时的程序名。如果在执行程序时后面跟随指令,则argc的值和argv[]中的元素就会发生变化。然而这第三个参数是什么意思呢?在我们调用一些系统库函数的时候,我们需要使用预处理指令#include。但是我们系统怎么知道去哪个路径寻找这些头文件的函数定义呢?就是通过char **environ(环境变量)来实现的。
话说回来,命令行参数就是我们的argv了。里面存放着我们的程序名,以及参数(如果你给了的话)。
⑧环境变量
就是在第⑦条中介绍的,main函数的第三个参数啦。
接下来我们来看内核空间:
①ZONE_DMA(16MB)
全名ZONE_DirectMemoryAccess(直接内存访问),可以加快磁盘和内存之间数据的交换,不需要经过CPU的寄存器,这时CPU可以去干别的事,大大增加了效率。
②ZONE_NORMAL (892MB)
内核中最重要最常用的部分。
③ZONE_HIGHMEM (128MB)
所谓的高端内存,用于在内核中映射高于1G的物理内存时使用。64位系统则没有该段(根本不需要,因为64位操作系统给内核空间分配的内存达到512G)。