目录

前言

Part1. 虚拟地址空间基础

Part2. fork 与写时拷贝

Part2.1. fork子进程内部过程

Part2.2. 写时拷贝

Part2.3. 进程修改数据过程

Part3. 缺页中断

Part4. 堆管理与malloc底层

Part5. 虚拟地址的意义

Part6. 整体总结

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相关的知识,敬请期待~

 最后,祝大家可以:春风得意马蹄疾,一日看尽长安花!

 最后的最后,要是觉得本文还可以的话,可以点点赞,关注小编一波,谢谢大家!~

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