【操作系统】进程虚拟地址空间分段解析

用户进程 的 虚拟地址空间部分 分段介绍

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)
  • 功能:
  1. 内存管理:
  2. 进程管理:
  3. 设备驱动管理:
  4. VFS虚拟文件系统:

2. 环境变量

  • 用于存放系统环境变量用户环境变量
  • 例如:Linux中的PATH环境变量

3. 命令行参数

int main(int argc, char* argv[])
  • 其中的是argv就是命令行参数
  • 命令行参数作用:可用于启动某一个程序的启动密码

4. 栈

  • 栈中存放非静态局部变量 函数形参 函数返回地址
  • 栈中内存空间由编译器(静态的)自动分配和释放,行为类似数据结构中的栈结构

主要用途:

  1. 为函数内部声明的非静态局部变量提供存储空间
  2. 记录函数调用过程相关的维护性信息,称为栈帧(stack frame)
  3. 作为临时存储区,用于暂时存放较长的算术表达式部分计算结果,或者运行时调用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,都是让指针指向该部分。

分段的好处

数据代码指令分别开辟空间有以下好处:

  1. 当程序被装载后,数据和代码指令分别映射到两个虚拟内存区域。数据区对于进程而言可读可写代码指令区对于进程而言只读
  2. 现代CPU一般数据缓存指令缓存分离,故进程虚拟地址空间中数据与代码指令分离有助于提高CPU缓存命中率
  3. 若系统中运行多个该程序的副本时,其代码指令相同,故内存中只需要保存一份该程序的代码指令,大大减少了内存的开销,相同的程序的代码指令可以被多个副本进程所共享,但是数据是每个副本进程所独有的。

猜你喜欢

转载自blog.csdn.net/weixin_45437022/article/details/115409679