http://blog.csdn.net/zgh1988/article/details/7098981

[置頂]全面剖析《自己動手寫操作系統》第三章---保護模式       

        分類:            操作系統649人閱讀評論(16)收藏舉報

《自己動手寫操作系統》讀後感                http://blog.csdn.net/zgh1988/article/details/7059936

全面剖析《自己動手寫操作系統》第一章   http://blog.csdn.net/zgh1988/article/details/7060032

全面剖析《自己動手寫操作系統》第二章 http://blog.csdn.net/zgh1988/article/details/7062065

  1. 1 ; ========================================== 
  2. 2 ; pmtest1.asm 
  3. 3 ; 編譯方法:nasm pmtest1.asm -o pmtest1.com 
  4. 4 ; ========================================== 
  5. 6 %include "pm.inc"    ; 常量, 宏, 以及一些說明 
  6. 8 org  0100h 
  7. 9  jmp LABEL_BEGIN 
  8. 10 
  9. 11 [SECTION .gdt] 
  10. 12 ; GDT 
  11. 13 ;                                         段基址,      段界限     , 屬性 
  12. 14 LABEL_GDT:       Descriptor         0,                0, 0           ; 空描述符 
  13. 15 LABEL_DESC_CODE32:   Descriptor         0, SegCode32Len - 1, DA_C + DA_32    ; 非一致代碼段, 32 
  14. 16 LABEL_DESC_VIDEO:    Descriptor   0B8000h,           0ffffh, DA_DRW      ; 顯存首地址 
  15. 17 ; GDT 結束 
  16. 18 
  17. 19 GdtLen       equ $ - LABEL_GDT   ; GDT長度 
  18. 20 GdtPtr       dw  GdtLen - 1  ; GDT界限 
  19. 21      dd  0       ; GDT基地址 
  20. 22 
  21. 23 ; GDT 選擇子 
  22. 24 SelectorCode32       equ LABEL_DESC_CODE32   - LABEL_GDT 
  23. 25 SelectorVideo        equ LABEL_DESC_VIDEO    - LABEL_GDT 
  24. 26 ; END of [SECTION .gdt] 
  25. 27 
  26. 28 [SECTION .s16] 
  27. 29 [BITS    16] 
  28. 30 LABEL_BEGIN: 
  29. 31  mov ax, cs 
  30. 32  mov ds, ax 
  31. 33  mov es, ax 
  32. 34  mov ss, ax 
  33. 35  mov sp, 0100h 
  34. 36  
  35. 37  ; 初始化 32 位代碼段描述符 
  36. 38  xor eax, eax 
  37. 39  mov ax, cs 
  38. 40  shl eax, 4 
  39. 41  add eax, LABEL_SEG_CODE32 
  40. 42  mov word [LABEL_DESC_CODE32 + 2], ax 
  41. 43  shr eax, 16 
  42. 44  mov byte [LABEL_DESC_CODE32 + 4], al 
  43. 45  mov byte [LABEL_DESC_CODE32 + 7], ah 
  44. 46 
  45. 47  ; 爲加載 GDTR 作準備 
  46. 48  xor eax, eax 
  47. 49  mov ax, ds 
  48. 50  shl eax, 4 
  49. 51  add eax, LABEL_GDT      ; eax <- gdt 基地址 
  50. 52  mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 
  51. 53 
  52. 54  ; 加載 GDTR 
  53. 55  lgdt    [GdtPtr] 
  54. 56 
  55. 57  ; 關中斷 
  56. 58  cli 
  57. 59 
  58. 60  ; 打開地址線A20 
  59. 61  in  al, 92h 
  60. 62  or  al, 00000010b 
  61. 63  out 92h, al 
  62. 64 
  63. 65  ; 準備切換到保護模式 
  64. 66  mov eax, cr0 
  65. 67  or  eax, 1 
  66. 68  mov cr0, eax 
  67. 69 
  68. 70  ; 真正進入保護模式 
  69. 71  jmp dword SelectorCode32:0  ; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0  處 
  70. 72 ; END of [SECTION .s16] 
  71. 73 
  72. 74 
  73. 75 [SECTION .s32]; 32 位代碼段. 由實模式跳入. 
  74. 76 [BITS    32] 
  75. 77 
  76. 78 LABEL_SEG_CODE32: 
  77. 79  mov ax, SelectorVideo 
  78. 80  mov gs, ax          ; 視頻段選擇子(目的) 
  79. 81 
  80. 82  mov edi, (80 * 10 + 0) * 2  ; 屏幕第 10 行, 第 0 列。 
  81. 83  mov ah, 0Ch         ; 0000: 黑底    1100: 紅字 
  82. 84  mov al, 'P' 
  83. 85  mov [gs:edi], ax 
  84. 86 
  85. 87  ; 到此停止 
  86. 88  jmp $ 
  87. 89 
  88. 90 SegCode32Len equ $ - LABEL_SEG_CODE32 
  89. 91 ; END of [SECTION .s32] 
 1 ; ==========================================
 2 ; pmtest1.asm
 3 ; 編譯方法:nasm pmtest1.asm -o pmtest1.com
 4 ; ==========================================
 5
 6 %include	"pm.inc"	; 常量, 宏, 以及一些說明
 7
 8 org	0100h
 9	jmp	LABEL_BEGIN
