序言(废话) : 在看书的过程中发现一开始不是很能理解pmtest8的目的,以及书上说得很抽象..于是在自己阅读过源代码后,将一些自己的心得写在这里。

  正文 :

  讲解顺序依然按照书上贴代码的顺序来。但是是几乎逐句解释的。可能会稍微有点啰嗦。废话就不多说了直接贴代码。

LABEL_DESC_FLAT_C:  Descriptor ,        0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G
LABEL_DESC_FLAT_RW: Descriptor , 0fffffh, DA_DRW|DA_LIMIT_4K ; 0~4G
SelectorFlatC       equ    LABEL_DESC_FLAT_C - LABEL_GDT                
SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT

  显然,两个分别是 FLAT_C 和  FLAT_RW 的描述符和选择子。

  问题 : 为什么要有这两个东西?

  解释 : FLAT_C是用来执行的非一致性32位代码段,粒度为4k,也就是 limit(段限长) = (0xfffff + 1)  * 4k = 4G,FLAT_RW 是用来修改数据的,因为需要利用这个描述符的权限(可写)来将代码写入到目的地(这个目的地允许在 0 - 4G区间内)。之所以要分两个选择符,是防止在执行的时候修改代码(所以FLAT_C不能给写的权限),但是又必须在执行之前进行复制,所以一定要有一个入口能提供写入的方式,于是设置两个描述符来进行。这样既安全又有章法。

SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小
div ebx
mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数
test edx, edx
jz .no_remainder
inc ecx ; 如果余数不为 0 就需增加一个页表
.no_remainder:
mov [PageTableNumber], ecx ; 暂存页表个数 ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. ; 首先初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase0 ; 此段首地址为 PageDirBase0
xor eax, eax
mov eax, PageTblBase0 | PG_P | PG_USU | PG_RWW
.: ; es:edi 初始等于 PageDirBase0 (当前页目录表项), eax 初始基地址等于 PageTblBase0
stosd
add eax, ; 为了简化, 所有页表在内存中是连续的.
loop . ; 再初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
mov edi, PageTblBase0 ; 此段首地址为 PageTblBase0
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.: ; es:edi 初始等于 PageTblBase0 (当前页表项), eax = 0 (线性地址 = 物理地址)
stosd
add eax, ; 每一页指向 4K 的空间
loop . mov eax, PageDirBase0
mov cr3, eax
mov eax, cr0
or eax, 80000000h
mov cr0, eax
jmp short .
.:
nop ret

  这段代码我加注了两句注释 分别在 .1 和 .2 这两个标签那行,其实这里和之前的setPaging并没有很大的区别,需要注意的就是 这里的 页目录表 的地址是  PageDirBase0, 页表的地址是PageTblBase0,强调这点的原因在于之后的  PSwitch 这个函数中则是 PageDirBase1 和 PageTblBase1。也就是说实际上数据中有两个页面管理的数据结构(页目录表和页表合起来相当于一个管理页面的数据结构)。

 PagingDemo:
mov ax, cs
mov ds, ax
mov ax, SelectorFlatRW ; 设置es为基地址为0的可读写的段(便于复制代码)
mov es, ax push LenFoo
push OffsetFoo
push ProcFoo ; 00401000h
call MemCpy
add esp, push LenBar ; 被复制代码段(但是以ds为段基址)的长度
push OffsetBar ; 被复制代码段(但是以ds为段基址)的段偏移量
push ProcBar ; 目的代码段的物理空间地址 00501000h
call MemCpy
add esp, push LenPagingDemoAll
push OffsetPagingDemoProc
push ProcPagingDemo ; [es:ProcPagingDemo] = ProcPagingDemo = 00301000h
call MemCpy
add esp, mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov es, ax call SetupPaging ; 启动分页
; 当前线性地址依然等于物理地址
call SelectorFlatC:ProcPagingDemo
call PSwitch ; 切换页目录,改变地址映射关系
call SelectorFlatC:ProcPagingDemo ret

  在这里首先要说明的是 MemCpy函数,这个函数有三个参数分别表示 :

   1)被复制段(但是以ds为段基址)的 长度 
   2)被复制段(但是以ds为段基址)的 段偏移量
   3)目的地的物理空间地址(之所以说是物理空间是因为当前线性地址等于物理地址,以es为段基址,但是es的段基址为0)
