在讲进程的内存布局,也就是进程的虚拟地址空间的时候,首先应该对虚拟内存有一定的了解:虚拟内存浅析。下面开始正题。
一 历史进程内存
在原来的32位的操作系统中,由于指针是4个字节,所以它的最大寻址空间就只有4G,进一步的,这4G分为3G的用户空间和1G的内核空间。
image.png
对于32位的cpu来说,进程的虚拟地址空间从高地址到低地址一共分为七段:
(1) 内核空间 : 权限较高
(2) 栈空间:栈里保存了局部变量,以及大多数编程语言中的函数参数。一次方法或函数调用就会向栈增加一个栈帧(stack frame)。当函数返回时栈帧就会被销毁。如果推入栈的数据过多,可能会耗尽栈映射的地址区域。这会导致一次页错误,Linux将其处理为一次expand_stack()调用,实际上是调用acct_stack_growth()检查当前是否可以增加栈大小。如果栈大小小于RLIMIT_STACK(通常8MB)就可以继续增长,程序会正常继续,不会察觉到什么。这是栈大小调整的默认处理。但是,如果栈大小达到了上限,就会发生栈溢出,程序会接到一次段错误(Segmentation Fault)。相对的,当栈变小时,不会缩减栈大小。
(3) mmap段:内核在这里将文件内容映射为内存。通常被用来加载动态链接库。
(4) 堆:堆提供了运行时的内存分配,这点和栈类似。
(5) BSS、data、代码段:
-
BSS和data段都是存储静态变量和全局变量的空间。区别是BSS段存储没有初始化的
变量。BSS区是匿名的:不映射自任何文件。如果代码中有static int cntActiveUsers;,那么cntActiveUsers就在BSS段. -
data段则存放代码中显示初始化了的静态变量。
-
代码段:通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定。
对于现在的64位操作系统,情况已经大不一样了,下面做个小实验:
using namespace std;
static int c = 4;
int main(void) {
int a = 1;
int b = 2;
cout << "栈开始: " << &a << " " << "栈地址减小: " << &b << " " << "BSS段位置: " << &c << endl;
return 0;
}
结果:
栈开始: 0x7ffee646dad8 栈地址减小: 0x7ffee646dad4 BSS段位置: 0x1097960c0
可以看到,栈起始地址:0x7ffee646dad8,转换为Gb为106万,虚拟地址已经恐怖如斯,可以看到,再在栈上定义一个变量b,栈空间向下减小4个字节。静态全局变量c的地址为0x1097960c0,远远小于栈地址,可以看到即使是64位的虚拟地址空间也是遵守基本规律的。
网友评论