10
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	; 非一致代碼段, 32
16 LABEL_DESC_VIDEO:	Descriptor	 0B8000h,           0ffffh, DA_DRW		; 顯存首地址
17 ; GDT 結束
18
19 GdtLen		equ	$ - LABEL_GDT	; GDT長度
20 GdtPtr		dw	GdtLen - 1	; GDT界限
21		dd	0		; GDT基地址
22
23 ; GDT 選擇子
24 SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
25 SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
26 ; END of [SECTION .gdt]
27
28 [SECTION .s16]
29 [BITS	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
39 	mov	ax, cs
40 	shl	eax, 4
41	add	eax, LABEL_SEG_CODE32
42	mov	word [LABEL_DESC_CODE32 + 2], ax
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, 並跳轉到 Code32Selector:0  處
72 ; END of [SECTION .s16]
73
74
75 [SECTION .s32]; 32 位代碼段. 由實模式跳入.
76 [BITS	32]
77
78 LABEL_SEG_CODE32:
79	mov	ax, SelectorVideo
80	mov	gs, ax			; 視頻段選擇子(目的)
81
82	mov	edi, (80 * 10 + 0) * 2	; 屏幕第 10 行, 第 0 列。
83	mov	ah, 0Ch			; 0000: 黑底    1100: 紅字
84	mov	al, 'P'
85	mov	[gs:edi], ax
86
87	; 到此停止
88	jmp	$
89
90 SegCode32Len	equ	$ - LABEL_SEG_CODE32
91 ; END of [SECTION .s32]

在下面我要講述3.1中所遇到的問題和迷惑。主要以pmtest1.asm爲例,從以下幾方面進行分析和講解。
1)[SECTION .XXX]爲何物?
2)全局描述符表(GDT)、描述符(Descriptor)、全局描述符表寄存器(GDTR)、選擇子(SelectorXXX) 是什麼?用來做什麼?
3)實模式下的尋址方式與保護模式下的尋址方式的區別?
4)描述符宏定義和初始化段描述符
5)爲加載gdtr做準備工作
6)其他
1、[SECTION .XXX]爲何物?
	SECTION和SEGMENT的作用相類似,就是代表“段”的意思,從整個程序來看,該程序分爲3個模塊,分別是[SECTION .gdt]、[SECITON .s16]、[SECTION .s32]三部分。我們很容易就可以看出,其中的[SECTION .gdt]應該是數據段,其他的兩個是代碼段。通過[SECTION .XXX]將程序分成不同模塊,完成不同的功能,使得程序看起來清晰明瞭。
