认识虚拟地址空间

很多初学者学习操作系统内存时,都会有一个疑问:为什么程序不直接访问物理内存,非要多一层虚拟地址转换?看似多此一举的地址映射,实则是操作系统隔离、安全、高效管理内存的核心精髓接下来就来刨析虚拟地址空间的核心吧!

首先要知道虚拟地址空间是什么,在Linux的内核数据结构当中存在进程的PCB,这个PCB就是内核数据结构当中描述进程核心资源的结构体,PCB里面就有描述组织虚拟地址空间的相关结构。这也是每个进程独有的一份资源。

如下是Linux2.6版本的内核代码:

如上的代码当中有PCB的结构体,里面有struct mm_struct *mm这个指针,他就是指向虚拟地址空间结构的指针,他会指向mm_struct这个结构体,该结构体当中有struct vm_area_struct * mmap这个指针,他就会指向vm_area_struct链表头。没错,Linux中对于虚拟空间的管理是采用双向带头链表结构来管理的,下图是逻辑链:

每个vm_area_struct结构体里面都会有所描述空间的起始虚拟地址,把这些地址进行划分,于此对用的就是我们常说的堆区,栈区等空间。这个时候就注意到了在虚拟地址空间当中会发现有内核空间,这部分区域是给内核,也就是操作系统使用的,用户无法直接使用该区域的地址。

所以,通过以上分析,虚拟地址空间是被划分区域的,总体被划分为俩个部分,内核空间和用户空间,那么这些区域都有什么用处?本篇文章也会解答。

例如,在栈区存放函数局部变量,函数调用栈帧(返回地址、函数参数),寄存器现场;堆区存放你申请的空间;代码段存放你相应的进程代码等,就不注意解释了。

其次,我们还要知道一个点,在用户看到的地址全部都是虚拟地址,真正的物理地址被屏蔽了,为了防止用户对内存误操作。

虚拟地址到物理地址的转换

那么,我们已经对虚拟地址空间有了初步的认识,那接下来的问题就是,他是如何找到数据的?这也是我在学习过程当中难以转变的一个坎。此时,就不得不提出另外一个结构了,处理虚拟地址到物理地址映射关系的结构——页表,他是负责虚拟地址到物理地址转换的结构,那他的转化具体过程较复杂,稍后进行说明。

接下来我们就要先了解一下,页表的相关内容了。首先,(32为机器下)在一个页表当中有2的十次方个条目,每个条目是有4字节,所以一个页表就是要占用一个物理页(系统内存管理) ,一个物理页就是4KB,而每个页表也需要被管理起来,就需要页目录存放页表的起始地址,注意,这里的起始地址是实际的物理地址,那么这时候就变成了查找页目录,在查找页表的过程了,那是谁来找到页目录的呢?这个工作就要硬件CR3来完成。所以,一个虚拟地址到物理地址的转换过程是由软件和硬件配合完成的。以上只是粗略的进行了阐述。下面来具体说明。

首先要知道在CPU当中集成了相当多的寄存器,而我们今天所用到的是CR3,MMU。先来说说这两的的用途,CR3里面记录了当前进程的页目录实际物理地址,MMU完成虚拟地址 ↔ 物理地址的翻译,同时做权限检查。这里我们先不考虑中断的问题。

接下来,CPU执行指令时候,发现要访问内存,于是把当前指令的虚拟地址扔给MMU,MMU结合CR3里面的当前进程的页目录地址,查找相应的内存。在这过程当中,虚拟地址发挥作用,虚拟地址一共32个比特位,在内核中我们把页表和页目录逻辑上视为数组,那么虚拟地址的最高10位,用于在页目录中去索引页表的实际物理地址,也就是页框的起始地址。再通过中间10位,索引到相应的实际存放数据代码的页框最后12位,则是在实际存放数据的页框中查找具体内容的偏移量。最后就锁定到了实际的内容。

这里我们注意到数字的特点,最后12位数,正好是4KB的大小,也就是一个物理页框的大小。同时,实际每个物理页框的起始地址的低12位也都是0,虚拟地址的后12位数的值正好可以把一个页框遍历完,这显然是被精心设计的。

那么我们在页目录和页表存储实际物理地址的时候,后12位就空了出来,那么这12位就是用来存储标志位的。没错,页表和页目录也需要权限设置,状态设置等。防止用户的非法访问,这也就是前面说的内核空间不允许用户访问的根本所在,因为标志位的存在

在上图中列举了一些标志位的具体含义和功能。

我们日常所说的页表是虚拟地址到物理地址的映射过程也就了然于胸了,无非就是通过虚拟地址+相应的硬件完成的查找的过程。

虚拟地址空间的优点及功能

虚拟地址空间在用户和武林垭村之间建立了屏障,保护我们物理内存当中的数据资源,防止用户误操作,如:野指针,越界。这些非法操作在虚拟地址空间和页表标志位这一步软件操作就可以阻断。

把物理内存的碎片化信息有序化,代码数据这些资源可以在不连续的页框当中存储,而虚拟地址空间把这些零散的数据通过虚拟地址的方式集合起来,通过页表构建映射,减少了物理内存带来的麻烦。同时,每个进程独有一份地址空间,进程 A 的虚拟地址,在进程 B 里指向完全不同内容;一个进程崩溃,不会破坏其他进程以及内核。实现软硬件层面隔离保护。

与此同时,虚拟地址空间也支持了如:mmap,懒加载这些功能。

好啦,这就是关于虚拟地址空间的一些内容,这个设计太夯了。本文章若有不足之处,希望大家指证!

Logo

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

更多推荐