功能则是 将被复制段 的数据复制 参数1)的长度字节 去目的地去(简单说就是利用三个参数复制数据)
我们可以知道的是在上面代码中三次调用 MemCpy 都没有进入分页模式,也就是说当下线性地址等于物理地址。那么根据我上面的注释就可以知道三个代码分别复制到哪里去了。
之后就是恢复数据段(之前将ds = cs,是为了复制代码),然后启动分页(上面已经讲了),然后启动分页后当前线性地址依然等于物理地址。
这个时候第一次调用 call SelectorFlatC:ProcPagingDemo,也就是访问的线性地址为 00301000h,物理地址也是 00301000h的代码(之前移动过去的)。
 下面这段代码就是被移动到00301000h的代码,这段代码只做了一件事那就是调用 [cs:LinearAddrDemo]的代码,但请注意,由于 call SelectorFlatC:ProcPagingDemo
所以此时的 cs = SelectorFlatC,也就是说段基址等于0,于是实际上这段代码的功能就是访问 物理地址为00401000h处的代码。
PagingDemoProc:
OffsetPagingDemoProc equ PagingDemoProc - $$
mov eax, LinearAddrDemo
call eax ; 未开始PSwitch前, eax = ProcFoo = 00401000h (cs 的段基址 = 0)
retf
LenPagingDemoAll equ $ - PagingDemoProc

  而物理地址00401000h处就是ProcFoo的代码(第一次调用MemCpy拷贝的代码)。被拷贝的代码如下

foo:
OffsetFoo equ foo - $$
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'F'
mov [gs:(( * + ) * )], ax ; 屏幕第 17 行, 第 0 列。
mov al, 'o'
mov [gs:(( * + ) * )], ax ; 屏幕第 17 行, 第 1 列。
mov [gs:(( * + ) * )], ax ; 屏幕第 17 行, 第 2 列。
ret
LenFoo equ $ - foo

  功能很明显就是现实一个字符串 Foo而已。

总结第一次分页后的动作:

  就是拷贝三份代码分别到ProcFoo, ProcBar, ProcPagingDemo 处(这四个都是物理内存哦,并且后面因为段基址是0(FLAT_C 段基址)于是很容易地就访问到了物理地址)。然后开启分页模式(其实几乎没什么影响 因为仍然和分段一样 线性地址 = 物理地址)。然后调用 被拷贝的函数 ProcPagingDemo ,ProcPagingDemo 函数调用 ProcFoo函数,显示字符 "Foo"然后两次返回(ret)。

修改页表后 : call PSwitch

被调用代码如下 :

 PSwitch:
; 初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase1 ; 此段首地址为 PageDirBase1
xor eax, eax
mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW
mov ecx, [PageTableNumber]
.: ; es:edi 初始等于 PageDirBase1 (当前页目录表项), eax 初始基地址等于 PageTblBase1
stosd
add eax, ; 为了简化, 所有页表在内存中是连续的.
loop . ; 再初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE个数 = 页表个数 * 1024
mov edi, PageTblBase1 ; 此段首地址为 PageTblBase1
xor eax, eax
mov eax, PG_P | PG_USU | PG_RWW
.: ; es:edi 初始等于 PageTblBase1 (当前页表项), eax 初始基地址等于 0(线性地址等于物理地址)
stosd
add eax, ; 每一页指向 4K 的空间
loop . ; 在此假设内存是大于 8M 的
; 下列代码将LinearAddrDemo所处的页表的相对第一个页表的偏移地址放入ecx中
mov eax, LinearAddrDemo
shr eax,
mov ebx, ; (LinearAddrDemo / 4M)表示第几个页表
mul ebx ; 第几个页表 * 4k (1024(一个页表项的数量) * 4(一个页表项的字节))
mov ecx, eax ; 也就是对应页表的偏移地址 ; 下列代码将LinearAddrDemo所处的页表项相对第一个页表项的偏移地址放入eax中
mov eax, LinearAddrDemo
shr eax, ; LinearAddrDemo / 4k,表示第几个页表项
and eax, 03FFh ; 1111111111b (10 bits) ; 取低10位,也就是余下的零散页表项(一个页表有2^10个页表项)
mov ebx,
mul ebx ; * 4 表示的是具体偏移字节数
add eax, ecx ; eax = (((LinearAddrDemo / 2^12) & 03FFh) * 4) + (4k * (LinearAddrDemo / 2^22)) add eax, PageTblBase1 ; 第一个页表的第一个页表项
mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW mov eax, PageDirBase1
mov cr3, eax
jmp short .
.:
nop ret

  在这里我加了几个比较重要的注释分别在第 9, 22, 28,35处。

  这段代码做了什么?

  首先是设置页面管理的数据结构(页表和页目录表),但是需要注意的是,这里设置页表和页目录表除了不是之前的页面管理结构之外,其实内容是差不多的,也就是说当前(第25行)这里的状态也是 线性地址 = 物理地址 !!!

 但是在第27行做了一个操作,就是将LinearAddrDemo对应的 页表项的地址(00401000h) 换成了 ProcBar(00501000h) 的地址。(具体如何实现的请看27-45行我写的注释)。
  在做完这些之后就返回第二次执行 call SelectorFlatC:ProcPagingDemo 了,在这个时候 cs = SelectorFlatC (段基址等于0), eip = ProcPagingDemo = 00301000h,也就是说访问了
