您的位置:首页 > 其它

于渊<orange's一个操作系统的实现>保护模式部分详解

2018-01-28 22:15 1631 查看

进入保护模式内容详解

实模式和保护模式的区别

实模式和保护模式是cpu的两种不同的工作模式,这两种工作模式主要的区别在于寻址方式的不同,寻址方式的意思是寻找地址的方式,这里稍微啰嗦一下,其实对于寻址更方便的理解方式应该是“寻值”,其实在汇编中无论是值还是地址或者变量本质上都是一个数值而已,所谓的“寻值”就是要确定这个东西在指令中究竟对应的数值是什么。

在介绍阆中不同工作模式下的不同寻址方式的时候,先介绍一下cpu中常见指令中的寻址。

1、立即寻址

mov ah ,80h ;把一个16进制数80放入寄存器中


80作为一个立即数,在指令中使用立即数的寻址方式称之为立即寻址,立即寻址在实模式和保护模式下并没有太大的区别,几乎一样

2、直接寻址

MOV BX, [1234H];


在指令中给出对应数值的直接有效地址的方式叫做直接寻址。

以上这条指令需要好好解释一下,因为两种模式下,直接寻址的工作方式是有区别的。上述这条指令,简单的来说就是:“把内存地址1234h的数据装入BX寄存器中,[]就像c中的数组一样,表示取[]中地址对应的数据”。但是在cpu进行实际执行这条指令的时候还有一些稍微复杂的工作。

先岔开一下说另一个事,我们看到的mov 实际上是伪指令,不同的mov指令对应的实际上的机器码是不同的,正如我们上面所看到的mov ah, 80h和mov bx,[1234h]这两个mov在经过汇编器的翻译之后会对应到不同的指令格式,所以是可以看成是两种不同的指令的,在cpu的执行方式是不同的。对于MOV BX, [1234H],在执行时,假设(DS)=2000H,内存单元21234H的值为5213H

实模式下是这么寻址的

cpu取出数据段寄存器DS中的值,将这个值乘以16之后再加上当前要访问的地址(也就是1234h),也就是如下的方式:

地址的=段地址×16+偏移地址地址的=段地址×16+偏移地址

也就是说指令中[]内的1234h在cpu执行的时候会变成 21234h(2000h x 16 + 1234h),这就是实模式下的寻址方式,可以看到汇编虽然已经离cpu流水线已经很近了,但还是向我们隐藏了一点点工作,还有一个特别要注意的地方是这个”段地址“的获取,是根据不同指令从而从不同的段寄存器中获取的。这里的使用的是数据段寄存器DS(Date segment),因为这个指令是一个取数指令mov,但如果涉及到的是一些跳转指令,比如说jmp或者是call,那么就会从代码段寄存器(code segment)去取这个值。

为什么使用”段:偏移”这种寻址方式呢?1.历史原因下,cpu的地址总线是20位的但是与寻址有关的寄存器却只有16位,为了不浪费多出的4位。2.将内存分段之后访问起来更加容易。

保护模式下是这么寻址的

保护模式下寻址方式与实模式下有很大的不同,在保护模式中,内存中多了一张叫做GPT的表,这类似于一个数组,其中每个元素(都是一个结构体)(也就是表项)都指向一段对应的内存,包含着这块内存的基本属性的描述(比如说段基址,段界限什么的),如下图所示:



虽然增加了GPT表,而且现在的cpu的地址总线以及相应的寻址寄存器都有了32位,但还是沿用过去的“段:偏移”的寻址方式,但是“段值”在此就和实模式不同,注意gpt表中下的 0,1,2,3,其实里面各对应着一个结构体对象,这个对象存储着对应一块内存的属性,而段值在此的主要作用就是要确定使用GPT表下的那个对象,从而找到对应的内存,说的明白点就是充当gpt数组的下标。

有了段值就可以偏移找到GPT对应的表项,从而偏移地址对应内存段中的地址,但是cpu是怎么知道GPT表在哪里的呢? 实质上,上图省略了cpu寻找GPT表的过程,GPT表的结构存放在CPU中的一个叫做gdtr的寄存器中,可以设定这个寄存器的值从而让cpu找到GPT表

代码分析

了解了cpu的寻址之后,接下来一步步分析于渊的代码:

6 %include    "pm.inc"    ; 常量, 宏, 以及一些说明
7
8 org 07c00h
9     jmp LABEL_BEGIN


首先是包含一些头文件和宏,规定起始于07c00h后就跳转到LABEL_BEGIN,接下来看看LABEL_BEGIN做了什么

