这个时候选择子已经被赋值了,指定了他们指向GDT中的哪个了哈哈哈,但是那个LABEL_DESC_CODE32这个段描述符还没有赋值哦
先定义有三个描述符的GDT 然后是GDT的长度
然后是GdtPtr 就是放到gdtr寄存器里的东西,6字节!dd是4字节这他妈的base还是零?在16位代码那里要把这个改变不信你看下面gdtr结构示意图 dw: 俩字节两个选择子,定义成了地址! 为啥是那样定义的呢?这两个选择子实际上就是偏移量啊,以后可以从选择子上面找到描述符啊哈哈哈哈。这里用的和真是的选择子不一样,这里估计就是为了仿真把!看见没,真正的选择子还要有TI和RPL呢!
把label_SEG_CODE32地址放到eax
也就是32位程序的开始地址放到eax中!接着把ax内容放到[LABEL_DESC_CODE32+2]
ax是eax 32位寄存器的低16位!!
这里的word应该是2个字节了吧
下面的右移动16位就是证据
上面的代码就是为了初始化描述符的base字段!你看base是分成三部分的对吧,分别是2字节,1字节,1字节!!
因为这个选择子和描述符是在保护模式的时候用到的,所以你需要先在实模式的时候将他们设置一下,!!
LABEL_DESC_CODE32是代码段的描述符地址啊xor异或xor eax,eax,那不就是把eax清零吗!!SHL逻辑左移指令 shift logical left第三段跳到实模式 实现实模式到保护模式的转换
这个程序被编译成了COM文件,最简单的二进制文件。 意味程序执行时的内存映像和二进制文件映像是一样的 上面的程序中secton没什么实质作用 不定义它们,从执行结果来看也是一样的(编译出来的二进制有微小差别) 定义它们只是使代码结构上清晰,且后面我们对这个程序渐渐扩展的时候,它还有点妙用
[SECTION.gdt]这个段Descriptor是在 pm.inc中定义的宏: DB定义字节类型变量,一个字节数据占1个字节单元,读完一个,偏移量加1
DW定义字类型变量,一个字数据占2个字节单元,读完一个,偏移量加2
DD定义双字类型变量,一个双字数据占4个字节单元,读完一个,偏移量加4
上面的图片是一个宏,这特妈不就是段描述符吗。8个字节啊!!
这个宏表示的不是一段代码,而是个数据结构,大小8字节
并列有3个 Descriptor,看上去是个结构数组,这个数组的名字叫做GDT
数组的名字叫GDT,从何体现?
GdtLen是GDT长度GdtPtr 前2字节是GDT长度,后4字节GDT的基地址为啥定义这个呀!因为把他们放在gdtr寄存器哦!
到一个代码段
[BITS 16]指明: 是16位代码段
这段修改一些GDT中的值
然后执行一些不常见指令最后通过jmp跳转这一句将“真正进入保护模式”
实际它将跳转到第三个section,即[ SECTIONS32]中,这个段是32位的,执行最后一小段代码。这段代码看上去是往某个地址处写入了2字节,然后无限循环首先按照注释中所说的办法编译:然后在 Virtual PC中按第2章2.5节的方法将包含 pmtestl. com的文件夹共享,且运行它,结果如图3-1 打印了一个红色的字符P,然后再也不动程序的最后一部分代码中写入的2字节是写进显存
程序定义叫做GDT的数据结构。16位代码进行了一些与GDT有关的操作。程序最后跳到32位代码中做了一点操作显存的工作
GDT是什么?干什么用的?程序对GDT做了什么?jmp Selectorcode32:0跟从前用过的jmp不同?
不打算全面介绍保护模式的课程,本着够用原则,不涉及保护模式的所有内容,只要能自由地编写操作系统代码就足够V86是保护模式的一部分,如果你不想在自己的操作系统中支持16位程序,你可能永远都不需要知道它的实现方法,学习它简直是浪费
Intel8086是16位的CPU,有16位的寄存器、16位的数据总线及20位的地址总线和1 MB寻址能力。地址是由段和偏移两部分组成的,物理地址=段值( Segment)×16+偏移( offset) 段值和偏移都是16位
16为数据总线是指的是外部数据总线吧
80386开始, Intel家族的CPU进入32位80386有32位地址线,寻址空间达4GB。单从寻址这方面说,使用16位寄存器的方法已经不够用了。这时候,我们需要新的方法来提供更大的寻址能力。 当然,保护模式的优点不仅仅在这一个方面。
实模式下,16位寄存器需用“段:偏移”才能达到IMB的寻址能力有32位寄存器,一个寄存器就可寻址4GB,从此段值就被抛弃?没有,地址仍用“SEG:OFFSET”表示,不过保护模式下“段”的概念发生变化实模式下,段值还可看做地址一部分,段值为XXXXH表示以 XXXXOH开始的一段内存保护模式下,段值仍由原来16位的cs、ds等寄存器表示, 此时它仅变成一个索引,指向GDT的一个表项,表项定义了段的起始地址、界限、属性等 就是GDT(还可能是LDT,这个以后再介绍)。GDT表项叫做描述符
GDT提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供。
看一下如图3-2所示的描述符的结构
示意图表示的是代码段和数据段描述符,还有系统段描述符和门描述符。
BYTE5和BTYE6中的一堆属性看上复杂
段的基址和界限。
由于历史问题,它们都被拆开那些属性,暂时先不管
对照着看啊 Descriptor这个宏用比较自动化的方法把段基址、段界限和段属性安排在一个描述符中合适的位置,有兴趣的读者可以研究这个宏的具体内容本例GDT中有3个描述符,DESC_DUMMY、 DESC_CODE32和 DESC_ VIDEODESC_VIDEO,段基址是OB8000h。 指向的正是显存
GDT中的每一个描述符定义一个段,cS、ds等段寄存器是如何和这些段对应起来的呢?段[SECTON.s32]中有两句代码是这样 段寄存器gs值变成了 Selector Video,Selector Video是这样定义 EQU是等值命令,count EQU $-offset A
的意思是count=$ -offset A。
它好像是DESC_ VIDEO这个描述符相对于GDT基址的偏移。有一个专门的名称,叫做选择子( Selector),它也不是一个偏移,而是稍稍复杂一些,它的结构如图3-3T和RPL都为零时,选择子就变成了对应描述符相对于GDT基址的 偏移,就好像我们程序中那样。mov [gs:edi], axgs的值是 Selector Video,它指示对应显存的描述符DESC VIDEO这条指令将把ax的值写入显存中偏移位edi的位置整个的寻址方式可以用如图3-4所示
示意图,真实的描述符中段基址以及段偏移等内容在描述符中的位置不是像图中这样安排
“段:偏移”形式的逻辑地址经段机制转化成“线性地址”,而不是“物理地址”,原因以后会提到。上面程序中,线性地址就是物理地址包含描述符的,不仅可是GDT,也可LDT。
只剩下[ SECTION.s16]这一段没有分析。
既然[ SECTION.s32]是32位的程序,并且在保护模式下执行,
[SECTION.s16]的任务一定是从实模式向保护模式跳转
接下来把GDT的物理地址填充到了 GdtPtr这个6字节的数据结构,然后执行
lgdt [GdtPtr]
这一句将GdtPtr指示的6字节加载到gdtr,gdtr的结构如图3-5 GdtPtr和gdr的结构完全一样下面是关中断,之所以关中断,因为保护模式下中断处理的机制是不同的,不关掉中断将会出现错误
再下面几句打开A20地址线。8086是用SEG:OFFSET这样的模式分段的, 它能表示的最大内存是FFFF:FFFF即10FFEFhFFFF0+0FFFF=10FFEFh1MB=FFFFF是最大地址啦!!8086只有20位地址总线,只能寻址1MB,如果试图访问超过1MB的地址时会怎样?系统并不异常,回卷(wrap)回去,重新从地址零寻址。 到了80286时,真的可以访问到1MB以上的内存,遇到同样的情况,系统不会再回卷寻址,造成了向上不兼容,为保证百分百兼容IBM用8042键盘控制器来控制第20个(从零开始数)地址位,这就是A20地址线,如果不被打开,第20个地址位将会总是零。
为访问所有内存,需把A20打开,开机默认关闭
如何打开呢?有点复杂,只用通过操作端口92h来实现这一种方式,如代码3-1中那样这不是惟一的方法,且在某些个别情况下,这种方法可能会出现问题。但在绝大多数情况下,它是适用的。
把crO的第0位置1这一位决定实模式和保护模式cr0结构如图3-6
PE位为0时,CPU运行于实模式,为1时,CPU运行于保护模式。mov cr0,eax”这一句之后,系统就运行于保护模式之下了。但此时cs的值仍然是实模式下的值,需把代码段的选择子装入cs。所以,我们需要jmp 这个跳转的目标将是描述符DESC_CODE32对应的段的首地址,即标号 LABEL_SEG_CODE32处。
至此,新皇帝登基,我们进入保护模式
这个jmp比看起来还要复杂,它不得不放在16位的段中,目标地址却32位的。它是混和16位和32位的代码
这个jmp眼一般的jmp是很不相同的,直接这样写是不严谨的
偏移地址应该是32位的,这样编译出来的只是16位的代码。目标地址的偏移不是0,而是32位的值,jmp SelectorCode32:0x12345678,则编译后偏移会被截断,只剩下0x5678。
要特殊的方法来处理。Linux中,这个跳转是用DB指令直接写二进制代码的方式来实现的,NASM提供更好的解决方法,就是加一个dword,本来 dword应该加在偏移之前,但NASM允许加在整个地址之前,这又可以说是NASM的优点
已成功进入保护模式,进入保护模式的主要步骤(1)准备GDT(2)用gdt加载gdr。(3)打开A20(4)置cr0的PE位。(5)跳转,进入保护模式。
如果目标代码的特权级低的话,无论它是不是一致代码段,都不能通 过call或者jmp转移进去, 尝试这样的转移将会导致常规保护错误。
数据段都是非一致的,不可能被低特权级的代码访问到。与代码段不同,数据段可以被更高特权级的代码访问到, 而不需要使用特定的门。
代码段的属性是“DA_C+DA_32”,根据 pm. Inc定义,DA_C是98h,二进制是10011000b。P位是1表明这个段在内存中存在,S位是1表明这个段是代码段或者数据段,TYPE=8表明这个段是只执行的段。DA_32是4000h, 这个段是代码段,D位是1表明这个段是32位的代码段。 总之,这个段是存在的只执行的32位代码段,DPL为0。 类似地, VIDEO段是存在的可读写数据段。
上面的程序为了让代码最短,进入保护模式之后开始死循环,除了重启系统外没有其他办法
在程序的末尾跳回实模式,使之有头有尾。
试验一下读写大地址内存在前面基础上,新建一个段(光盘pmtest2.asm),这个段以5MB为基址,远超实模式下1MB先读出开始处8字节内容,然后写入一个字符串,再从中读出8字节。如果读写成功,两次读出内容应不同, 且第二次读出的内容应该是我们写进的字符串字符串是保存在数据段中的,也是新增加的。 这两个段对应的描述