2、描述符(Descriptor)、全局描述符表(GDT)、全局描述符表寄存器(GDTR)、選擇子(SelectorXXX) 是什麼?用來做什麼?
 
 
	段,在80X86中,分段機制將內存空間分成一個或者多個線性區域,我們把這些線性區域稱爲段。我們需要將這些段區分開來,於是分段機制爲每個段賦予3個屬性,分別是:1、段基址(Base address):指定段在線性地址空間中的開始地址。2、段界限(Limit):表示了段內最大可用偏移量,也就是說它定義了段的長度。3、段屬性(Attribute):指定了段的特性,包括:可讀,可寫或者可執行,特權等級等特性。
	段描述符(Descriptor),在程序中,我們需要定義一個數據結構來表示段,它同樣包含三個元素,段基址(Base),段界限(Limit),段屬性(Attribute),我們稱它爲段描述符(Descriptor)。段描述符和段就是同一個概念,每個段描述符要佔用8個字節的空間。
	段描述符表(Descriptor Table),在一個程序中,不只存在一個段(段描述符)。所以我們需要將這些段描述符組織起來,於是定義了一個存儲段描述符的數組,稱爲段描述符表。
	段選擇子(SelectorXXX),把所有段描述符都存儲在段描述符表中,當我們使用其中某一個段的時候,我們並不直接指向該段,而是通過該段描述符在段描述符表中的位置來訪問的。故段選擇子,就是一個16位的標識符,用來標識該段描述符在描述符表中的位置。
 
 
	段描述符表可以分爲兩類,一類是全局描述符表GDT(Global Descriptor Table),一類是局部描述符表LDT(Local Descriptor Table)。系統中供所有的任務共享的是全局描述符表,而不同的任務卻是使用自己的局部描述符表。
	緊接着,如何讓系統知道段描述符表在什麼地方呢?處理器提供了內存管理寄存器,分別是全局描述符表寄存器(GDTR)、局部描述符表寄存器(LDTR)。GDTR寄存器中用於存放全局描述符表GDT的32位線性基地址和16位的表的長度值。LDTR寄存器中用於存放局部描述符表LDT的32位線性基地址和16位的表的長度值。通過系統指令,lgdt將GDT的線性基址和長度值加載到GDTR寄存器中,lldt將LDT的線性基址和長度值加載到LDTR寄存器中。
下面我們來分析程序中的代碼:
  1. 11 [SECTION .gdt]
  2. 12 ; GDT
  3. 13 ; 段基址, 段界限 , 屬性
  4. 14 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
  5. 15 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32 ; 非一致代碼段, 32
  6. 16 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址
  7. 17 ; GDT 結束
  8. 18
  9. 19 GdtLen equ $ - LABEL_GDT ; GDT長度
  10. 20 GdtPtr dw GdtLen - 1 ; GDT界限
  11. 21 dd 0 ; GDT基地址
  12. 22
  13. 23 ; GDT 選擇子
  14. 24 SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
  15. 25 SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
  16. 26 ; END of [SECTION .gdt]
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	; 非一致代碼段, 32
16 LABEL_DESC_VIDEO:	Descriptor	 0B8000h,           0ffffh, DA_DRW		; 顯存首地址
17 ; GDT 結束
18
19 GdtLen		equ	$ - LABEL_GDT	; GDT長度
20 GdtPtr		dw	GdtLen - 1	; GDT界限
21		dd	0		; GDT基地址
22
23 ; GDT 選擇子
24 SelectorCode32		equ	LABEL_DESC_CODE32	- LABEL_GDT
25 SelectorVideo		equ	LABEL_DESC_VIDEO	- LABEL_GDT
26 ; END of [SECTION .gdt]
	在程序中,14、15、16行定義了3個段描述符,LABEL_GDT(空描述符),LABEL_DESC_CODE32(32位代碼段描述符),LABEL_SESC_VIDEO(顯示內存描述符)。每個描述符都包含了3個屬性,段基址、段界限、段屬性。
	將三個描述符組織到一起構成一個全局段描述符表(GDT)。12-17行完成了GDT的定義。
	GdtLen爲GDT的長度。
	GdtPtr爲一個數據結構,裏面包含兩個元素,第一個元素是2 bytes的GDT界限。第二個元素是4 bytes的GDT的基地址。該數據結構與全局描述符表寄存器(GDTR)的數據結構相同,所以在加載GDTR的時候(源代碼55行),就是將該GdtPtr加載到GDTR中。
	由於第一個段LABEL_GDT是空描述符,它僅僅代表該GDT的初始地址,所以該描述符爲空描述符,一般情況下,不爲它創建選擇子。然後該程序建立了兩個選擇子(24、25行)SelectorCode32和SelectorVideo,分別對應着這兩個段LABEL_SESC_CODE32和LABEL_DESC_VIDEO。
	這段代碼的結構大家應該明白了吧,下面我要分別介紹段描述符,全局描述符表寄存器,選擇子的數據結構。
