在多任务操作系统中,每个进程都运行在属于自己的内存沙盘中。这个沙盘就是虚拟地址空间(Virtual Address Space),在32位模式下它是一个4GB的内存地址块。在Linux系统中, 内核进程和用户进程所占的虚拟内存比例是1:3,而Windows系统为2:2(通过设置Large-Address-Aware Executables标志也可为1:3)。这并不意味着内核使用那么多物理内存,仅表示它可支配这部分地址空间,根据需要将其映射到物理内存。
虚拟地址通过页表(Page Table)映射到物理内存,页表由操作系统维护并被处理器引用。内核空间在页表中拥有较高特权级,因此用户态程序试图访问这些页时会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化。
Linux进程在虚拟内存中的标准内存段布局如下图所示:
其中,用户地址空间中的蓝色条带对应于映射到物理内存的不同内存段,灰白区域表示未映射的部分。这些段只是简单的内存地址范围,与Intel处理器的段没有关系。
上图中Random stack offset和Random mmap offset等随机值意在防止恶意程序。Linux通过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以免恶意程序通过计算访问栈、库函数等地址。execve(2)负责为进程代码段和数据段建立映射,真正将代码段和数据段的内容读入内存是由系统的缺页异常处理程序按需完成的。另外,execve(2)还会将BSS段清零。
用户进程部分分段存储内容如下表所示(按地址递减顺序):
名称 |
存储内容 |
栈(stack) |
局部变量、函数参数、返回地址等 |
堆(heap ) |
动态分配的内存 |
静态区(BSS段) |
未初始化或初值为0的全局变量和静态局部变量 |
数据段 .data |
已初始化且初值非0的全局变量和静态局部变量 |
代码段 .text |
可执行代码、字符串字面值、只读变量 |
在将应用程序加载到内存空间执行时,操作系统负责代码段、数据段和BSS段的加载,并在内存中为这些段分配空间。栈也由操作系统分配和管理;堆由程序员自己管理,即显式地申请和释放空间。
BSS段、数据段和代码段是可执行程序编译时的分段,运行时还需要栈和堆。
以下详细介绍各个分段的含义。
1 int a = 0; //a在全局已初始化数据区 2 char *p1; //p1在BSS区(未初始化全局变量) 3 main() 4 { 5 int b; //b在栈区 6 char s[] = "abc"; //s为数组变量,存储在栈区, 7 //"abc"为字符串常量,存储在已初始化数据区 8 char *p1,p2; //p1、p2在栈区 9 char *p3 = "123456"; //123456\0在已初始化数据区,p3在栈区 10 static int c =0; //C为全局(静态)数据,存在于已初始化数据区 11 //另外,静态数据会自动初始化 12 p1 = (char *)malloc(10);//分配得来的10个字节的区域在堆区 13 p2 = (char *)malloc(20);//分配得来的20个字节的区域在堆区 14 free(p1); 15 free(p2); 16 }
1 内核空间
内核总是驻留在内存中,是操作系统的一部分。内核空间为内核保留,不允许应用程序读写该区域的内容或直接调用内核代码定义的函数。
2 栈(stack)
由系统自动分配,栈区的分配运算内置于处理器的指令集,当函数执行结束时由系统自动释放。存放局部变量。栈的缺点是:容量有限,当相应的区间被释放时&#x