学习好资料 欢迎下载
1
org 07c00h ;这是告诉编译器将这段程序加载到内存偏移地址0x7c00处 movax,cs movds,ax moves,ax callDispStr jmp $ DispStr:
movax,BootMessage movbp,ax mov cx,16
mov ax,01301h mov bx,000ch mov dl,0 int 10h ret
BootMessage: db \times 510-($-$$) db 0 dw0xaa55
这是第一章的程序,从中可以看出起始位置是07c00h,程序的结束标志是0xaa55。 $表示当前行被汇编后的地址。
$$表示一个节的开始处被汇编后的地址。
那么$-$$表示本行距离程序开始处的相对距离。 1.对于程序的说明
实际上以上程序只是一个引导扇区(Boot Sector),不是完整的OS,当计算机电源被打开时,它会加电自检(POST),然后寻找启动盘,如果是从软盘启动,计算机就会检查软盘的0面0磁道1扇区,如果发现它以0xaa55结束,并且包含了少于512字节的执行码,那么BIOS认为它是一个引导扇区。
一旦发现了引导扇区,就将这512字节放入内存地址0000:7c00处,然后跳转并且移交控制器。
2.制作软盘过程
本身这段程序是boot.asm,是汇编程序,然后使用工具nasm将其转换为boot.bin, 然后使用软盘绝对扇区读写工具将这个文件写到一张空白软盘的第一个扇区。
(.bin文件的说明:*.bin是Foxbase定义的一种特殊的可执行文件,它同DOS的.com文件类似,区别在于.com文件是从100H开始运行的,而.bin从0开始,.bin的调用参数保存在DS:BX中。如果在Foxbase中,你可以使用Call命令调用。如果你在VFP中,将无法直接调用,只能使用Debug反汇编,分析其功能,然后重新编写.) 3.方括号[]的使用
在NASM中,任何不被方括号[]括起来的标签或变量名都被认为是地址,访问标签中的内容必须使用[]。
3.1认识保护模式
学习好资料 欢迎下载
1.对于这一节的程序的理解 数据区:
先是定义了一些描述符(LABEL_GDT,LABEL_DESC_CODE32,LABEL_DESC_VIDEO),
然后定义了GdtPtr,它是一个6字节的数据结构,前两字节表示GDT的界限(即描述符的界限),后4字节表示GDT的基地址(GdtPtr就是用来描述GDT的,会用一个寄存器GDTR来保存它的值)。然后定义两个选择子,分别指向已定义的描述符。 代码区:
初始化32位代码段描述符,为加载GDTR作准备,加载GDTR: lgdt [GdtPtr],然后是关中断:cli,(保护模式下中断处理的机制是不同的,不关中断会出错)打开地址线A20,然后将cr0的PE位置为1(0:实模式,1:保护模式),准备切换到保护模式,跳转到保护模式中jmpdword SelectorCode32:0
2.Descriptor是一个宏,它是一个8字节的数据结构。三个描述符组成了一个结构数组。 3.[BITS 16]说明这是一个16位的代码段,而[BITS 32]是32位的代码段。 程序中的知识点 要搞清楚的有:
GDT是一个索引,指向一个数据结构的表项。GDT作用:提供段式存储机制。 程序对GDT做了什么? jmp SelectorCode32:0? GDT
1.在保护模式下,CPU有着巨大的寻址能力(保护模式下为4G,实模式下为1M),并为强大的32位操作系统提供了更好的硬件保障。
2.在保护模式下,段值仅仅变成了一个索引,这个索引指向一个数据结构的一个表项,表项中定义了段的起始地址、界限、属性等内容。这个数据结构就叫GDT,表项就叫描述符。 3.描述符分为代码段和数据段描述符,还可以分为系统段描述符和门描述符。 4.选择子
TI位 0:GDT,1:LDT
当TI和RPL都为0时,选择子就变为了偏移。 5.\段:偏移\经过段机制转化为“线性地址”,而不是物理地址。 JMP
1.对于cr0的设置是实模式和保护模式的关键
2.jmp dword SelectorCode32:0的作用是将描述符DESC_CODE32对应的段的首地址。
3.为什么那儿有一个dword呢?(没有dword的话编译出来是16位的代码,而目标地址是32位的。)
进入保护模式的主要步骤: 1.准备GDT
2.用lgdt加载gdtr 3.打开A20
4.设置cr0的PE位 5.跳转,进入保护模式 描述符的属性
1.P位——存在位。P=1,表示在段内存中存在;P=0则在内存中不存在。 2.DPL位——描述符特权级。有0,1,2,3级,数字越小级别越大
3.S位——指明描述符是数据段/代码段(s=1),还是系统段/门描述符(s=0)
3.2保护模式进阶
学习好资料 欢迎下载
在这一节中,首先在上一节程序的基础上改写: (1)跳入保护模式之后再次跳回实模式 (2)读写大地址内存 对于读大地址内容,它是先读,然后再写,然后再读,以此来判断是否可以读写大地址内存。 (注意:程序中调用子程序时要保存edi的值,要对其进行压栈,所以要用到堆栈,那么在程序中也要有堆栈的描述符和堆栈的选择子。)
从保护模式跳转到实模式时要复杂一些(从实模式到保护模式只要一个跳转就可以了),我们不能直接从32位的代码段中返回实模式,只能从16位代码段中返回,因为无法从32为代码段返回时cs高速缓存寄存器中的属性符合实模式的要求。所以增加一个Normal描述符,在返回实模式前把对应的选择子加载到ds、ss和es. 跳回实模式之后要做的事情:用Normal描述符的选择子加载到ds、ss和es,值cr0的PE=0,关闭A20地址线,开中断STI。 LDT:局部描述符表
1.LDT与GDT差不多,一个是全局的,一个是局部的。在代码中也要有一个LDT的描述符和选择子,它的初始化有所不同,有两步:(1)初始化LDT在GDT中的描述符,(2)初始化LDT中的描述符。其中,第二步与GDT类似,多了一个在GDT中定义LDT。 2.LDT与GDT的区别就在于选择子中TI位(0为GDT,1为LDT) 3.在使用LDT时需要先用lldt指令加载ldtr(lgdt加载gdtr) 4.使用LDT的目的:多任务处理。 5.保护模式“保护”的含义
(1)描述符中的段基址和段界限定义了一个段的范围,对超越段界限之外的地址的访问是被禁止的,这是对段的一种保护。
(2)有点复杂的段属性作为对一个段各个方面的定义规定和限制了段的行为和性质 以上两点是静态的
(3)在涉及特权级的每一步中,处理器都会对CPL,DPL和RPL等内容进行比较,这种比较是动态的。 特权级概述
1.常规保护模式错误
2.将Level0叫做内核,Level1、Level2叫做服务,Level3叫做应用程序。 3.CPL、DPL、RPL
CPL 是当前执行的程序或任务的特权级。通常情况下,CPL等于代码所在段的特权级, 当程序转移到不同特权级的代码段时,处理器将改变CPL。当处理器访问一个与CPL特权级不同的一致代码段时,CPL不会被改变。
DPL 表示段或者门的特权级,下面是各种类型的段或者门的情况 数据段:高级->低级,相同级别之间 非一致代码段:相同级别之间 调用门:与数据段一致
一致代码段和通过调用门访问的非一致代码段:低级->高级,相同级别之间 TSS(Task-State Stack:任务状态栈):与数据段一致 RPL 通过选择子的第0位和第1位表现出来的。操作系统过程往往用RPL来避免低特权级应用程序访问高特权级段内的数据。 4.不同特权级代码之间的转移
程序从一个代码转移到另一个代码之前,目标代码的选择子将会被加载到cs中。通常使用jmp和call指令来实现转移,转移分为两大类:(1)直接转移(2)间接转移
学习好资料 欢迎下载
特权级转移
1.如果目标是非一致代码段,要求CPL必须等于目标代码段的DPL,同时要求RPL小于等于DPL。如果目标是一致代码段,要求CPL大于或者等于目标段的DPL,RPL此时不做检查。这种直接转移是非常有限的,为了实现不同特权级之间的转移,可以使用门描述符或者TSS。 2.门是一种描述符,它由一个选择子和一个偏移所指定的线性地址。 门的种类:调用门,中断门,陷阱门,任务门。 作用:实现不同特权级之间的转换。
在原程序的基础上做的一些工作:定义门描述符及其选择子,初始化描述符,使用call指令来实现跳转到已经定义好的通过门描述符能够跳转到的目标段。 门实际上是一个入口地址,只不过增加了若干的属性而已。 3.门转移总结:
通过调用门和call指令,可以实现从低特权级到高特权级的转移,无论目标代码是一致的还是非一致的。我们的目标是从低到高,在从高到低,那么下面的任务就是如何从高到低了。 4.长的和短的jmp和call的区别:对jmp而言,短跳转对应段内,长跳转对应段间; call要复杂一些,因为call指令会影响堆栈。 5. 在使用call指令时,由于某些原因堆栈发生了切换,也就是说call指令执行前后的堆栈已经不是同一个。Intel提供了一种机制,将堆栈A的诸多内容赋值到堆栈B中。事实上,由于每一个任务最多可能在4个特权级间转移,所以,每个任务实际上需要4个堆栈,此时要用到一种数据结构,即TSS(Task-State Stack)。
6.综合:使用调用门的过程实际上分为两个部分,一部分是从低特权级到高特权级,通过调用门和call指令来实现;另一部分则是从高特权级到低特权级,通过ret指令来实现。 7.用程序实现从ring0到ring3
在这里我们需要在原程序的基础上增加ring3的代码段描述符和堆栈段描述符,并添加响应的选择子,
做好工作后,执行一下程序: push SelectorStack3 push TopOfStack3
push SelectorCodeRing3 push 0 retf
为什么会进入ring3呢?
原来程序是执行在ring0的,当使用retf指令后,堆栈将被自动切换到ring3的堆栈段,执行ring3的代码,也就完成了从高特权级项低特权级的转换。
8.从高特权级项低特权级的转换和从低特权级项高特权级的转换都需要用到TSS。
3.页式存储
1.页的概念
页是一块内存,在80386中,大小固定为4K。在Pentium中,大小可以是2MB或4MB,并且可以访问多余4GB的内存。
2.逻辑地址、线性地址、物理地址
在未打开分页机制的时候,逻辑地址经过分段机制直接转换成物理地址。 打开分页机制后,逻辑地址经过分段机制先是转换成线性地址, 线性地址再经过分页机制转换成物理地址。 3.分页的目的
学习好资料 欢迎下载
实际上使用分段机制已经提供了很好的保护机制, 分页的目的在于实现虚拟存储器。 用代码启动分页机制 代码
PageDirBaseequ 200000h ; 页目录开始地址: 2M PageTblBaseequ 201000h ; 页表开始地址: 2M+4K 。。。
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, DA_DRW;Page Directory
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, DA_DRW|DA_LIMIT_4K;Page Tables 。。。
SelectorPageDirequ LABEL_DESC_PAGE_DIR - LABEL_GDT SelectorPageTblequ LABEL_DESC_PAGE_TBL - LABEL_GDT 。。。
; 启动分页机制 -------------------------------------------------------------- SetupPaging:
; 为简化处理, 所有线性地址对应相等的物理地址. ; 首先初始化页目录
mov ax, SelectorPageDir ; 此段首地址为PageDirBase moves, ax
movecx, 1024 ; 共 1K 个表项 xoredi, edi xoreax, eax
moveax, PageTblBase | PG_P | PG_USU | PG_RWW .1: stosd
addeax, 4096 ; 为了简化, 所有页表在内存中是连续的. loop .1
; 再初始化所有页表 (1K 个, 4M 内存空间) mov ax, SelectorPageTbl ; 此段首地址为PageTblBase moves, ax
movecx, 1024 * 1024 ; 共 1M 个页表项, 也即有 1M 个页 xoredi, edi xoreax, eax
moveax, PG_P | PG_USU | PG_RWW .2: stosd addeax, 4096 ; 每一页指向 4K 的空间 loop .2
moveax, PageDirBase mov cr3, eax moveax, cr0
oreax, 80000000h mov cr0, eax jmp short .3