段描述符(Descriptor)的結構圖如下:
 
 
 
	段描述符佔有8個字節,在這裏我只想提醒大家一下,段基址分別佔有BYTE2、BYTE3、BYTE4和BYTE7。在下面初始化段描述符的時候需要用到這些。
段選擇子的結構圖如下圖所示:
 
 
	在這裏簡單的介紹一下,它使用(15…3)來表示索引,故每一個描述符表最多隻用213個描述符,除去第一個空描述符,則可以使用的描述符爲8191個描述符。
	TI標誌着該選擇子所指向的段描述符是全局描述符,還是局部描述符。當TI=0時,表示全局描述符,當TI=1時,表示局部描述符。
	RPL請求優先級,稍後下一節將會提到。
全局描述符表寄存器(GDT)的結構圖如下所示:
 
 
 
	在這裏,你可以和上面程序中的GdtPtr數據結構做比較,是不是格式相同。2個字節表示GDT界限,4個字節表示GDT基地址。
 
3、實模式下的尋址方式與保護模式下的尋址方式有什麼不同?
	在實模式下,也就是在8086系統下的尋址方式。 Intel 8086是16位的CPU,它有着16位的寄存器(Register),16位的數據總線(Data Bus)以及20位的地址總線(Address Bus)和1MB的尋址能力。一個地址是由段和偏移兩部分組成的,物理地址遵循這樣的計算公式:
	物理地址(Physical Address) = 段值(Segment) * 16 + 偏移(Offset)
	其中段值和偏移都是16位的。故尋址範圍爲1MB。
	在保護模式下,有了分段機制,所以它的尋址方式發生了很大的變化。具體如下圖所示:
 
 
 
 
	在保護模式下,首先使用段選擇子 在 段描述符表 中查找到相對應的 段描述符,找到32位段基址,然後在與32位的偏移量相加,得到線性地址。段基址和段偏移量都是32位的,所以尋址範圍大小爲4GB。在程序中jmp dword SlectorCode32:0的作用,就是進入保護模式下的尋址方式。其中,在使用某個段時,它的段選擇子是存儲在段寄存器中的。
	這裏面存在着一個問題,是否我們每次尋址都要先去全局描述符表寄存器(GDTR)中,查找到全局描述符表(GDT)的基址,然後再次根據選擇子的索引跳轉到該描述符所在的位置,然後取得段描述符中的基址,如果這樣的話,我們裏裏外外採訪了幾次內存,太浪費時間了。實際上段寄存器結構是這樣的:
 
 
	這樣的好處就是,我們可以直接獲取段描述符。
 
4、描述符宏定義和初始化段描述符
  1. 1 ; 描述符
  2. 2 ; usage: Descriptor Base, Limit, Attr
  3. 3 ; Base: dd
  4. 4 ; Limit: dd (low 20 bits available)
  5. 5 ; Attr: dw (lower 4 bits of higher byte are always 0)
  6. 6 %macro Descriptor 3
  7. 7 dw %2 & 0FFFFh ; 段界限 1 (2 字節)
  8. 8 dw %1 & 0FFFFh ; 段基址 1 (2 字節)
  9. 9 db (%1 >> 16) & 0FFh ; 段基址 2 (1 字節)
  10. 10 dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 屬性 1 + 段界限 2 + 屬性 2 (2 字節)
  11. 11 db (%1 >> 24) & 0FFh ; 段基址 3 (1 字節)
  12. 12 %endmacro ; 共 8 字節
 1 ; 描述符
 2 ; usage: Descriptor Base, Limit, Attr
 3 ;        Base:  dd
 4 ;        Limit: dd (low 20 bits available)
 5 ;        Attr:  dw (lower 4 bits of higher byte are always 0)
 6 %macro Descriptor 3
 7	dw	%2 & 0FFFFh				; 段界限 1				(2 字節)
 8	dw	%1 & 0FFFFh				; 段基址 1				(2 字節)
 9	db	(%1 >> 16) & 0FFh			; 段基址 2				(1 字節)
