《自己动手写操作系统》第三章 pmtest1——从实模式到保护模式

摘要:本节,我们将学习如何从实模式进入保护模式,剖析pmtest1.asm源码。详细讲解如何从保护模式进入实模式。


1.头文件编写:

这一部分,你需要把一些关键物理器件和数据结构的布局记在脑子里

GDT描述符:32b段基地址(B2B3B4B7+20b段限长(B0B1B6的低4位)+12b段属性(B5B6的高4位),详细布局见下图(针对代码段和数据段):

《自己动手写操作系统》第三章 pmtest1——从实模式到保护模式_第1张图片


所以,我们定义了如下的存储段描述符结构体:

42;宏定义-----------------------------------
43;segment discripor
44;for code and data
45;usage Descripor Base, Limit, Attr
46; Base dd
47; Limit dd (Low 20b are available)
48; Attr dw(lower 4b of higher byte are 0)
49%macro Descriptor 3
50 dw %2&0FFFFh
51 dw %1&0FFFFh
52 db (%1 >> 16)&0FFh ;注意,运算以后超过8b,但是会被db截断
53 dw (%3&0F0FFh) | ((%2 >> 8)&F0FFh)
54 db (%1>> 24)&0FFh
55 %endmacro;8字节

同时,我们定义了如下的门描述符结构:

《自己动手写操作系统》第三章 pmtest1——从实模式到保护模式_第2张图片


57;for gate
58;usage Gate Selector Selector,Offset,Dcount,Attr
59; selector dw
60; offset dd
61; Dcount dw
62; Attr dw
63%macro Gate 4
64 dw (%2&FFFFh)
65 dw %1
66 dw (%3 & 1Fh) | ((%4<< 8) & FF00h)
67 dw (%2 >> 16) & FFFFh
68 %endmarco

段选择子:布局是2+1+13

相应的,我们根据IA-32的有关说明定义了如下的常量:

《自己动手写操作系统》第三章 pmtest1——从实模式到保护模式_第3张图片

1;描述符类型说明:
2_LDT EQU 82h ; 局部描述符表段类型值
3DA_TaskGate EQU 85h ; 任务门类型值
4DA_386TSS EQU 89h ; 可用386任务状态段类型值
5DA_386CGate EQU 8Ch ; 386 调用门类型值
6DA_386IGate EQU 8Eh ; 386 中断门类型值
7DA_386TGate EQU 8Fh ; 386 陷阱门类型值
8;---------------------------------------------------
9;DA_32 EQU 4000h ;32b段
10DA_DPL0 EQU 00h ; DPL = 0
11DA_DPL1 EQU 20h ; DPL = 1
12DA_DPL2 EQU 40h ; DPL = 2
13DA_DPL3 EQU 60h ; DPL = 3
14
15;数据段和代码段描述符的低8位
16;---------------------------------------------------------------------------
17DA_DR EQU 90h ; 存在的只读数据段类型值
18DA_DRW EQU 92h ; 存在的可读写数据段属性值
19DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
20DA_C EQU 98h ; 存在的只执行代码段属性值
21DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值
22DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值
23DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值
24
25
26;选择子类型说明
27------------------------------------------
28;SA_selector attribute
29SA_RPL0 equ 0;
30SA_RPL1 equ 1;
31SA_RPL2 equ 2;
32SA_RPL3 equ 3;
33
34SA_TIG equ 0;
35SA_TIG equ 4;

注意,在这里,我们为什么用的是4,不是1,是为了方便以后用加法赋值。从中,我们也可以看到,尽量做到所有的数字,是字节对齐的.

写好上述代码以后,放入一个.inc文件之中,然后使用命令%include"pm.inc"将头文件引入


2.保护模式的运行环境

我们采用dos的引导程序,直接来书写启动内核。这样,可以解决引导扇区只有512B的限制。

需要在bochs环境中设置从软盘a启动,然后运行软盘b中的程序。


3.GDT

GDT的产生,需要追溯到实模式与保护模式的寻址差别:段寄存器CS等存储的内容如果是索引,那么我们需要有一个结构来保存段的信息,这个结构就是GDTGDT的作用是提供保护模式下的段存储机制。对于这一步分,需要明白段描述符的详细组成(8B)。

关于GDT的详细介绍,可以参考这里http://blog.csdn.net/trochiluses/article/details/8968750


4.详细代码与分析

代码解惑:


4.1[BITS32]等修饰,起到了什么作用?与BITS16有什么区别?

答:关于BITS(可能,你需要了解一下处理器字长和指令位数等:)

'BITS'指令指定NASM产生的代码是被设计运行在16位模式的处理器上还是运行在32位模式的处理器上。语法是'BITS16''BITS32'在大多数情况下,你可能不需要显式地指定'BITS'

'a.out','elf''win32'目标文件格式都是被设计用在32位操作系统上的,它们会让NASM缺省选择32位模式。而'obj'目标文件格式允许你为每一个段指定'USE16''USE32',然后NASM就会按你的指定设定操作模式,所以多次使用'BITS'是没有必要的。

但是我们的Realinit.bin的开始部分代码,采用NASM编译,目标格式为BIN格式,即纯粹的二进制可执行文件,不带任何头文件。

最有可能使用'BITS'的场合是在一个纯二进制文件中使用32位代码;这是因为'bin'输出格式在作为DOS'.COM'程序,DOS'.SYS'设备驱动程序,或引导程序时,默认都是16位模式。如果你仅仅是为了在16位的DOS程序中使用32位指令,你不必指定'BITS32',如果你这样做了,汇编器反而会产生错误的代码,因为这样它会产生运行在16位模式下,却以32位平台为目标的代码。当NASM'BITS16'状态下时,使用32位数据的指令可以加一个字节的前缀0x66,要使用32位的地址,可以加上0x67前缀。在'BITS32'状态下,相反的情况成立,32位指令不需要前缀,而使用16位数据的指令需要0x66前缀,使用16位地址的指令需要0x67前缀。'BITS'指令拥有一个等效的原始形式:[BITS16][BITS32]。而用户级的形式只是一个仅仅调用原始形式的宏。


4.2段基地址是如何计算的?LABEL_BIGIN不是段基地址?

我们要清楚,一个物理地址,如果没有开启分页机制,在保护模式和实模式下对应的地址是相同的,是不过寻址方式不同而已。


4.3实模式和保护模式的寻址区别在哪里?在代码中是如何体现出来的?

结合上一个问题,实模式下是segment*16+offset,注意,这个segmentcs等中的数值;保护模式也是CSoffset,不过此时的CS仅仅是一个选择符而已,还需要转换成段基地址。另外,代码中的LABEL-BEGIN等,表示的是段内偏移,所以采用(eax<<4)+ LABEL_BEGIN来表示段的首地址。


4.4不同长度的操作数是如何进行运算的?是截断还是补齐?为什么会有修饰符movbytejmpdword等?

分情况:movaxbl:寄存器之间,按照高位补0的原则;

mov alff0ffh:寄存器与操作树之间,按照截断和补充高位0的原则

mov [ax],03:立即数和内存,很显然,这个难办的地方在于双方都没有固定的长度,或者说只要有内存在内,就没有固定长度,此时需要指令操作数长度,例如movbyte [ax],03等。

同理,jmpworddword在于对偏移量的处理,假如我们使用前者,只有16b寄存器,会发生截断;所以用后者才比较安全。所以,jmp指令是一个需要特殊对待的指令。

另外,不要将长操作数赋值给短的:movbyte [bx],ax这样是错误的;总之,进行相关操作的时候,尽量确保两个操作数长度的统一


4.5为什么进入保护模式之前,需要先关中断?需要打开A20?设置GDT?

这里,我们先挖坑,等以后回来再填补.


4.6实模式下的偏移和保护模式之下的offset有什么区别?实模式下,标号的地址表示实际的物理地址还是相当于段的offset呢?

答:offset本身的含义没有什么不同。label表示offset而不是实际的物理地址,之所以看起来像物理地址,是因为那时候cs的初始值是0000h。
那么,如果仅仅是offset,那么如何解释“mov    word [LABEL_DESC_CODE32 + 2], ax”中内存数据的寻址呢?
我们来看一下,上面这条指令汇编以后的结果:
 mov word ptr ds:0x010e, ax ; a30e01
看见了没有,对于内存寻址,ds被隐藏起来了,这就是我们之前为什么要初始化ds的原因!!!
也就是说,标号的地址是本身相对与段首地址的offset

4.7org 0100h是什么意思?为什么要加载到这个地方?

答:要了解ORG 0100h,就必须先了解程序段前缀PSPProgram Segment Prefix)
程序段前缀是一个操作系统(DOS)概念。当输入一个外部命令或通过EXEC子功能(系统功能调用INT21h,子功能号为4Bh)加载一个程序时,COMMAND确定当前可用内存的最低端作为程序段的起点,也就是程序被加载到内存空间的起点。在程序所占用内存的前2560100h)个字节中,DOS会为程序创建前缀(PSP)数据区。PSP结构与CP/M中的“控制区域”概念十分接近,这是因为DOS就是从CP/M演变而来的。

比如你有一个标号Test的偏移地址是0x0Bh,当编译器看见ORG 0x100h后,就会给这个偏移加上0x100h,编译完成的.com文件中,这个偏移就变成了0x10Bh。所以,你如果想在最开始设定断点,b 00h是不会生效的,因为00h被移动到了100hDOS下的.com程序肯定被加载的CS+0x100h处,因为段的前0100h256字节)是留给PSP的。这是操作系统DOS的概念,跟ORG没有关系,所以你写ORG 0200h
,也不会让程序加载到CS段的0200h处。另外,dos是一个运行在实模式之下的操作系统。
 
  
4.8关于equ?是什么时候计算的?
答:equ实际上相当于一个宏定义,所以它仅仅是一个替换作用,只有在程序实际运行的时候,才得到前面的值。

5.让我们再来回顾一下,从实模式到保护模式的过程

1)完成对段描述符的初始化

2)装填gdtptr

3)关中断

4)打开A20

5)设置cr0寄存器

6jmp切换


6.调试程序

观察各个寄存器和相关指令的执行和变化,得到的经验。

书写16进制数的时候可以写成ox0100,或者0010h,但是不能写成ox0100h

你可能感兴趣的:(自己动手写操作系统,自己动手写操作系统,实模式,保护模式)