目錄
一、前景回顧
二、用C語言編寫核心
三、載入核心
四、執行測試
本回開始,我們要開始編寫核心程式碼了,在此之前,先梳理一下已經完成的工作。
藍色部分是目前已經完成的部分,黃色部分是本節將要實現的。
為什麼要用C語言來編寫核心呢,其實用組合語言也可以實現,只是對於我們來講,看C語言程式碼肯定要比組合語言更容易理解,看起來也沒那麼費勁。所以用C語言可以更加省事。
先來看看我們核心程式碼的最初形態,首先在專案路徑下新建一個project/kernel的目錄,以後我們核心相關的檔案都存放於此,在該目錄下新建一個名為main.c的檔案,在main.c中鍵入如下程式碼:
1 int main(void) 2 { 3 while(1); 4 return 0; 5 }
這就是我們的核心程式碼,當然現在什麼都還沒有,就算核心成功載入進去也沒有什麼反應。這裡我們先實現一個自己的列印函數,在main函數中呼叫這個列印函數來列印出「HELLO KERNEL」的字元,這樣就能測試核心程式碼執行是否成功。前面我們一直都是直接操作視訊記憶體段的記憶體來往螢幕上來列印字元,現在開始用C語言程式設計了,自然要封裝一個列印函數來列印字元。
同樣,在專案路徑下新建另一個project/lib/kernel目錄,該目錄用來存放一些供核心使用的庫檔案。在該目錄下新建名為print.S和print.h的檔案,在此之前,我們在project/lib目錄下新建一個名為stdint.h的檔案用來定義一些資料型別。程式碼如下:
1 #ifndef __LIB_STDINT_H__
2 #define __LIB_STDINT_H__
3 typedef signed char int8_t;
4 typedef signed short int int16_t;
5 typedef signed int int32_t;
6 typedef signed long long int int64_t;
7 typedef unsigned char uint8_t;
8 typedef unsigned short int uint16_t;
9 typedef unsigned int uint32_t;
10 typedef unsigned long long int uint64_t;
11 #endif
1 TI_GDT equ 0
2 RPL0 equ 0
3 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
4
5 section .data
6 put_int_buffer dq 0
7
8 [bits 32]
9 section .text
10 ;-----------------------------------put_str--------------------------------------
11 ;功能描述:put_str通過put_char來列印以0字元結尾的字串
12 ;----------------------------------------------------------------------------------
13 global put_str
14 put_str:
15 push ebx
16 push ecx
17 xor ecx, ecx
18 mov ebx, [esp + 12]
19 .goon:
20 mov cl, [ebx]
21 cmp cl, 0
22 jz .str_over
23 push ecx
24 call put_char
25 add esp, 4
26 inc ebx
27 jmp .goon
28 .str_over:
29 pop ecx
30 pop ebx
31 ret
32
33 ;--------------------------put_char-------------------------
34 ;功能描述:把棧中的一個字元寫入到遊標所在處
35 ;---------------------------------------------------------------
36 global put_char
37 put_char:
38 pushad ;備份32位元暫存器環境
39 mov ax, SELECTOR_VIDEO ;不能直接把立即數送入段暫存器中
40 mov gs, ax
41
42 ;----------------------獲取當前遊標位置---------------------------------
43 ;先獲取高8位元
44 mov dx, 0x03d4
45 mov al, 0x0e
46 out dx, al
47 mov dx, 0x03d5
48 in al, dx
49 mov ah, al
50
51 ;再獲取低8位元
52 mov dx, 0x03d4
53 mov al, 0x0f
54 out dx, al
55 mov dx, 0x03d5
56 in al, dx
57
58 ;將遊標位置存入bx
59 mov bx, ax
60
61 ;在棧中獲取待列印的字元
62 mov ecx, [esp + 36] ;pushad將8個32位元暫存器都壓入棧中,再加上主調函數4位元組的返回地址,所以esp+36之後才是主調函數壓入的列印字元
63 cmp cl, 0xd ;判斷該字元是否為CR(回車),CR的ASCII碼為0x0d
64 jz .is_carriage_return
65
66 cmp cl, 0xa ;判斷該字元是否為LF(換行),LF的ASCII碼為0x0a
67 jz .is_line_feed
68
69 cmp cl, 0x8 ;判斷該字元是否為BS(空格),BS的ASCII碼為0x08
70 jz .is_backspace
71
72 jmp .put_other
73
74 ;字元為BS(空格)的處理辦法
75 .is_backspace:
76 dec bx
77 shl bx, 1
78 mov byte [gs:bx], 0x20
79 inc bx
80 mov byte [gs:bx], 0x07
81 shr bx, 1
82 jmp set_cursor
83
84 ;字元為CR(回車)以及LF(換行)的處理辦法
85 .is_line_feed:
86 .is_carriage_return:
87 xor dx, dx
88 mov ax, bx
89 mov si, 80
90 div si
91 sub bx, dx
92
93 ;CR(回車)符的處理結束
94 .is_carriage_return_end:
95 add bx, 80
96 cmp bx, 2000
97 ;LF(換行)符的處理結束
98 .is_line_feed_end:
99 jl set_cursor
100
101 .put_other:
102 shl bx, 1
103 mov [gs:bx], cl
104 inc bx
105 mov byte [gs:bx], 0x07
106 shr bx, 1
107 inc bx
108 cmp bx, 2000
109 jl set_cursor
110
111 .roll_screen:
112 cld
113 mov ecx, 960
114 mov esi, 0xc00b80a0
115 mov edi, 0xc00b8000
116 rep movsd
117
118 mov ebx, 3840
119 mov ecx, 80
120
121 .cls:
122 mov word [gs:ebx], 0x0720
123 add ebx, 2
124 loop .cls
125 mov bx, 1920
126 global set_cursor
127 set_cursor:
128 mov dx, 0x03d4
129 mov al, 0x0e
130 out dx, al
131 mov dx, 0x03d5
132 mov al, bh
133 out dx, al
134
135 mov dx, 0x03d4
136 mov al, 0x0f
137 out dx, al
138 mov dx, 0x03d5
139 mov al, bl
140 out dx, al
141 .put_char_done:
142 popad
143 ret
144 ;-----------------------------------put_int--------------------------------------
145 ;功能描述:將小端位元組序的數位變成對應的ASCII後,倒置
146 ;輸入:棧中引數為待列印的數位
147 ;輸出:在螢幕中列印十六進位制數位,並不會列印字首0x
148 ;如列印十進位制15時,只會列印f,而不是0xf
149 ;----------------------------------------------------------------------------------
150 global put_int
151 put_int:
152 pushad
153 mov ebp, esp
154 mov eax, [ebp + 36]
155 mov edx, eax
156 mov edi, 7
157 mov ecx, 8
158 mov ebx, put_int_buffer
159
160 ;將32位元數位按照16進位制的形式從低位到高位逐個處理,共處理8個16進位制數位
161 .16based_4bits: ; 每4位元二進位制是16進位制數位的1位,遍歷每一位16進位制數位
162 and edx, 0x0000000F ; 解析16進位制數位的每一位。and與操作後,edx只有低4位元有效
163 cmp edx, 9 ; 數位0~9和a~f需要分別處理成對應的字元
164 jg .is_A2F
165 add edx, '0' ; ascii碼是8位元大小。add求和操作後,edx低8位元有效。
166 jmp .store
167 .is_A2F:
168 sub edx, 10 ; A~F 減去10 所得到的差,再加上字元A的ascii碼,便是A~F對應的ascii碼
169 add edx, 'A'
170
171 ;將每一位數位轉換成對應的字元后,按照類似「大端」的順序儲存到緩衝區put_int_buffer
172 ;高位字元放在低地址,低位字元要放在高地址,這樣和大端位元組序類似,只不過咱們這裡是字元序.
173 .store:
174 ; 此時dl中應該是數位對應的字元的ascii碼
175 mov [ebx+edi], dl
176 dec edi
177 shr eax, 4
178 mov edx, eax
179 loop .16based_4bits
180
181 ;現在put_int_buffer中已全是字元,列印之前,
182 ;把高位連續的字元去掉,比如把字元000123變成123
183 .ready_to_print:
184 inc edi ; 此時edi退減為-1(0xffffffff),加1使其為0
185 .skip_prefix_0:
186 cmp edi,8 ; 若已經比較第9個字元了,表示待列印的字串為全0
187 je .full0
188 ;找出連續的0字元, edi做為非0的最高位字元的偏移
189 .go_on_skip:
190 mov cl, [put_int_buffer+edi]
191 inc edi
192 cmp cl, '0'
193 je .skip_prefix_0 ; 繼續判斷下一位字元是否為字元0(不是數位0)
194 dec edi ;edi在上面的inc操作中指向了下一個字元,若當前字元不為'0',要恢復edi指向當前字元
195 jmp .put_each_num
196
197 .full0:
198 mov cl,'0' ; 輸入的數位為全0時,則只列印0
199 .put_each_num:
200 push ecx ; 此時cl中為可列印的字元
201 call put_char
202 add esp, 4
203 inc edi ; 使edi指向下一個字元
204 mov cl, [put_int_buffer+edi] ; 獲取下一個字元到cl暫存器
205 cmp edi,8
206 jl .put_each_num
207 popad
208 ret
1 #ifndef __LIB_KERNEL_PRINT_H
2 #define __LIB_KERNEL_PRINT_H
3 #include "stdint.h"
4 void put_char(uint8_t char_asci);
5 void put_str(char *message);
6 void put_int(uint32_t num);
7 #endif
最後輸入如下命令來編譯print.S:
nasm -f elf -o ./project/lib/kernel/print.o ./project/lib/kernel/print.S
完善了列印函數後,我們現在可以在main函數中實現列印功能了,修改main.c檔案:
1 #include "print.h" 2 int main(void) 3 { 4 put_str("HELLO KERNEL\n"); 5 while(1); 6 return 0; 7 }
前面我們已經將核心程式碼實現完成了,接下來按道理應該和前面一樣,將main.c檔案編譯載入到硬碟中,隨後通過loader來讀取載入該檔案,最終跳轉執行。的確也是如此,不過略有不同。請聽我慢慢講來。
現在我們是main.c檔案,不同於組合程式碼,我們接下來要使用gcc工具將main.c檔案編譯成main.o檔案:
gcc -m32 -I project/lib/kernel/ -c -fno-builtin project/kernel/main.c -o project/kernel/main.o
它只是一個目標檔案,也稱為重定位檔案,重定位檔案指的是檔案裡面所用的符號還沒有安排地址,這些符號的地址將來是要與其他目標檔案「組成」一個可執行檔案時再重定位(編排地址),這裡的符號就是指的所呼叫的函數或使用的變數,看我們的main.c檔案中,在main函數中呼叫了print.h中宣告的put_str函數,所以將來main.o檔案需要和print.o檔案一起組成可執行檔案。
如何「組成」呢?這裡的「組成」其實就是指的C語言程式變成可執行檔案下的四步驟(預處理、編譯、組合和連結)中的連結,Linux下使用的是ld命令來連結,我們是在Linux平臺下的,所以自然使用ld命令:
ld -m elf_i386 -Ttext 0xc0001500 -e main -o project/kernel/kernel.bin project/kernel/main.o project/lib/kernel/print.o
最終生成可執行檔案kernel.bin。它就是我們需要載入到硬碟裡的那個檔案。
到這裡都和前面步驟一致,只是後面loader並不是單純的將kernel.bin檔案拷貝到記憶體某處再跳轉執行。這是因為我們生成的kernel.bin檔案的格式為elf,elf格式的檔案,在檔案最開始有一個名為elf格式頭的部分,該部分詳細包含了整個檔案的資訊,具體內容過多我這裡不再展開講,感興趣的朋友可以參考原書《作業系統真象還原》p213~222,或者百度。所以說如果我們只是單純地跳轉到該檔案的載入處,那麼必定會出現問題,因為該檔案的開始部分並不是可供CPU執行的程式,我們跳轉的地址應該是該檔案的程式部分。這個地址在我們前面連結時已經指定為0xc0001500,因為我們前面已經開啟了分頁機制,所以實際上這個地址對應的是實體地址的0x1500處。
接下來再修改loader.S檔案,增加拷貝核心部分程式碼以及拷貝函數程式碼,為了便於閱讀,我將新程式碼附在了之前的loader.S檔案下,除此之外,boot.inc也有新增的內容。
1 %include "boot.inc"
2 section loader vstart=LOADER_BASE_ADDR
3 LOADER_STACK_TOP equ LOADER_BASE_ADDR
4 jmp loader_start
5
6 ;構建gdt及其內部描述符
7 GDT_BASE: dd 0x00000000
8 dd 0x00000000
9 CODE_DESC: dd 0x0000FFFF
10 dd DESC_CODE_HIGH4
11 DATA_STACK_DESC: dd 0x0000FFFF
12 dd DESC_DATA_HIGH4
13 VIDEO_DESC: dd 0x80000007
14 dd DESC_VIDEO_HIGH4
15
16 GDT_SIZE equ $-GDT_BASE
17 GDT_LIMIT equ GDT_SIZE-1
18 times 60 dq 0 ;此處預留60個描述符的空位
19
20 SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
21 SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
22 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
23
24 ;以下是gdt指標,前2個位元組是gdt界限,後4個位元組是gdt的起始地址
25 gdt_ptr dw GDT_LIMIT
26 dd GDT_BASE
27
28 ;---------------------進入保護模式------------
29 loader_start:
30 ;一、開啟A20地址線
31 in al, 0x92
32 or al, 0000_0010B
33 out 0x92, al
34
35 ;二、載入GDT
36 lgdt [gdt_ptr]
37
38 ;三、cr0第0位(pe)置1
39 mov eax, cr0
40 or eax, 0x00000001
41 mov cr0, eax
42
43 jmp dword SELECTOR_CODE:p_mode_start ;重新整理流水線
44
45 [bits 32]
46 p_mode_start:
47 mov ax, SELECTOR_DATA
48 mov ds, ax
49 mov es, ax
50 mov ss, ax
51 mov esp, LOADER_STACK_TOP
52 mov ax, SELECTOR_VIDEO
53 mov gs, ax
54
55 mov byte [gs:160], 'p'
56
57 ;------------------開啟分頁機制-----------------
58 ;一、建立頁目錄表並初始化頁記憶體點陣圖
59 call setup_page
60
61 ;將描述符表地址及偏移量寫入記憶體gdt_ptr,一會兒用新地址重新載入
62 sgdt [gdt_ptr]
63 ;將gdt描述符中視訊段描述符中的段基址+0xc0000000
64 mov ebx, [gdt_ptr + 2]
65 or dword [ebx + 0x18 + 4], 0xc0000000
66
67 ;將gdt的基址加上0xc0000000使其成為核心所在的高地址
68 add dword [gdt_ptr + 2], 0xc0000000
69
70 add esp, 0xc0000000 ;將棧指標同樣對映到核心地址
71
72 ;二、將頁目錄表地址賦值給cr3
73 mov eax, PAGE_DIR_TABLE_POS
74 mov cr3, eax
75
76 ;三、開啟cr0的pg位
77 mov eax, cr0
78 or eax, 0x80000000
79 mov cr0, eax
80
81 ;在開啟分頁後,用gdt新的地址重新載入
82 lgdt [gdt_ptr]
83 mov byte [gs:160], 'H'
84 mov byte [gs:162], 'E'
85 mov byte [gs:164], 'L'
86 mov byte [gs:166], 'L'
87 mov byte [gs:168], 'O'
88 mov byte [gs:170], ' '
89 mov byte [gs:172], 'P'
90 mov byte [gs:174], 'A'
91 mov byte [gs:176], 'G'
92 mov byte [gs:178], 'E'
93
94 ;---------------------------------------------
95
96 ;--------------------拷貝核心檔案並進入kernel--------------------------
97 mov eax, KERNEL_START_SECTOR ;kernel.bin所在的磁區號 0x09
98 mov ebx, KERNEL_BIN_BASE_ADDR ;從磁碟讀出後,寫入到ebx指定的地址0x70000
99 mov ecx, 200 ;讀入的磁區數
100
101 call rd_disk_m_32
102
103 ;由於一直處在32位元下,原則上不需要強制重新整理,但是以防萬一還是加上
104 ;跳轉到kernel處
105 jmp SELECTOR_CODE:enter_kernel
106
107 enter_kernel:
108 call kernel_init
109 mov esp, 0xc009f000 ;更新棧底指標
110 jmp KERNEL_ENTRY_POINT ;核心地址0xc0001500
111 ;jmp $
112 ;---------------------將kernel.bin中的segment拷貝到指定的地址
113 kernel_init:
114 xor eax, eax
115 xor ebx, ebx ;ebx記錄程式頭表地址
116 xor ecx, ecx ;cx記錄程式頭表中的program header數量
117 xor edx, edx ;dx記錄program header 尺寸,即e_phentsize
118
119 ;偏移檔案42位元組處的屬性是e_phentsize, 表示program header大小
120 mov dx, [KERNEL_BIN_BASE_ADDR + 42]
121
122 ;偏移檔案28位元組處的屬性是e_phoff
123 mov ebx, [KERNEL_BIN_BASE_ADDR + 28]
124
125 add ebx, KERNEL_BIN_BASE_ADDR
126 mov cx, [KERNEL_BIN_BASE_ADDR + 44]
127
128 .each_segment:
129 cmp byte [ebx + 0], PT_NULL
130 je .PTNULL
131
132 ;為函數memcpy壓入引數,引數是從右往左壓入
133 push dword [ebx + 16]
134 mov eax, [ebx + 4]
135 add eax, KERNEL_BIN_BASE_ADDR
136 push eax
137 push dword [ebx + 8]
138 call mem_cpy
139 add esp, 12
140
141 .PTNULL:
142 add ebx, edx
143 loop .each_segment
144 ret
145
146 ;-----------逐位元組拷貝mem_cpy(dst, src, size)
147 mem_cpy:
148 cld
149 push ebp
150 mov ebp, esp
151 push ecx
152 mov edi, [ebp + 8]
153 mov esi, [ebp + 12]
154 mov ecx, [ebp + 16]
155 rep movsb
156
157 pop ecx
158 pop ebp
159 ret
160 ;---------------------------------------------------
161
162 ;--------------函數宣告------------------------
163 ;setup_page:(功能)設定分頁------------
164 setup_page:
165 ;先把頁目錄佔用的空間逐位元組清0
166 mov ecx, 4096
167 mov esi, 0
168 .clear_page_dir:
169 mov byte [PAGE_DIR_TABLE_POS + esi], 0
170 inc esi
171 loop .clear_page_dir
172
173 ;開始建立頁目錄項
174 .create_pde:
175 mov eax, PAGE_DIR_TABLE_POS
176 add eax, 0x1000 ;此時eax為第一個頁表的位置
177 mov ebx, eax
178
179 ;下面將頁目錄項0和0xc00都存為第一個頁表的地址,每個頁表表示4MB記憶體
180 ;頁目錄表的屬性RW和P位為1,US為1,表示使用者屬性,所有特權級別都可以存取
181 or eax, PG_US_U | PG_RW_W | PG_P
182
183 ;在頁目錄表中的第1個目錄項中寫入第一個頁表的地址(0x101000)和屬性
184 mov [PAGE_DIR_TABLE_POS + 0x0], eax
185
186 mov [PAGE_DIR_TABLE_POS + 0xc00], eax
187
188 ;使最後一個目錄項指向頁目錄表自己的地址
189 sub eax, 0x1000
190 mov [PAGE_DIR_TABLE_POS + 4092], eax
191
192 ;下面建立頁表項(PTE)
193 mov ecx, 256 ;1M低端記憶體/每頁大小4K=256
194 mov esi, 0
195 mov edx, PG_US_U | PG_RW_W | PG_P
196 .create_pte: ;建立page table entry
197 mov [ebx + esi*4], edx
198 add edx, 4096
199 inc esi
200 loop .create_pte
201
202 ;建立核心其他頁表的PDE
203 mov eax, PAGE_DIR_TABLE_POS
204 add eax, 0x2000 ;此時eax為第二個頁表的位置
205 or eax, PG_US_U | PG_RW_W | PG_P
206 mov ebx, PAGE_DIR_TABLE_POS
207 mov ecx, 254 ;範圍為第769~1022的所有目錄項數量
208 mov esi, 769
209 .create_kernel_pde:
210 mov [ebx + esi*4], eax
211 inc esi
212 add eax, 0x1000
213 loop .create_kernel_pde
214 ret
215
216 ;rd_disk_m_32:(功能)讀取硬碟n個磁區------------
217 rd_disk_m_32:
218 mov esi,eax ;備份eax,eax中存放了磁區號
219 mov di,cx ;備份cx,cx中存放待讀入的磁區數
220
221 ;讀寫硬碟:
222 ;第一步:設定要讀取的磁區數
223 mov dx,0x1f2
224 mov al,cl
225 out dx,al
226
227 mov eax,esi
228
229 ;第二步:將lba地址存入到0x1f3 ~ 0x1f6
230 ;lba地址7-0位寫入埠0x1f3
231 mov dx,0x1f3
232 out dx,al
233
234 ;lba地址15-8位元寫入埠0x1f4
235 mov cl,8
236 shr eax,cl
237 mov dx,0x1f4
238 out dx,al
239
240 ;lba地址23-16位元寫入埠0x1f5
241 shr eax,cl
242 mov dx,0x1f5
243 out dx,al
244
245 shr eax,cl
246 and al,0x0f
247 or al,0xe0
248 mov dx,0x1f6
249 out dx,al
250
251 ;第三步:向0x1f7埠寫入讀命令,0x20
252 mov dx,0x1f7
253 mov al,0x20
254 out dx,al
255
256 ;第四步:檢測硬碟狀態
257 .not_ready:
258 nop
259 in al,dx
260 and al,0x88
261 cmp al,0x08
262 jnz .not_ready
263
264 ;第五步:從0x1f0埠讀資料
265 mov ax,di
266 mov dx,256
267 mul dx
268 mov cx,ax
269 ;di為要讀取的磁區數,一個磁區共有512位元組,每次讀入一個字,總共需要
270 ;di*512/2次,所以di*256
271 mov dx,0x1f0
272 .go_on_read:
273 in ax,dx
274 mov [ebx],ax
275 add ebx,2
276 loop .go_on_read
277 ret
278 ;----------------------------------------------
1 ;--------------------loader和kernel ---------------
2 LOADER_BASE_ADDR equ 0x900
3 LOADER_START_SECTOR equ 0x2
4 PAGE_DIR_TABLE_POS equ 0x100000
5 KERNEL_START_SECTOR equ 0x9
6 KERNEL_BIN_BASE_ADDR equ 0x70000
7 KERNEL_ENTRY_POINT equ 0xc0001500
8 PT_NULL equ 0
9 ;-------------------gdt描述符屬性------------------
10 ;使用平坦模型,所以需要將段大小設定為4GB
11 DESC_G_4K equ 100000000000000000000000b ;表示段大小為4G
12 DESC_D_32 equ 10000000000000000000000b ;表示運算元與有效地址均為32位元
13 DESC_L equ 0000000000000000000000b ;表示32位元程式碼段
14 DESC_AVL equ 000000000000000000000b ;忽略
15 DESC_LIMIT_CODE2 equ 11110000000000000000b ;程式碼段的段界限的第2部分
16 DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ;相同的值 資料段與程式碼段段界限相同
17 DESC_LIMIT_VIDEO2 equ 00000000000000000000b ;第16-19位 視訊記憶體區描述符VIDEO2 書上後面的0少打了一位 這裡的全是0為高位 低位即可表示段基址
18 DESC_P equ 1000000000000000b ;p判斷段是否在記憶體中,1表示在記憶體中
19 DESC_DPL_0 equ 000000000000000b
20 DESC_DPL_1 equ 010000000000000b
21 DESC_DPL_2 equ 100000000000000b
22 DESC_DPL_3 equ 110000000000000b
23 DESC_S_CODE equ 1000000000000b ;S等於1表示非系統段,0表示系統段
24 DESC_S_DATA equ DESC_S_CODE
25 DESC_S_sys equ 0000000000000b
26 DESC_TYPE_CODE equ 100000000000b ;x=1,c=0,r=0,a=0 程式碼段是可執行的,非一致性,不可讀,已存取位a清0
27 DESC_TYPE_DATA equ 001000000000b ;x=0,e=0,w=1,a=0 資料段是不可執行的,向上拓展,可寫,已存取位a清0
28
29 DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00 ;程式碼段的高四個位元組內容
30 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00 ;資料段的高四個位元組內容
31
32 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B
33
34
35 ;------------選擇子屬性------------
36 RPL0 equ 00b
37 RPL1 equ 01b
38 RPL2 equ 10b
39 RPL3 equ 11b
40 TI_GDT equ 000b
41 TI_LDT equ 100b
42
43 ;---------------頁表相關屬性----------------
44 PG_P equ 1b
45 PG_RW_R equ 00b
46 PG_RW_W equ 10b
47 PG_US_S equ 000b
48 PG_US_U equ 100b
來看程式碼,首先呼叫函數rd_disk_m_32將kernel.bin檔案從硬碟拷貝到地址KERNEL_BIN_BASE_ADDR,也就是0x70000處。
enter_kernel是進入核心的函數,首先呼叫kernel_init函數,在該函數中其實就是對前面拷貝到地址0x70000處的kernel.bin檔案進行解析,將其中的程式部分拷貝到地址0xc0001500處,隨後再跳轉過去。
這裡講解一下為什麼是地址0xc0001500處,實體記憶體中的0x900是loader.bin的載入地址,在該地址開始部分是GDT,GDT以後會被一直使用不能被覆蓋,這裡預計loader.bin的大小不會超過2000位元組,前面我們有說到核心是要放在loader的上面的,因為核心會不斷增大,所以我們可選的實體地址是0x900+2000=0x10d0,湊個整數就選了0x1500作為核心的入口地址,不必好奇為什麼是這個地址,只是憑感覺就這麼設計了。因為我們的記憶體相對來說比較寬鬆,沒必要那麼緊湊。
進入核心後,我們修改了棧頂指標,不再是以前的0x900,檢視記憶體佈局,我們可以知道在地址0x7E00~0x9FBFF之間,還有約630KB的空間未被使用,因此我們選用地址0x9F000作為棧頂。考慮到以後核心的拓展,預計也就只有70KB,我們核心從0x1500開始,棧向下發展,我們的核心是不會和棧發生衝突的。
首先將前面生成的可執行檔案kernel.bin,也就是我們最終的核心檔案使用dd命令將其寫入到硬碟中去,這裡記得還要重新編譯loader.S以及載入loader.bin檔案,因為loader.S也被修改了。
dd if=./project/kernel/kernel.bin of=./hd60M.img bs=512 count=200 seek=9 conv=notrunc
這裡我們count的引數為200,意為向硬碟一次性寫入兩百個磁區,當然我們的核心檔案現在還沒有這麼大,Seek=9表示跳過前面9個磁區,從第10個磁區開始存放(以LBA法計算)。啟動boch,最終得到如下畫面:
說明我們的核心檔案成功寫入並且載入成功了。雖然只是一小步,但是卻是我們整個作業系統學習的一大步,到了這裡我們整個作業系統的基本框架算是搭建完畢了,接下來就是不斷地進行完善核心檔案即可。
欲知後事如何,請看下回分解。