分段、分页虚拟内存访问原理、应用


参考书及图来源:x86实模式到保护模式、操作系统真像还原、操作系统导论、mooc计算机系统基础

分段机制

一个段是地址空间里一个定长的区域,在典型的地址空间中分为3个逻辑不同的段:代码、栈、堆(实际更多)

分段机制使得操作系统将不同的段加入到不同的物理内存区域。

上图为将一个程序分为代码、堆、栈 三段在内存中的分布图。

 关于具体的如果将代码进行分段后面会写ELF文件相关文章。

那如何选择一个段呢?

硬件方案一:

  每个段准备一个段寄存器。操作系统切换程序时段数据在进程结构中保存,也可以建立单独的表保存。

方案二:

  一个段寄存器,最高两位用于段的选择,其他位用于偏移量。

段选择位+段偏移量  构成的二进制数即为虚拟地址。

假设段偏移量为12位,

segment = (registerValue & 0x3000) >> 12  //拿到段序号

offset = SegmentRegisterValue & 0xFFF     //拿到偏移量

if luckly(segment < len(Base)) {         //段存在状态
physAddr = Base[segment] + offset         //生成物理地址
register    = AccessMemory(physAddr)      //将物理地址中的数据或代码写入相应寄存器
}
 报错

以上逻辑可以通过软件、硬件实现。

虚拟内存

一段C代码经过编译、汇编、链接后会生成可执行文件如下图,可执行文件的 text 和 data 就是我们了解的代码段和数据段,栈段和堆都要创建进程时在内核态申请的空间。

上图中每个段其实都分配了一个虚拟地址,这个虚拟地址是在链接时分配记录在可执行文件中的。

 可执行文件中段的虚拟内存地址。

虚拟地址并不是物理地址,虚拟地址会和实际的物理地址进行一 一对应。(但是并不是每个虚拟地址都会分配到实际的物理地址)

在内存中,每个进程时会创建一个页表,在进程中的PCB中记录了页表的相关信息包括其所在内存中的起始位置,因为计算机中有个页表寄存器cr3,会记录当前所执行的程序或者内核所使用的多级页表的起始位置方便快速找到地址。这个页表中会记录虚拟地址所对应的物理地址。

二级页表所对应的虚拟地址如下图

13 12 11 10 9 8 7 6 5 4 3 2 1 0  这一串数字就是虚拟地址,但是我们一般看到的都是16进制表示的。

举例虚拟地址的访问过程:

设gs段寄存器中的数据为全0.

mov  eax,gs : [13 12 11 10 9 8 7 6 5 4 3 2 1 0 ]

所以这条命令可以知道我们访问的地址为13 12 11 10 9 8 7 6 5 4 3 2 1 0  ,将这个地址中的32位数据复制到eax寄存器中一份。

这串数字会交给CPU的mmu组件,根据页目录索引、页表索引、偏移量 从页表中找到对应的物理地址再交给CPU。

将物理地址放入MAR寄存器,打开读信号,再从MDR寄存器中取出物理地址的数据,取操作数周期结束

在执行周期将数据写入eax寄存器中。

页表使用的过程:

32位的系统中,一个32位的虚拟地址,10位用来表示页目录索引,10位用来表示页表索引,12位表示偏移量

10位页目录索引可以表示 1024项既1024个页目录索引,每项4字节

每个页目录记录一个页表索引物理地址,

每个页表索引有1024项,每项中存储一个物理地址,每项4字节

假设我们访问地址  0000000001    0000000001   000000000011

设0000000001 * 4  中的物理地址为 0x1000 (乘4的原因是因为每项4字节,所以第二项的地址为8),这里还要考虑cr3寄存器的地址,为了简单不考虑了。

0x1000 为页表索引的基址。

每个页表索引也有1024项,中间10位就是指向的第几项,我们这里是第二项。(0位第一项)

0x1000 + 1 的地址就是我们需要的物理地址,假设物理地址为 0x12000

物理地址+ 偏移量 3 即为我们要寻找的物理地址。