[055][x86汇编语言]16.3.2 使用高端1MB线性地址0x80000000~0x800FFFFF

学习笔记

《x86汇编语言:从实模式到保护模式》
https://www.jianshu.com/p/d481cb547e9f

将内核映射到高端地址

说明

  • 任务的4GB空间
任务的4GB包括:局部空间和全局空间
4G虚拟内存空间的线性地址


高端2G 是 0x8000 0000~0xFFFF FFFF 
用于 全局空间 用于指向内核的页表

低端2G 是 0x0000 0000~0x7FFF FFFF 
用于 局部空间 用于指向任务的页表

实现

  • 一、需要修改页目录
在内核的页目录表中,创建一个和 线性地址 0x 8000 0000
(高端2G空间起始线性地址 )相对应的目录项,并使它指向指定的页表。

对内核程序而言,内核程序的任务也是内核程序自己,
意味着 属于内核程序的页表 和 内核任务的页表 是同一个页表。
  • 二、需要修改与内核有关的段描述符
需要依次修改有关的段描述符
#  初始代码段
#  初始栈段
#  文本模式显存
#  公用例程段
#  内核数据段
#  内核代码段
#  GDT

效果

  • 完成上面的实现步骤,可以达到这样的效果:在任何任务内,如果段部件发出的线性地址高于或者等于0x8000 0000,指向和访问的就是全局地址空间,或者说内核

一、需要修改页目录

什么叫"页目录也是一个物理页"?

  • 对内存而言,数据是数据,代码也是数据,一切都只是数据;
  • 分页,就是按照4KB(4K Byte = 4096字节 = 0x1000 字节)为单位算作一个页;
  • 1个任务有且只有只需要1个页目录,页目录里是1024个页表的物理地址,1个物理地址需要4字节存储,可知,1个页目录的大小正好就是4KB,那么本质上就是内存中的某个物理页被拿来当做页目录了而已,这是标题的意思;
  • 1个页表,里面是1024个也页的物理地址,那么1个页表的大小也正好是4KB,所以在内存里也有某些物理页,成为了页表;
  • 能不能既是页目录又是页表?可以的。
1.对不同的任务而言,这完全就是同一块内存被反复覆盖刷新使用而已,
你把某个物理页当页目录,我把它当页表或者其他,
再顺理成章不过了,内存就是这样重复用的;

2.同一个任务呢?其实,也是可以的!
只要在页目录的某个表项填入页目录自己本身的物理地址,
使得经过由 
段部件生成线性地址→取高10位做页目录表偏移量→取得页表的物理地址时,
拿到的页表物理地址是页目录自己的物理地址,
再继续
取中10位做页表的偏移地址,
此时页表就是页目录,页目录就是页表。

举例说明

回顾:通过 段部件 以及 页部件 的 线性地址 转 物理地址 计算过程

  • 完全理解检测点16.1的计算过程,知道取高10位、取中10位以及取低12位的计算过程,才能理解下面的举例说明

检测点16.1 参见 https://www.jianshu.com/p/704044463b52

举例:前提是在页目录的最后一个表项已经填入了页目录自己的物理地址

填入页目录自己的物理地址过程见
https://www.jianshu.com/p/4bb9514c4500

图解过程取自 16.3.2 节


[055][x86汇编语言]16.3.2 使用高端1MB线性地址0x80000000~0x800FFFFF_第1张图片
使用线性地址访问和修改页目录表
  • 对应配书代码包,源码文件:c16_core.asm (第969~973行)
;在页目录内创建与线性地址0x80000000对应的目录项
mov ebx,0xfffff000                 ;页目录自己的线性地址 
mov esi,0x80000000                 ;映射的起始地址
shr esi,22                         ;线性地址的高10位是目录索引
shl esi,2
mov dword [es:ebx+esi],0x00021003  ;写入目录项(页表的物理地址和属性)
                                   ;目标单元的线性地址为0xFFFFF200
  • 0x0800的意义是什么?
0x00021003 是第一个页表的物理地址
在图解的最开始就已经被填入到了页目录的第一个表项之中

4G虚拟内存空间的线性地址
高端2G 是 0x8000 0000~0xFFFF FFFF
低端2G 是 0x0000 0000~0x7FFF FFFF


对于页目录而言
页目录最大4KB 也就是0x1000
从页目录表项的偏移量来看,
最后一个表项就是 0x0FFC~0x0FFF (占用4个字节)
而第一个表项是 0x0000~0x0003(占用4个字节)

为什么 高端2G和低端2G 切在了页目录偏移量 0x0800处
其实就是除以2   0x1000 ÷ 2 = 0x0800

代码最终的目的就是,
在页目录表偏移量0x0800处写上0x0002 1003(第一个页表的物理地址)


谁是因?
最根本的原因在于,高端2G 和 低端2G的五五开,
由此而来就是偏移量的五五开,0x0000~0x07FF 和 0x0800~0x0FFF
那么 低端2G 要用 页表,就在偏移量0x0000 处开始填写页表的物理地址
同时,高端2G 要用 页表,就需要在偏移量0x0800处填写相同的页表物理地址

