# 虚拟内存空间分布图
# 1.学习意义
1.C语言和C++
底层原理分析利器
2.将我学过的计算机组成原理
,汇编
,C语言
,C++
和操作系统
能够很好的串起来
# 2.程序加载"后"的虚拟内存分布✔️
- 环境:
- CPU:32位X86的物理机器
- OS:Linux
- ELF格式文件
# 2.1."虚拟内存空间"大致分布【严格来说画错了-因为虚拟内存不存在.XX这样的,因为映射完后都是BSS和DATA这种】
# 2.2.从下到上进行说明
- 内核区:kernel映射到进程虚拟内存,但程序无法访问
argv
,environ
:命令行参数和环境变量- 其实就比如
ping -t www.baidu.com
,我们也可以写命令行的参数
- 其实就比如
- Stack(栈/堆栈,向低地址生长):
- 程序中,『非静态的局部变量』放的地方
- 吐槽,叫栈就好了,前面还加个堆,以前入门的时候,还在这个到底是堆,还是栈。。。
- 共享库:
- 比如,Linux你所制作的.so(动态库),加载的位置就是在这。
- 内存映射段,文件映射(包括动态库)和匿名映射。示例:/lib/libc。所以
- heap(堆,向高地址生长):
- 比如C语言中
malloc
的,C++
中new
- 吐槽:这个堆和数据结构中的堆不一样,弄得那时候先学数据结构的我,看到这个,条件反射问最大堆还是最小堆...最后发现都不是..
- 比如C语言中
.bss
:- 『未初始化』的
全局变量
和静态变量(全局+局部)
- 存放程序中『初始化为0』的
全局变量
和静态变量(全局+局部)
听说这样放的原因是:初始化为0,这个可以被优化为这个
- 『未初始化』的
- .
data
(数据段):- 存放『已初始化』的
全局变量
和静态变量(全局+局部)
- 存放『已初始化』的
.rodata
(只读存储区,有书叫常量区):- 比如存放,『字符产常量』
.text
(代码段,还叫.code
):- 程序源代码编译之后的机器指令,放在这个代码段
- 吐槽:有的书上,把
.rodata
和.text
统称为代码段,这些规定还真是。
- 其实这里,还有一个保留区:禁止访问,这也解释了我们
- 为什么不能给地址为0的地方写数据,不然就会产生段错误。
注意:
- 1.我为什么要将
.rodata
和.text
这么画呢?- 因为这两段区域的权限是read only的
将来在链接
的时候,会把他们完成数据段合并,合并到一块
- 因为这两段区域的权限是read only的
- 2.其实ELF文件,映射到虚拟内存空间(不存在,假象的,但是用来分析进程很有用)
不止以上,我画的这几个段,但那些和我们编程无关的话,比如,调试段,我们就没画了 - 3.我们在分析C语言和C++的底层原理的时候,用上面的粗略的就完全足够了,没必要用太详细的
# 3.虚拟内存空间+物理内存空间
计算机工作流程
- 程序→加载后→进程(进程实际不存在)
- (虚拟内存空间→映射→物理内存空间)
以Linux系统为例
程序:比如,编译后的可执行文件
a.out
,是用ELF格式存储注意:进程是不存在实体的,但是我们可以通过,虚拟内存实际上是不存在实体的,但是引入这个抽象的概念,能够简化我们汇编编程,C语言编程等程序设计。
- 正是因为有虚拟内存,所以,我们才能在物理内存少于虚拟内存的地方,还能跑起来程序
物理内存空间:也就是我们的内存条,是实际存在的。
补充:
我们想要获取进程的
进程id
啥的,可以通过内核区
的PCB虚拟内存
如何映射到实际(物理)内存
?- 通过CPU中,有个硬件叫做
MMU
- 通过CPU中,有个硬件叫做
如果r和w权限和r权限的那两个段的大小允许的话
- 我们会把他放到一个**page(页)**里面,在我们32位机上,一个页面的大小,是4KB,这样的目的是为了节省内存,节省空问
# 4.关于"虚拟内存空间"的画法
下面展示了其他书籍上,关于虚拟内存空间的画法
- 《程序员的自我修养》P167
- 知乎 (opens new window)具体链接到一个国外的:https://manybutfinite.com/post/anatomy-of-a-program-in-memory/ (opens new window)
说明的问题:
栈的生长方式等的争论,这个其实取决于操作系统和硬件实现,但是业界目前面试等时候,还是默认用大多数机型的,我前面写的那种的方式。
《CSAPP(深入理解计算机系统)》原书第2版,中文P554
# 1.坑-虚拟内存中有没有.rodata和.data和.text呢?
回答:没有!你说的这些概念,是ELF文件中的!
ELF文件中的一部分映射到虚拟内存是不存在这些的!
1、ELF文件映射到【虚拟内存】
- 程序编译后的二进制文件如何映射到虚拟内存空间中
我们写的程序代码编译之后会生成一个 ELF 格式的二进制文件,这个二进制文件中包含了程序运行时所需要的元信息,比如程序的机器码,程序中的全局变量以及静态变量等。
这个 ELF 格式的二进制文件中的布局和我们前边讲的虚拟内存空间中的布局类似,也是一段一段的,每一段包含了不同的元数据。
2、【核心】
磁盘文件中的段我们叫做 Section,内存【虚拟内存】中的段我们叫做 Segment,也就是内存区域。
3、映射规则
磁盘文件【ELF文件】中的这些 Section 会在进程运行之前加载到内存中并映射到内存中的 Segment。
- 通常是多个 Section 映射到一个 Segment。
ELF文件中的Section | 虚拟内存空间中 | |
---|---|---|
.text,.rodata 等一些只读的 Section | 一个只读可执行的 Segment 里(代码段) | |
.data,.bss 等一些可读写的 Section | 一个具有读写权限的 Segment 里(数据段,BSS 段) | BSS,未初始化的全局变量或静态变量被加载进内存之后会被初始化为 0 值 |
注意点:
**动态链接库(.so)中的代码段,数据段,BSS 段,**以及通过 mmap 系统调用映射的共享内存区,在虚拟内存空间的存储区域叫做【文件映射与匿名映射区】。
# 参考资料
- 俞甲子 / 石凡 / 潘爱民《程序员的自我修养 (opens new window)》,
- 《程序员的自我修养》—全书思维导图,博客 (opens new window),知乎