10	dw	((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)	; 屬性 1 + 段界限 2 + 屬性 2		(2 字節)
11	db	(%1 >> 24) & 0FFh			; 段基址 3				(1 字節)
12 %endmacro ; 共 8 字節

2、3、4、5行註釋告訴我們,該宏定義需要三個變量,分別是段基址(4 bytes),段界限(4 bytes),段屬性(dw)。

回顧剛纔的段描述符結構,該宏定義,就是將變量Base,Limit,Attr分別安插到描述符中相應的位置。Base是1,Limit是2,Attr是3

7 是將Limit低16位賦值給描述符的BYTE0和BYTE1

8 是將Base低16位賦值給描述符的BYTE2和BYTE3

9 是將Base右移16位後的低8位(也就是原Base的第16—23位)賦值給描述符的BYTE4

10是將Limit右移8位之後的第8—11位和Attr的0—7和12—15位,組合起來存儲到描述符的BYTE5和BYTE6

11是將Base右移24位後的低8位(也就是原Base的24—32位)賦值給描述符的BYTE7

初始化段描述符代碼:

  1. 37 ; 初始化 32 位代碼段描述符
  2. 38 xor eax, eax
  3. 39 mov ax, cs
  4. 40 shl eax, 4
  5. 41 add eax, LABEL_SEG_CODE32
  6. 42 mov word [LABEL_DESC_CODE32 + 2], ax
  7. 43 shr eax, 16
  8. 44 mov byte [LABEL_DESC_CODE32 + 4], al
  9. 45 mov byte [LABEL_DESC_CODE32 + 7], ah
37 	; 初始化 32 位代碼段描述符
38 	xor	eax, eax
39 	mov	ax, cs
40 	shl	eax, 4
41	add	eax, LABEL_SEG_CODE32
42	mov	word [LABEL_DESC_CODE32 + 2], ax
43	shr	eax, 16
44	mov	byte [LABEL_DESC_CODE32 + 4], al
45	mov	byte [LABEL_DESC_CODE32 + 7], ah

爲什麼要初始化?你會發現這裏只是修改了段描述符LABEL_DESC_CODE32的BYTE2,BYTE4,BYTE7。是不是突然恍然大悟?因爲在我們初始化該LABEL_DESC_CODE32描述符時,將其基地址初始化爲0,所以我們要修改描述符的基地址爲其實際的地址。這也是在前面介紹段描述符的時候,我提醒大家需要注意的地方,即描述符的基地址所佔有的字節是BYTE2,BYTE4,BYTE7。

5、爲加載gdtr做準備工作

  1. 47 ; 爲加載 GDTR 作準備
  2. 48 xor eax, eax
  3. 49 mov ax, ds
  4. 50 shl eax, 4
  5. 51 add eax, LABEL_GDT ; eax <- gdt 基地址
  6. 52 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
  7. 53
  8. 54 ; 加載 GDTR
  9. 55 lgdt [GdtPtr]
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]

這個很好理解,我們就是對GdtPtr進行賦值,主要是初始化GDT的基地址。也就是將GDT的初始地址,賦值給GdtPtr的BYTE2,BYTE3,BYTE4,BYTE5。使GdtPtr的數據結構剛好符合GDTR,然後執行lgdt [GdtPtr],加載全局描述符表寄存器。將GDT的基地址和界限賦值給GDTR。

6、其他

至於接下來的 關中斷、打開地址線A20、切換到保護模式、進入保護模式,跳轉到32位代碼段等一系列的問題,可以從書中找到合適的解釋。

參考書籍:Linux內核完全剖析基於0.12內核

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章