0x0800由此而来.
  • 偏移量0x0800最后和谁结合?
处理器遵从的规矩是:你访问的是页目录表,
但却还要通过页目录表进行地址转换之后才能访问。

即,要符合 “从段部件发出线性地址 经过 取高10位 取中10位 
以及 取最后偏移量,最后经由页部件算出来的物理地址”,
这个物理地址才是 目标 页目录表偏移量0x0800所在的 物理地址。

其实也就是说,要注意到 0x0800最后是和谁结合?
恰恰就是 页目录 自己的物理地址!

为什么是这样?
对于 段部件 发出的线性地址 0xFFFFF200 而言,
取高10位,取到的是 0x3FF ,乘以4就是 0xFFC ,
这就是页目录表的最后一个表项;
取中10位,取到的仍旧是 0x3FF,乘以4还是0xFFC,
依旧是页目录表的最后一个表项;

段部件发出的线性地址,是我们精心设计的,
只要是 0x FFFF??? 前20位全是1,
那么,取高10位 和 取中10位 就一定是一样的值。

加之,前提是在页目录的最后一个表项已经填入了页目录自己的物理地址,
所以,就能找到 页目录偏移量0x0800所在的物理地址。

说白了

  • 整个通过 段部件 和页部件 从 线性地址 到 物理地址 的过程没有动,动的只是 一开始的线性地址,程序员知道,后面的转换过程,所以在凑那个0x0800,为什么不一开始就给一个偏移量0x0800,是因为高位必须是0xffff,这样才能找到最后一个页目录表项,经过移位恰恰还有个0x200的偏移量,刚好乘以4就能得到0x800不然我估计作者会显式地赋值,因为必须遵从原理,让处理器固件走一趟取高10位,中10位的操作,把自己的地址写在自己身上,本质上取中10位的偏移量还是访问自己了。

结果

  • 不同的线性地址,可以指向同一个页表!注意是页表!不是页!页的物理地址填写早在映射操作之前就完成了,就不改变了。

二、需要修改与内核有关的段描述符

  • 修改:与内核有关的段描述符统统要改
从数学角度上来说就是 全部加上 0x8000 0000
因为高端2G就是从线性地址 0x8000 0000 开始的


;下面这些将GDT中的段描述符映射到线性地址0x80000000

;先取出GDT的物理地址 用来访问GDT
sgdt [pgdt]
mov ebx,[pgdt+2]

;依次修改
;#  初始代码段
or dword [es:ebx+0x10+4],0x80000000
;#  初始栈段
or dword [es:ebx+0x18+4],0x80000000
;#  文本模式显存
or dword [es:ebx+0x20+4],0x80000000
;#  公用例程段
or dword [es:ebx+0x28+4],0x80000000
;#  内核数据段
or dword [es:ebx+0x30+4],0x80000000
;#  内核代码段
or dword [es:ebx+0x38+4],0x80000000

;最后修改GDT本身描述符
add dword [pgdt+2],0x80000000 ;GDTR也用的是线性地址
lgdt [pgdt]

GDT布局参考 https://www.jianshu.com/p/4a420617c5db

  • 刷新 :使段寄存器生效,通过重新加载一次对应的段描述符,使得段寄存器CS SS DS的高速缓存刷新
         jmp core_code_seg_sel:flush        ;刷新段寄存器CS,启用高端线性地址 
                                             
   flush:
         mov eax,core_stack_seg_sel
         mov ss,eax
         
         mov eax,core_data_seg_sel
         mov ds,eax
  • 结果:旁边的地址都是线性地址
    [055][x86汇编语言]16.3.2 使用高端1MB线性地址0x80000000~0x800FFFFF_第2张图片
    映射到高端地址后的系统核心布局

举例说明:段部件发出的线性地址高于或者等于 0x8000 0000

[055][x86汇编语言]16.3.2 使用高端1MB线性地址0x80000000~0x800FFFFF_第3张图片
举例:线性地址 0x80007E00 与 线性地址 0x0000 7E00 如何指向指向同一个页表.png
  • 线性地址0x80007E00 与 线性地址 0x0000 7E00 如何指向指向同一个页表
  • 加上 0x8000 0000的操作,所影响的是只是取高10位计算出来的页目录偏移量,后面从取中10位以及最后低12位偏移量都是不变的;
  • 不要觉得 0x200 * 4 = 0x800 这里乘以4很奇怪,要回想起来页目录表存的是页表的物理地址,一个物理地址是一个双字,占用4个字节,偏移量0x800起始偏移地址,从0x800开始的0x800 0x801 0x802 0x803四个内存空间一起才是表示一个物理地址,所以是要乘以4的,而且这是处理器固件的设计,是焊死在芯片里的设计,不是程序员的编程;

你可能感兴趣的:([055][x86汇编语言]16.3.2 使用高端1MB线性地址0x80000000~0x800FFFFF)