学习笔记
《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 节
使用线性地址访问和修改页目录表
- 对应配书代码包,源码文件:
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]
- 刷新 :使段寄存器生效,通过重新加载一次对应的段描述符,使得段寄存器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
-
结果:旁边的地址都是线性地址
映射到高端地址后的系统核心布局
举例说明:段部件发出的线性地址高于或者等于 0x8000 0000
举例:线性地址 0x80007E00 与 线性地址 0x0000 7E00 如何指向指向同一个页表.png- 线性地址
0x80007E00
与 线性地址0x0000 7E00
如何指向指向同一个页表 - 加上
0x8000 0000
的操作,所影响的是只是取高10位计算出来的页目录偏移量,后面从取中10位以及最后低12位偏移量都是不变的; - 不要觉得
0x200 * 4 = 0x800
这里乘以4
很奇怪,要回想起来页目录表存的是页表的物理地址,一个物理地址是一个双字
,占用4个字节
,偏移量0x800
是起始偏移地址,从0x800
开始的0x800 0x801 0x802 0x803
这四个内存空间一起才是表示一个物理地址,所以是要乘以4
的,而且这是处理器固件的设计,是焊死在芯片里的设计,不是程序员的编程;
网友评论