从零开始写一个操作系统内核 笔记(四) 番外篇 8086CPU 之分页机制

一. 逻辑地址和线性地址

下面是一条普通的 eax赋值指令
> mov 0x80495b0, %eax
在这里插入图片描述
首先 这条指令 真正会被执行成 mov ds:0x80495b0, %eax
在保护模式下,ds 是个段选择子,通过 GDT在 表 找到对应的 线性起始地址 再 加上 偏移地址 计算出 线性地址,在 保护模式未开启分页的情况下 线性地址就直接对应到物理地址,如果在 开启了分页机制 通过分页映射到一个物理地址。

CPU 分页机制

在这里插入图片描述
页内部连续的线性地址映射到连续的物理地址中

32位 下 分页大小为 4Kb.CPU分页 要借助 页表,页表 存放在内存中

保护模式下,控制寄存器CR0的最高位PG位控制分页的开启 PG =1 此时通过页表才能把线性地址转换为物理地址,

PG = 0 分页机制无效,线性地址就直接作为 物理地址。

每个任务可以有自己的页目录表和页表

为了节约内,8086 CPU 的设计者将线性地址通过页目录表和页表两级查找转换成物理地址。
  1. 32模式下线性地址通过3个部分组成:

    • 最高10位 Directory 页目录表偏移量

      • 页目录表的大小为4KB(32位模式下),记录一个地址需要4K在32位模式下,如果 内存被拆成 4KB 为最小单位,那么 4G = 2 的 20次方 * 4KB = 220102442^{20} * 1024 * 4 如果页表是一个大数组,每个页数组记录连续4KB的起始位置,那么 每个地址32位地址栈4个字节 需要22042^{20} * 4 = 4M的大小存放,Intel觉得这样太耗费空间了,将其拆分成 2个表 通过计算编程一个线性地址。

        下面来个直观的例子:
        image-20200322163417627

    • 中间10位 Table 页表偏移量

    • 最后12位Ofsset是屋里也内侧字节偏移量。
      在这里插入图片描述
      总结 : 1个一目录占 4个字节,每条记录 栈4个字节 32 位 其中只有 前10位表示表示页表的起始地址一个进程都会有自己独立的页目录,一个页目录对应多个 页表,通过线性地址的 前 10位找到页目录的起始索引 -> 通过线性地址的后 10位 取得页表的索引,通过页表的索引的20bit 索引到物理页的 地址 -> 线性地址的最后12位为 4K内的偏移
      在物理页内找到的 20位 + 线性地址最后12位 这样就能索引到2322^{32} 4G的内存。
      值得注意的是,页表不需要一开始就分配,当CPU 执行程序 查找页表时没分配时触发系统异常中断,有操作系统处理再行分配,同时页表可以放在内存不连续处。
      f(:)=线f_{段映射}(选择子:偏移)=线性地址
      f(线)=f_{页映射}(线性地址)=物理地址

一、虚拟地址、物理地址、逻辑地址概述

物理地址(physical address)
用于内存芯片级的单元寻址,与处理器和CPU连接的地址总线相对应。
——这个概念应该是这几个概念中最好理解的一个,但是值得一提的是,虽然可以直接把物理地址理解成插在机器上那根内存本身,把内存看成一个从0字节一直到最大空量逐字节的编号的大数组,然后把这个数组叫做物理地址,但是事实上,这只是一个硬件提供给软件的抽像,内存的寻址方式并不是这样。所以,说它是“与地址总线相对应”,是更贴切一些,不过抛开对物理内存寻址方式的考虑,直接把物理地址与物理的内存一一对应,也是可以接受的。也许错误的理解更利于形而上的抽像。

