文章目录
用户进程 的 虚拟地址空间部分 分段介绍
Linux中每一个运行的程序(进程),32位操作系统都会为其分配一个 0 ~ 4GB 的进程虚拟地址空间,64位操作系统会为其分配一个 0 ~ 16TB 的进程虚拟地址空间。
解释:
32 位操作系统下,一个指针的大小为 32 位即 4 个字节,它所能保存的地址范围为 [0, 2^32] ,所以它的寻址范围为 4GB 大小,所以 32 位操作系统下系统给进程分配的虚拟地址空间大小为 4 GB 。
64 位操作系统下,一个指针的大小为 64 位即 8 个字节,它所能保存的地址范围为 [0, 2^64] ,即 4GB * 4GB = 16TB,所以它的寻址范围为 16TB 大小,所以 64 位操作系统下系统给进程分配的虚拟地址空间大小为 16TB 。
进程:运行中的程序,Windows下的可执行文件有 .exe
文件,Linux下可执行文件格式ELF
图解虚拟地址空间分段
1. 内核空间
- 内核空间为内核保留,是受到系统保护的,用户不能对内核空间中的内容进行读写操作,否则会出现段错误(segmention fault)
- 功能:
- 内存管理:
- 进程管理:
- 设备驱动管理:
- VFS虚拟文件系统:
2. 环境变量
- 用于存放
系统环境变量
和用户环境变量
- 例如:Linux中的
PATH环境变量
3. 命令行参数
int main(int argc, char* argv[])
- 其中的是
argv
就是命令行参数 - 命令行参数作用:可用于启动某一个程序的启动密码
4. 栈
- 栈中存放
非静态局部变量
函数形参
函数返回地址
等 - 栈中内存空间由
编译器
(静态的)自动分配和释放,行为类似数据结构中的栈结构
主要用途:
- 为函数内部声明的非静态局部变量提供存储空间
- 记录函数调用过程相关的维护性信息,称为栈帧(stack frame)
- 作为
临时存储区
,用于暂时存放较长的算术表达式部分计算结果
,或者运行时调用alloca函数动态分配
栈内内存
- 栈内存增长:栈能够增长到的最大内存容量为RLIMIT_STACK(通常是8M),如果此时栈的大小未达到RLIMIT_STACK,则栈会自动增长至程序运行所需的大小,如果此时栈的大小已经达到RLIMIT_STACK,若再向栈中不断压入数据,会触发页错误。栈的实时大小会在运行时由内核动态调整
- 查看栈大小:
ulimit -s
可查看和设置栈的最大值
,当程序使用的栈大小超过该值,会发生segmentation fault
- 栈的增长方向:既可以向高地址增长,也可以向低地址增长,这取决于具体实现,该篇文章默认是向下增长
5. 内存映射段
- 内核将
硬件文件的内容
直接映射到内存,任何应用程序都可通过操作系统的系统调用来请求这种映射。 - Linux下的
请求映射的系统调用
为mmap系统调用
;Windows下的请求映射的系统调用为CreateFileMapping或MapViewOfFile系统调用。 - 内存映射是一种高效的文件IO方式,通过
映射
来间接的高效访问位于磁盘上的文件,因而可以用来装载动态共享库文件(动态链接库文件)。 - 映射动态链接库文件:一个程序运行时,在
运行期的链接阶段
需要链接很多依赖的动态链接库文件
,在Linux 2.4内核版本中,系统会将0x40000000作为起始地址为这些动态链接库文件分配相应空间,并在程序装载时将动态链接库文件载入到该空间;而在Linux 2.6内核版本中,装载的起始地址移动至更加靠近栈区的位置,约在0xBFxxxxxx附近。 - 若通过
malloc
请求一大块内存(意味着比MMAP_THRESHOLD
还要大,缺省为128KB),此时C运行库将创建一个匿名内存映射
,而不是使用堆内存
。
6. 堆
- 堆用于存放进程
运行时动态分配
的内存段,可动态扩张或缩减。 - 堆中内容是
匿名
的,无法通过名字进行访问,只能通过指针
进行间接访问。 - 当进程调用
malloc(C)/new(C++)
等函数分配内存时,新分配的在堆上动态扩张
;当调用free(C)/delete(C++)
等函数释放内存时,被释放的内存从堆上动态缩减
- 分配的堆内存时经过
字节对齐
的空间,以适合原子操作
。 堆管理器
通过链表
管理每个申请的内存块- 由于堆内存块的申请与释放都是
无序
的,最终会产生许许多多内存碎片
。 - 堆的末端由
break指针
标识,当堆管理器需要更多内存时,可通过系统调用brk和sbrk
来移动break指针
以扩张堆,一般情况下由系统自动调用。
7. 数据段
- 用于存放
全局变量
和静态变量
7.1 BSS段
- 未初始化的全局变量和初始化为 0 的
全局
变量(包含全局静态变量) - 未初始化的静态局部和初始化为 0 的
静态局部
变量
7.2 DATA段
- 初始化值不为 0 的
全局
变量(包含全局静态变量) - 初始化值不为 0 的
静态局部
变量
8. 代码段
- 代码段也称正文段或文本段,用于存储CPU执行的
机器指令
(C语言执行语句翻译成机器代码) - 通常代码段是
可共享
的,可共享是指频繁运行的程序只需要在内存中有一份拷贝即可 - 通常代码段是只读的,为了防止他人恶意修改机器指令
9. 保留区
- 位于
虚拟地址空间的最低部分
,系统不给该部分虚拟地址分配物理地址空间
,任何对它的引用和访问都是非法
的。 - 比如:给指针赋值为NULL或者0,都是让指针指向该部分。
分段的好处
数据
与代码指令
分别开辟空间有以下好处:
- 当程序被装载后,数据和代码指令分别映射到两个虚拟内存区域。
数据区
对于进程而言可读可写
,代码指令区
对于进程而言只读
- 现代CPU一般
数据缓存
和指令缓存
分离,故进程虚拟地址空间中数据与代码指令分离有助于提高CPU缓存命中率
- 若系统中运行多个该程序的副本时,其代码指令相同,故
内存
中只需要保存一份该程序的代码指令
,大大减少了内存的开销,相同的程序的代码指令可以被多个副本进程所共享,但是数据
是每个副本进程所独有
的。