目录
[Part1. 虚拟地址空间基础](#Part1. 虚拟地址空间基础)
[Part2. fork 与写时拷贝](#Part2. fork 与写时拷贝)
[Part2.1. fork子进程内部过程](#Part2.1. fork子进程内部过程)
[Part2.2. 写时拷贝](#Part2.2. 写时拷贝)
[Part2.3. 进程修改数据过程](#Part2.3. 进程修改数据过程)
[Part3. 缺页中断](#Part3. 缺页中断)
[Part4. 堆管理与malloc底层](#Part4. 堆管理与malloc底层)
[Part5. 虚拟地址的意义](#Part5. 虚拟地址的意义)
[Part6. 整体总结](#Part6. 整体总结)
[Part7. 结语](#Part7. 结语)
前言
在Linux操作系统中,认识虚拟地址空间是认识Linux内存管理非常重要的一部分,接下来来跟随小编的视角来看看吧。
let's go!!!!!!!!
Part1. 虚拟地址空间基础
一个进程对应一份虚拟地址空间。
• 32位机器:虚拟地址总空间 2^{32},大小4GB。
◦ 用户空间:0~3G;内核空间:3G~4G。
◦ 注意:内核空间,本质也是拿用户空间的虚拟地址去访问。
• 64位机器:拥有巨大的虚拟地址范围。
对于一个变量:
1. 变量拥有物理地址;同时,在所属进程的虚拟地址空间内,拥有虚拟地址。
2. 每个进程维护一张页表:虚拟地址 ↔ 物理地址,一一映射。
3. 小端机器取变量地址:取4字节中最小的地址;再结合类型做偏移,完成访问。
虚拟地址空间的内核数据结构
mm_struct 用来描述整个进程的虚拟地址空间。
内部包含若干个 vm_area_struct(VMA,虚拟内存区域),每一个代表一块区域:代码段、数据段、堆、栈。
每个VMA记录:start、end,代表该区域虚拟地址的起止;还有权限、偏移等属性。
虚拟地址空间是连续的;但是对应的物理内存可以是不连续。
Part2. fork 与写时拷贝
Part2.1. fork子进程内部过程
1. 调用fork(),分配内核数据结构,给子进程复制:task_struct、页表、虚拟地址空间。
2. 父进程的数据结构内容拷贝给子进程。
3. 将子进程加入运行队列。
4. fork返回,父子进程分别继续执行。
进程具备独立性:进程内部执行系统调用,都是操作自己这份虚拟内存。
Part2.2. 写时拷贝
fork刚创建子进程的时候:
1. 父子页表指向同一份物理内存,代码段、数据段共享物理页。
2. 但是把这些页的权限设置为只读。
3. 父子此时虚拟地址完全一样,映射到相同物理内存。
Part2.3. 进程修改数据过程
• CPU发现要写的页是只读,触发缺页异常。
• OS识别:这是写时拷贝场景,不是真的权限错误。
• OS分配一份新的物理内存,把原来页的内容复制过去。
• 修改当前进程的页表映射,让当前进程虚拟地址映射到新物理页;另一个进程依旧指向老物理页。
• 恢复页权限为可写,异常处理完毕,程序继续运行。
只有发生写操作,才真正分配物理内存;读操作依旧共享,不会拷贝。
fork 会返回两个id:父进程返回子进程pid,子进程返回0。本质是两个进程各自的页表,同一个虚拟地址读到不同返回值。
Part3. 缺页中断
虚拟地址已经分配,但还没有绑定物理内存,访问时触发缺页中断。
场景举例:我们程序有2G代码,虚拟地址会开辟2G虚拟空间,但是物理内存只有4G。不会一次性把全部代码加载进物理内存。
• 访问还没有映射物理内存的虚拟地址,触发缺页异常。
• 操作系统分配物理页,建立页表映射,把磁盘上对应的数据读到物理内存;异常处理完成,程序重新执行这条指令。
缺页的两种情况
1. 合法缺页:虚拟地址属于某个VMA(vm_area_struct),只是还没有分配物理页,正常分配内存即可。
2. 非法访问:访问的虚拟地址不在任何VMA之内,属于野指针,程序报段错误。
进程和内存解耦:进程的代码、数据,是基于虚拟地址。我们可以不释放虚拟地址,仅仅修改页表映射,解除和物理内存的绑定。不会影响进程运行。
进程退出
进程退出时:根据页表,把物理内存的数据写回磁盘;释放物理内存。
如果有虚拟内存,不需要把全部数据写回磁盘;只需要释放物理内存,虚拟地址本身依旧存在。
Part4. 堆管理与malloc底层
堆由vm_area_struct管理。
malloc(100)完整流程:
1. 在堆区划分虚拟地址,此时还没有物理内存。
2. 用户访问这块内存,触发缺页异常。
3. 操作系统分配物理内存,建立映射。
4. 如果堆空间不足,向OS申请更多虚拟地址。
free不会把虚拟地址还给操作系统。只是标记这块堆空间为空闲,交由malloc内部管理。堆过大才会触发堆回缩,把部分虚拟地址还给OS。
Part5. 虚拟地址的意义
1. 地址隔离保护:每个进程拥有独立虚拟地址空间,进程之间互不干扰。
2. 内存管理:虚拟地址连续,物理内存碎片化也没关系;OS可以把不常用内存置换到磁盘。
3. 权限管控:页表中设置页权限,实现只读、可读可写。访问违反权限直接触发异常。
4. 延迟分配物理内存:只有真正访问的时候,才分配物理内存。fork写时拷贝也是基于这个思想,节省内存。
Part6. 整体总结
1. 虚拟地址空间:逻辑上连续;物理内存可以离散。
2. 页表:完成虚拟地址→物理地址的翻译,同时记录页权限。
3. fork:初期父子共享物理页,标记只读;写时拷贝,发生写操作才复制物理页。
4. 缺页中断:虚拟地址已分配,无物理内存,访问触发异常;OS分配物理内存,加载数据。
5. malloc:先分配虚拟地址,真正访问才分配物理内存;free只做堆内部标记,不一定释放虚拟地址给操作系统。
6. VMA(vm_area_struct):管理各个段(代码段、数据段、堆、栈)的虚拟地址范围与权限。
Part7. 结语
这篇文章我们认识并知道了Linux虚拟地址空间,接下来,小编还会带来更多Linux相关的知识,敬请期待~
最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~