虚拟内存(virtual memory)
这是对整个内存(不要与机器上插那条对上号)的抽像描述。它是相对于物理内存来讲的,可以直接理解成“不直实的”,“假的”内存,例如,一个0x08000000内存地址,它并不对就物理地址上那个大数组中0x08000000 - 1那个地址元素;
之所以是这样,是因为现代操作系统都提供了一种内存管理的抽像,即虚拟内存(virtual memory)。进程使用虚拟内存中的地址,由操作系统协助相关硬件,把它“转换”成真正的物理地址。这个“转换”,是所有问题讨论的关键。
有了这样的抽像,一个程序,就可以使用比真实物理地址大得多的地址空间。(拆东墙,补西墙,银行也是这样子做的),甚至多个进程可以使用相同的地址。不奇怪,因为转换后的物理地址并非相同的。
——可以把连接后的程序反编译看一下,发现连接器已经为程序分配了一个地址,例如,要调用某个函数A,代码不是call A,而是call 0x0811111111 ,也就是说,函数A的地址已经被定下来了。没有这样的“转换”,没有虚拟地址的概念,这样做是根本行不通的。
打住了,这个问题再说下去,就收不住了。

逻辑地址(logical address)
Intel为了兼容,将远古时代的段式内存管理方式保留了下来。逻辑地址指的是机器语言指令中,用来指定一个操作数或者是一条指令的地址。以上例,我们说的连接器为A分配的0x08111111这个地址就是逻辑地址。
——不过不好意思,这样说,好像又违背了Intel中段式管理中,对逻辑地址要求,“一个逻辑地址,是由一个段标识符加上一个指定段内相对地址的偏移量,表示为 [段标识符:段内偏移量],也就是说,上例中那个0x08111111,应该表示为[A的代码段标识符: 0x08111111],这样,才完整一些”

32位保护模式下 开启分页:

;========================================;
;                开启分页机制
; 加载页到1M以上空间,页目录地址:0x100000 1M处
; 页表开始位置:0x101000  1M + 4KB(一个页目录);                                        
;========================================;
SwitchPage:
  xor   edx,edx         ;除法初始化edx寄存器
  mov   eax,[ddMemSize] ;获取内存大小
  mov   ebx,0x400000 ; 一个页表 有1024 项 每一项 表示4KB 所以 是 1024 * 4KB = 4MB 一个页表能表示4MB大小
  div   ebx          ;计算 你当前拥有内存需要几个页表 才能表示  内存大小/每页页表所能表示的大小
  mov   ecx,eax      ;ecx 表示 所需页表PDE的格式 
  test  edx,edx      ;如果 不能整除 就要再添加一个页表
  jz    .no_remainder;如果 能整除 
  inc   ecx          ;多加一个 表
.no_remainder:
  push  ecx
  ;首先初始化页目录
  mov   ax,SelectorData
  mov   es,ax
  mov   edi,PDE_ORIGIN_ADDRESS ;页目录起始位置 页目录占4K 
  xor   eax,eax
  ;分页的 地址 和属性 占 32字节 前20字节指向物理页的前10位页表起始位置 前 20位 页存在属性 | U/S 属性位值,用户级 | R/W 属性位值,读/写/执行
  mov   eax,PTE_ORIGIN_ADDRESS | PG_P | PG_US_U | PG_RW_W
.SetupPDE:
  stosd
  add     eax,0x1000 ;每个页表地址 相差4K 4096个字节
  loop    .SetupPDE ;循环 直到ecx为0
  pop     eax  ;取出页表个数
  mov     ebx,0x400;每个页表可以存放 1024 个PTE
  mul     ebx     ;页表个数 * 1024  得到需要多少个PTD
  mov     ecx,ebx ;eax = PTE个数,用于循环
  mov     edi,PTE_ORIGIN_ADDRESS ;页表首地址 
  xor     eax,eax
  ;页表从0 开始
  mov     eax,PG_P | PG_US_U | PG_RW_W
.SetupPTE:
  stosd    ;将ds:eax -> ds:edi
  add      eax,0x1000  ;页指向 物理内存 从 0 ,4K ,8K  ....;
  loop   .SetupPTE
  ;设置CR3 寄存器 和CR0 开启分页机制
  mov     eax,PDE_ORIGIN_ADDRESS
  mov     cr3,eax
  mov     eax,cr0;将CR0 PF位置位
  or      eax,0x80000000
  mov     cr0,eax
  jmp     short .SetupWating
  ;给CPU 一点延迟 让分页机制 生效
.SetupWating:
  nop
  nop
  ret

推荐文章 : https://www.cnblogs.com/alantu2018/p/9002441.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章