28 [SECTION .s16]          ;定义了一个节叫做.s16
29 [BITS   16]             ;制定下面的指令位16位
30 LABEL_BEGIN:
31     mov ax, cs
32     mov ds, ax
33     mov es, ax
34     mov ss, ax
35     mov sp, 0100h
36
37     ; 初始化 32 位代码段描述符
38     xor eax, eax            ;将eax置零
39     mov ax, cs              ;将当前的代码段放入ax中
40     shl eax, 4              ; 16位下的段:基址寻址=段 × 16 + 基地址, 这里是>    先左移4位,代表乘16
41     add eax, LABEL_SEG_CODE32;将代码段的基地址加上
42     mov word [LABEL_DESC_CODE32 + 2], ax;初始化32位代码段的各个部分
43     shr eax, 16
44     mov byte [LABEL_DESC_CODE32 + 4], al
45     mov byte [LABEL_DESC_CODE32 + 7], ah
46
47     ; 为加载 GDTR 作准备
48     xor eax, eax
49     mov ax, ds
50     shl eax, 4
51     add eax, LABEL_GDT      ; eax <- gdt 基地址
52     mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
53
54     ; 加载 GDTR
55     lgdt    [GdtPtr]
56
57     ; 关中断
58     cli
59
60     ; 打开地址线A20
61     in  al, 92h
62     or  al, 00000010b
63     out 92h, al
64
65     ; 准备切换到保护模式
66     mov eax, cr0
67     or  eax, 1
68     mov cr0, eax
69
70     ; 真正进入保护模式
71     jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs,
72                                 ; 并跳转到 Code32Selector:0  处
73 ; END of [SECTION .s16]


从37~45行的主要内容是对LABEL_DESC_CODE32这个地址对应的内存进行赋值,让我们来看看LABEL_DESC_CODE32对应的地址是什么东西

11 [SECTION .gdt]
12 ; GDT
13 ;                              段基址,       段界限     , 属性
14 LABEL_GDT:         Descriptor       0,                0, 0           ; 空描述符
15 LABEL_DESC_CODE32: Descriptor       0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段
16 LABEL_DESC_VIDEO:  Descriptor 0B8000h,           0ffffh, DA_DRW      ; 显存首地址
17 ; GDT 结束


在第15行我们找到了LABEL_DESC_CODE32,发现它是一个Descriptor类型,这个类型是作者自定义的宏结构体(理解成一个结构体就好),而后面跟着的三个参数可以用来初始化这个结构体对象,注意第11行显示的GDT注释,其实这一段就是GDT表,三个表项分别在LABEL_GDT,LABEL_DESC_CODE32,LABEL_DESC_VIDEO

所以也就是说37~45行就是对GPT表项的初始化,这个表项对应的内存段是一个代码段,因为41行中把LABEL_SEG_CODE32加入eax寄存器相加,并在后续讲运算完后的地址去初始化表项,LABEL_SEG_CODE32对应的代码如下,主要就是获取通过gpt中的对应屏幕内存段的表项(80~81),从而写屏幕映射的内存缓存(86)

79 LABEL_SEG_CODE32:
80     mov ax, SelectorVideo
81     mov gs, ax          ; 获取gpt表项中屏幕对应的内存段的表项下标
82
83     mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
84     mov ah, 0Ch         ; 0000: 黑底    1100: 红字
85     mov al, 'P'
86     mov [gs:edi], ax
87
88     ; 到此停止
89     jmp $
90
91 SegCode32Len    equ $ - LABEL_SEG_CODE32
92 ; END of [SECTION .s32]


似乎跳的有点开,再回到程序的原来的运行的地方,这时候执行的是47~55行,如下,这一部分主要就是将GPT表的地址赋值给gdpt寄存器

47     ; 为加载 GDTR 作准备
48     xor eax, eax
49     mov ax, ds
50     shl eax, 4
51     add eax, LABEL_GDT      ; eax <- gdt 基地址
52     mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
53
54     ; 加载 GDTR
55     lgdt    [GdtPtr]


然后是57~63的关中断和打开A20门,在《oranges ‘s一个操作系统的实现》中P34开始对这部分有很详细的阐述了就不搬运了,

57     ; 关中断
58     cli
59
60     ; 打开地址线A20
61     in  al, 92h
62     or  al, 00000010b
63     out 92h, al


66~68是cr0寄存器的PE位置1,使cpu进入保护模式

65     ; 准备切换到保护模式
66     mov eax, cr0
67     or  eax, 1
68     mov cr0, eax


最后是71行的,代码段的跳转,一开始死活没明白这句为什么跳转到一个偏移值就可以跳转代码,搞了很久才发现,这个时候cpu已经进入了保护模式,寻址方式已经是通过GPT进行的了,所以这个jmp是先把段偏移寄存器cs值设定成GPT表项中代码段表项的偏移值,然后跳转到该代码段的0偏移处,SelectorCode32:0后面的0表示的是从代码段的0偏移处开始执行,代码段我们已经介绍过了,就是37~45行对那个代码段表项的初始化成79~92的LABEL_SEG_CODE32的代码。如果对这里还是不明白那就只能是上面介绍保护模式下是这么寻址的部分没看

71     jmp dword SelectorCode32:0  ; 执行这一句会把 SelectorCode32 装入 cs,
72                                 ; 并跳转到 Code32Selector:0  处


一些参考资料

更多的寻址方式:http://blog.csdn.net/liutianshx2012/article/details/50731280

说说gpt:http://www.techbulo.com/708.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