线性地址 = 物理地址 = 00301000h 处,然后开始调用
 mov    eax, LinearAddrDemo
call eax 但是第二次调用时这里已经被修改,当call eax 时候,跳转的线性地址应该是 0:00401000h,但是访问的物理地址却是 0:00501000h
于是便调用了 ProcBar 段的代码,而这段的代码是第二次调用MemCpy时候复制到物理地址 0:0x501000h 的。被复制的具体代码是:
bar:
OffsetBar equ bar - $$
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'B'
mov [gs:(( * + ) * )], ax ; 屏幕第 18 行, 第 0 列。
mov al, 'a'
mov [gs:(( * + ) * )], ax ; 屏幕第 18 行, 第 1 列。
mov al, 'r'
mov [gs:(( * + ) * )], ax ; 屏幕第 18 行, 第 2 列。
ret
LenBar equ $ - bar
也就是显示一个字符串 "Bar", 然后返回到PagingDemo的最后一句 ret,再次返回。于是这段代码也就结束了。
第二次代码是如何实现调用 ProcBar的?
  通过将线性地址 = PaocFoo (00401000h)对应的页表项的地址值给修改成了 PaocBar(00501000h)的物理地址,于是从 00401000h 的线性地址 映射到 00501000h的物理地址上去了,
但是其实其他地方(除了这个页之外)的线性地址 = 物理地址依然成立。也是上面这段代码很小,一定是小于 4k(一页的大小),于是只需要修改一个页表项就可以了!
 

x86汇编分页模式实验 --《ORANGE'S一个操作系统的实现》中 pmtest8.asm解析的更多相关文章

  1. 《Orange'S:一个操作系统的实现》笔记(一)

    感觉自己对于操作系统始终没有一个清楚的概念,尤其最近困扰于实模式.保护模式以及寻址方式等一些概念.转而一想,所有的程序,最终都是操作的计算机资源,需要和操作系统打交道,所以操作系统有必要深入了解一下. ...

  2. 配置《Orange's一个操作系统的实现》环境心得

    <Orange>这本书开篇第一章就做了一个实例,编写了一段引导扇区的代码,但是引导介质仍然采用了已被淘汰多年的软盘.在经历了两天的痛苦查找后终于找到了最方便的解决办法,在此做一下记录,希望 ...

  3. 《Orange’s 一个操作系统的实现》1.搭建操作系统开发环境

    书中给出了两种环境:windows和linux,平台选择根据自己喜好.本人这里选择ubuntu10.04+virtualbox作为开发平台. 1.下载.安装VirtualBox     http:// ...

  4. oslab oranges 一个操作系统的实现 实验三 认识保护模式(二):分页

    实验目的: 掌握内存分页机制 对应章节:3.3 实验内容: 1.认真阅读章节资料,掌握什么是分页机制 2. 调试代码,掌握分页机制基本方法与思路 – 代码3.22中,212行---237行,设置断点调 ...

  5. oslab oranges 一个操作系统的实现 实验五 让操作系统走进保护模式

    实验目的: • 如何从软盘读取并加载一个Loader程序到操作 系统,然后转交系统控制权 • 对应章节:第四章 实验内容: 1. 向软盘镜像文件写入一个你指定的文件,手 工读取在磁盘中的信息 2. 在 ...

  6. oslab oranges 一个操作系统的实现 实验二 认识保护模式

    https://github.com/yyu/osfs00 实验目的: 理解x86架构下的段式内存管理 掌握实模式和保护模式下段式寻址的组织方式. 关键数据结构.代码组织方式 掌握实模式与保护模式的切 ...

  7. oslab oranges 一个操作系统的实现 实验四 认识保护模式(三):中断异常

    实验目的: 理解中断与异常机制的实现机理 对应章节:第三章3.4节,3.5节 实验内容: 1. 理解中断与异常的机制 2. 调试8259A的编程基本例程 3. 调试时钟中断例程 4. 建立IDT,实现 ...

  8. 寄存器理解 及 X86汇编入门

    本文整理自多材料源,感谢原址分享,请查看末尾Url I, 汇编语言分类: 汇编语言和CPU息息相关,但是不能把汇编语言完全等同于CPU的机器指令.不同架构的CPU指令并不相同,如x86,powerpc ...

  9. 对X86汇编的理解与入门

    本文描述基本的32位X86汇编语言的一个子集,其中涉及汇编语言的最核心部分,包括寄存器结构,数据表示,基本的操作指令(包括数据传送指令.逻辑计算指令.算数运算指令),以及函数的调用规则.个人认为:在理 ...

随机推荐

  1. Helm神器,让管理Kubernetes像yum安装包一样简单

    目录 一.什么是Helm 二.安装 1.安装helm客户端 2.安装Tiller 3.创建服务端 4.给Tiller授权 5.为 Tiller 设置帐号 6.验证Tiller是否安装成功 三.Helm ...

  2. 拨云见日,彻底弄清楚Java日志框架 log4j, logback, slf4j的区别与联系

    log4j 以及 logback, slf4j 官网 日志框架的困惑 作为一个正常的项目,是必须有日志框架的存在的,没有日志,很难追踪一些奇奇怪怪的系统问题. 但是,我们经常在项目的依赖中,见到奇奇怪 ...

  3. Java常见的异常,Java运行时异常和一般异常的区别

    Java常见的异常,Java运行时异常和一般异常的区别 异常和错误二者的不同之处: Exception: 1.可以是可被控制(checked,检查异常) 或不可控制的(unchecked,非检查异常) ...

  4. 设计模式(十五)Facade模式

    Facade模式可以为相互关联在一起的错综复杂的类整理出高层接口,可以让系统对外只有一个简单的接口,而且还会考虑到系统内部各个类之间的责任关系和依赖关系,按照正常的顺序调用各个类. 还是先看一下示例程 ...

  5. WebApi HttpUtils

    public class HttpUtils { private static final String TAG = "uploadFile"; * ;//超时时间 /** * 通 ...

  6. BFM模型介绍及可视化实现(C++)

    BFM模型介绍及可视化实现(C++) BFM模型基本介绍 Basel Face Model是一个开源的人脸数据库,其基本原理是3DMM,因此其便是在PCA的基础上进行存储的. 目前有两个版本的数据库( ...

  7. URL中文参数,JSON转换,PHP赋值JS

    var jsonProps = { "dispMode":dispMode, "autoRun":autoRun, "clientPath" ...

  8. 2018.8.15 python 中的sorted()、filter()、map()函数

    主要内容: 1.lambda匿名函数 2.sorted() 3.filter() 4.map() 5.递归函数 一. lambda匿名函数 为了解决一些简单的需求而设计的一句话函数 # 计算n的n次方 ...

  9. Android H5混合开发(3):原生Android项目里嵌入Cordova

    前言 如果安卓项目已经存在了,那么如何使用Cordova做混合开发? 方案1(适用于插件会持续增加或变化的项目): 新建Cordova项目并添加Android平台,把我们的安卓项目导入Android平 ...

  10. 【BZOJ4720】【UOJ262】【NOIP2016】换教室

    Description 对于刚上大学的牛牛来说,他面临的第一个问题是如何根据实际情况申请合适的课程. 在可以选择的课程中,有 2n 节课程安排在n个时间段上.在第 i(1≤i≤n)个时间段上,两节内容 ...