WinixJ---boot/loader.s文件详解
为了尽快进入内核,并没有让loader做太多工作,它的工作其实主要有三项:
1、保存一些系统参数,如光标位置、扩展内存大小、显示模式、显存大小以及硬盘参数等等
2、将内核移动到指定位置,这里移动到0x0起始的位置
3、切换进入保护模式
还是先贴代码:
1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
2 ; 该文件主要作用是获取一些系统参数,将参数保存在0xf000:0x0的位置,然后准备gdt,
3 ; 并切换到保护模式,不过该gdt只是临时性的,在进入kernel之后还会重新设置gdt;
4 ; 在保护模式下将kernel从原来位置加载到指定位置,这里我们假定指定位置为0x0开始的地方。
5 ; 移动kernel文件的方法因为文件的类型不同而不同,在linux3. 0.0 .12下,gcc版本4.5编译
6 ; 为ELF格式的二进制文件,将program segment按照编译时指定的虚拟地址加载到相应位置。
7 ; kernel的入口地址为KERNEL_ADDR,注意该值必须和在链接内核时指定的入口地址一致,
8 ; 否则加载内核失败;成功加载内核后便跳转到内核执行。
9 ;
10 ; Author :
11 ; v0. 01 2011 / 11 / 22
12 ;
13 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14 jmp start
15
16 PARAM_ADDR equ 0xf000 ; 系统参数保存在的位置的段基址
17 KERNEL_ADDR equ 0x0 ; kernel入口地址,注意,此处的值一定要和编译kernel文件时指定的入口地址相同
18
19 [SECTION .code16]
20 [BITS 16 ]
21 start:
22 mov ax, cs ; 当前cs值为: 0x8000
23 mov ds, ax
24 mov es, ax
25 sub ax, 0x20
26 mov ss, ax ; ss地址为0x7fe0
27 mov sp, 0x200 ; 堆栈大小为512B = 0 .5KB
28
29 ; 获取当前光标位置
30 mov ah, 0x03
31 xor bh, bh
32 int 0x10
33
34 ; 打印一些信息
35 mov ax, cs
36 mov es, ax
37 mov cx, MSG1_LEN
38 mov bx, 0x0007
39 mov bp, MSG1 ; 显示字符串 " Loading "
40 mov ax, 0x1301
41 int 0x10
42
43 push es ; 先保存es寄存器的值
44 mov ax, 0x0
45 mov es, ax
46 mov byte al, [es: 0x7dfb ] ; 取出保存在boot文件尾部的loader文件大小值
47 mov byte [LOADER_LEN], al
48 mov word ax, [es: 0x7dfc ]
49 mov word [KERNEL_LEN], ax ; 取出保存在boot文件尾部的kernel文件大小值
50 pop es
51
52 ; 取得kernel的入口地址以及第一个program segment在内存中的物理地址
53 mov bx, es
54 xor ax, ax
55 mov byte al, [LOADER_LEN]
56 shl ax, 0x05
57 add bx, ax
58 mov es, bx ; es指向kernel文件头部
59 mov si, MSG3
60 mov edi, 0x0
61 mov cx, 0x06
62 cld
63 repe cmpsb
64 cmp cx, 0x0 ; 如果kernel文件头部的魔数确实为“kernel”,则认为是合法的kernel文件
65 jne no_kernel
66 mov dword eax, [es:di]
67 cmp eax, KERNEL_ADDR
68 jne no_kernel
69 mov dword [KERNEL_ENTRY], eax ; 取得kernel的入口地址
70 add di, 0x04
71 mov word ax, [es:di]
72 mov word [PROG_SEG_NUM], ax ; 取得kernel文件中program segment的数量
73 add di, 0x02
74 xor eax, eax
75 mov ax, es
76 shl eax, 4
77 and edi, 0x0000ffff
78 add eax, edi
79 mov dword [PROG_SEG_FIRST], eax ; 保存kernel中第一个program segment在内存中的物理地址
80
81 ; 下面的操作是从linux0.11中“拿”来的
82 ; 取得当前光标位置
83 push ds
84 push es
85
86 mov ax, PARAM_ADDR
87 mov ds, ax
88 mov ah, 0x03
89 xor bh, bh
90 int 0x10
91
92 ; 获得扩展内存大小,即1MB以后的内存大小,以KB计
93 mov word [ 0 ], dx
94 mov ah, 0x88
95 int 0x15
96 mov word [ 2 ], ax
97
98 ; 显示卡当前显示模式
99 mov ah, 0x0f
100 int 0x10
101 mov word [ 4 ], bx
102 mov word [ 6 ], ax
103
104 ; 检查显示方式(EGA / VGA)并取参数
105 mov ah, 0x12
106 mov bl, 0x10
107 int 0x10
108 mov word [ 8 ], ax
109 mov word [ 10 ], bx
110 mov word [ 12 ], cx
111
112 ; 取第一块硬盘hd0的信息(复制硬盘参数表)。
113 ; 第一个硬盘参数表的首地址竟然是中断向量0x41的向量值!
114 ; 而第二块硬盘参数表紧接着第一个表的后面。
115 ; 中断向量0x46的向量值也指向这第二块硬盘的参数表首地址。
116 ; 表的长度为16字节( 0x10 )。
117 mov ax, 0x0000
118 mov ds, ax
119 lds si, [ 4 * 0x41 ]
120 mov ax, PARAM_ADDR
121 mov es, ax
122 mov di, 0x0080
123 mov cx, 0x10
124 rep movsb
125
126 ; 取第二块硬盘hd1的信息
127 mov ax, 0x0000
128 mov ds, ax
129 lds si, [ 4 * 0x46 ]
130 mov ax, PARAM_ADDR
131 mov es, ax
132 mov di, 0x0090
133 mov cx, 0x10
134 rep movsb
135
136 ; 检查系统是否存在第二块硬盘,不存在则将第二个表清零。
137 mov ax, 0x1500
138 mov dl, 0x81
139 int 0x13
140 jc no_disk1
141 cmp ah, 0x03
142 je is_disk1
143 no_disk1:
144 mov ax, PARAM_ADDR
145 mov es, ax
146 mov di, 0x0090
147 mov cx, 0x10
148 mov ax, 0x00
149 rep movsb
150
151 is_disk1:
152 pop es
153 pop ds
154 jmp setup_pm
155
156 no_kernel:
157 ; 获取当前光标位置
158 mov ah, 0x03
159 xor bh, bh
160 int 0x10
161
162 ; 打印一些信息
163 mov ax, cs
164 mov es, ax
165 mov cx, MSG2_LEN
166 mov bx, 0x0007
167 mov bp, MSG2 ; 显示字符串 " [[ NO KERNEL ]]\r\nPress any key to reboot now "
168 mov ax, 0x1301
169 int 0x10
170
171 xor ax, ax
172 int 0x16 ; 等待用户输入任意一个键盘字符
173 int 0x19 ; 系统重启
174
175 setup_pm:
176 ; 将保护模式代码段基地址填入GDT1描述符中
177 xor eax, eax
178 mov ax, cs
179 shl eax, 4
180 add eax, PMCODE
181 mov word [CS_TMP + 2 ], ax
182 shr eax, 16
183 mov byte [CS_TMP + 4 ], al
184 mov byte [CS_TMP + 7 ], ah
185
186 ; 将保护模式数据段基地址填入GDT1描述符中
187 xor eax, eax
188 mov ax, ds
189 shl eax, 4
190 add eax, PMDATA
191 mov word [DS_TMP + 2 ], ax
192 shr eax, 16
193 mov byte [DS_TMP + 4 ], al
194 mov byte [DS_TMP + 7 ], ah
195
196 ; 填充GDTPTR结构体
197 ; 该结构体将被加载进gdtr寄存器
198 xor eax, eax
199 mov ax, ds
200 shl eax, 4
201 add eax, GDT
202 mov dword [GDTPTR + 2 ], eax
203
204 lgdt [GDTPTR]
205
206 ; 关闭中断
207 cli
208
209 ; 通过设置键盘控制器的端口值来打开A20地址线
210 call empty_8042
211 mov al, 0xd1
212 out 0x64 , al
213 call empty_8042
214 mov al, 0xdf
215 out 0x60 , al
216 call empty_8042
217
218 ; 修改cr0寄存器的最后一位PE位
219 ; 注意lmsw指令仅仅对cr0寄存器的最后四位有影响
220 mov eax, cr0
221 or eax, 0x01
222 lmsw ax
223
224 ; 真正跳转到保护模式!!!!!!
225 jmp dword 0x08 : 0x00
226
227 empty_8042:
228 nop
229 nop
230 in al, 0x64
231 test al, 0x02
232 jnz empty_8042
233 ret
234
235 [section .pmcode]
236 align 32
237 [bits 32 ]
238 PMCODE:
239 mov ax, 0x10
240 mov ds, ax
241 xor ecx, ecx
242 mov word cx, [pm_PROG_SEG_NUM] ; 取得kernel文件中program segment的段数
243 mov dword esi, [pm_PROG_SEG_FIRST] ; 取得kernel文件中的第一个program segment的地址
244 mov ax, 0x20
245 mov ds, ax
246 mov es, ax
247 move_kernel:
248 push ecx
249 mov ecx, [esi] ; 取得program segment在文件中的大小
250 add esi, 4
251 mov edi, [esi] ; 取得段在内存中的地址
252 add esi, 4
253 cld
254 rep movsb
255 pop ecx
256 loop move_kernel
257
258 mov ax, 0x20
259 mov ds, ax
260 mov es, ax
261 mov ss, ax
262 mov fs, ax
263 mov gs, ax
264 jmp 0x18 :KERNEL_ADDR
265
266 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
267 ; GDT描述符中由低到高的含义如下:
268 ; BYTE7 || BYTE6 BYTE5 || BYTE4 BYTE3 BYTE2 || BYTE1 BYTE0 ||
269 ; 段基地址 || 属性 || 段基址 || 段界限 ||
270 ; ( 31 - 24 ) || || ( 23 - 0 ) || ( 15 - 0 ) ||
271 ;
272 ; 属性含义如下:
273 ; 7 || 6 || 5 || 4 || 3 2 1 0 || 7 || 6 || 5 || 4 3 2 1 0 ||
274 ; G || D / B || 0 || AVL || 段界限( 19 - 16 ) || P || DPL || S || TYPE ||
275 ;
276 ; G位:段界限的粒度,0为字节,1为4KB
277 ; D / B;在可执行代码段中,这一位叫做D位:
278 ; D = 1时指令使用32位地址及32位或8位操作数;D = 0时使用16位地址及16位或8位操作数
279 ; 在数据段描述符中,这一位叫做B位:
280 ; B = 1时段上部界限为4GB;B = 0时段上部界限为64KB
281 ; 在堆栈段描述符中,这一位叫做B位:
282 ; B = 1时隐式的堆栈访问指令(比如push、pop、call等)使用32位堆栈指针寄存器esp
283 ; B = 0时隐式的堆栈访问指令使用16位堆栈指针寄存器sp
284 ; AVL: 保留
285 ; P位: 1代表段在内存中存在;0代表段在内存中不存在
286 ; DPL:描述符特权级
287 ; S位:1代表是数据段或者代码段描述符;0代表是系统段或者门描述符
288 ;TYPE:说明描述符类型
289 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
290
291 [section .pmdata]
292 align 32
293 [bits 32 ]
294 PMDATA:
295 ; 第一个描述符空,不使用
296 GDT: db 0x00 , 0x00
297 db 0x00 , 0x00 , 0x00
298 db 0x00 , 0x00
299 db 0x00
300
301 ; 第二个描述符为保护模式代码所在的段
302 ; 段基地址在切换到保护模式前填入;
303 ; 界限为0xfffff,段界限粒度为4KB,因此段长为4GB;
304 ; 是可执行可读的32位代码段
305 CS_TMP: db 0xff , 0xff
306 db 0x00 , 0x00 , 0x00
307 db 0x9a , 0xcf
308 db 0x00
309
310 ; 第三个描述符为切换到保护模式用到的数据段
311 ; 段基地址为PMDATA,在切换到保护模式前填入;
312 ; 界限为0xfffff,段界限粒度为4KB,因此段长为4GB;
313 ; 是可读可写32位数据段
314 DS_TMP: db 0xff , 0xff
315 db 0x00 , 0x00 , 0x00
316 db 0x92 , 0xcf
317 db 0x00
318
319 ; 第四个描述符的段基地址为0;
320 ; 界限为0xfffff,段界限粒度为4KB;
321 ; 是可执行可读的32位代码段
322 CODE: db 0xff , 0xff
323 db 0x00 , 0x00 , 0x00
324 db 0x9a , 0xcf
325 db 0x00
326
327 ; 第五个描述符的段基地址为0;
328 ; 界限为0xfffff,段界限粒度为4KB;
329 ; 是可读可写32位数据段
330 DATA: db 0xff , 0xff
331 db 0x00 , 0x00 , 0x00
332 db 0x92 , 0xcf
333 db 0x00
334
335 GDT_LEN equ $ - GDT
336 GDTPTR dw GDT_LEN - 1
337 dd 0
338
339 ; 在实模式下直接使用LOADER_LEN等标号就能取出内存中的内容
340 ; 在保护模式下想要取LOADER_LEN等内存中的内容需要使用基于段基地址的偏移
341 pm_LOADER_LEN equ $ - PMDATA
342 LOADER_LEN: db 0 ; 记录loader文件的大小
343
344 pm_KERNEL_LEN equ $ - PMDATA
345 KERNEL_LEN: dw 0 ; 记录kernel文件的大小,注意,这里指的是从System.Image解压出来前的大小
346
347 pm_KERNEL_ENTRY equ $ - PMDATA
348 KERNEL_ENTRY: dd 0 ; 记录kernel的入口虚拟地址
349
350 pm_PROG_SEG_FIRST equ $ - PMDATA
351 PROG_SEG_FIRST dd 0 ; 记录kerne文件中第一个program segment在内存中的物理地址
352
353 pm_PROG_SEG_NUM equ $ - PMDATA
354 PROG_SEG_NUM dw 0 ; 记录kerne文件中program segment的数量
355
356 ; 以下为在实模式下需要打印的字符串
357 MSG1: db 13 , 10 , " Loading " , 13 , 10
358 MSG1_LEN equ $ - MSG1
359
360 MSG2: db 13 , 10 , " [[ NO KERNEL ]] " , 13 , 10 , " Press any key to reboot now " , 13 , 10
361 MSG2_LEN equ $ - MSG2
362
363 MSG3: db " kernel "
364 MSG3_LEN equ $ - MSG3
365
2 ; 该文件主要作用是获取一些系统参数,将参数保存在0xf000:0x0的位置,然后准备gdt,
3 ; 并切换到保护模式,不过该gdt只是临时性的,在进入kernel之后还会重新设置gdt;
4 ; 在保护模式下将kernel从原来位置加载到指定位置,这里我们假定指定位置为0x0开始的地方。
5 ; 移动kernel文件的方法因为文件的类型不同而不同,在linux3. 0.0 .12下,gcc版本4.5编译
6 ; 为ELF格式的二进制文件,将program segment按照编译时指定的虚拟地址加载到相应位置。
7 ; kernel的入口地址为KERNEL_ADDR,注意该值必须和在链接内核时指定的入口地址一致,
8 ; 否则加载内核失败;成功加载内核后便跳转到内核执行。
9 ;
10 ; Author :
11 ; v0. 01 2011 / 11 / 22
12 ;
13 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
14 jmp start
15
16 PARAM_ADDR equ 0xf000 ; 系统参数保存在的位置的段基址
17 KERNEL_ADDR equ 0x0 ; kernel入口地址,注意,此处的值一定要和编译kernel文件时指定的入口地址相同
18
19 [SECTION .code16]
20 [BITS 16 ]
21 start:
22 mov ax, cs ; 当前cs值为: 0x8000
23 mov ds, ax
24 mov es, ax
25 sub ax, 0x20
26 mov ss, ax ; ss地址为0x7fe0
27 mov sp, 0x200 ; 堆栈大小为512B = 0 .5KB
28
29 ; 获取当前光标位置
30 mov ah, 0x03
31 xor bh, bh
32 int 0x10
33
34 ; 打印一些信息
35 mov ax, cs
36 mov es, ax
37 mov cx, MSG1_LEN
38 mov bx, 0x0007
39 mov bp, MSG1 ; 显示字符串 " Loading "
40 mov ax, 0x1301
41 int 0x10
42
43 push es ; 先保存es寄存器的值
44 mov ax, 0x0
45 mov es, ax
46 mov byte al, [es: 0x7dfb ] ; 取出保存在boot文件尾部的loader文件大小值
47 mov byte [LOADER_LEN], al
48 mov word ax, [es: 0x7dfc ]
49 mov word [KERNEL_LEN], ax ; 取出保存在boot文件尾部的kernel文件大小值
50 pop es
51
52 ; 取得kernel的入口地址以及第一个program segment在内存中的物理地址
53 mov bx, es
54 xor ax, ax
55 mov byte al, [LOADER_LEN]
56 shl ax, 0x05
57 add bx, ax
58 mov es, bx ; es指向kernel文件头部
59 mov si, MSG3
60 mov edi, 0x0
61 mov cx, 0x06
62 cld
63 repe cmpsb
64 cmp cx, 0x0 ; 如果kernel文件头部的魔数确实为“kernel”,则认为是合法的kernel文件
65 jne no_kernel
66 mov dword eax, [es:di]
67 cmp eax, KERNEL_ADDR
68 jne no_kernel
69 mov dword [KERNEL_ENTRY], eax ; 取得kernel的入口地址
70 add di, 0x04
71 mov word ax, [es:di]
72 mov word [PROG_SEG_NUM], ax ; 取得kernel文件中program segment的数量
73 add di, 0x02
74 xor eax, eax
75 mov ax, es
76 shl eax, 4
77 and edi, 0x0000ffff
78 add eax, edi
79 mov dword [PROG_SEG_FIRST], eax ; 保存kernel中第一个program segment在内存中的物理地址
80
81 ; 下面的操作是从linux0.11中“拿”来的
82 ; 取得当前光标位置
83 push ds
84 push es
85
86 mov ax, PARAM_ADDR
87 mov ds, ax
88 mov ah, 0x03
89 xor bh, bh
90 int 0x10
91
92 ; 获得扩展内存大小,即1MB以后的内存大小,以KB计
93 mov word [ 0 ], dx
94 mov ah, 0x88
95 int 0x15
96 mov word [ 2 ], ax
97
98 ; 显示卡当前显示模式
99 mov ah, 0x0f
100 int 0x10
101 mov word [ 4 ], bx
102 mov word [ 6 ], ax
103
104 ; 检查显示方式(EGA / VGA)并取参数
105 mov ah, 0x12
106 mov bl, 0x10
107 int 0x10
108 mov word [ 8 ], ax
109 mov word [ 10 ], bx
110 mov word [ 12 ], cx
111
112 ; 取第一块硬盘hd0的信息(复制硬盘参数表)。
113 ; 第一个硬盘参数表的首地址竟然是中断向量0x41的向量值!
114 ; 而第二块硬盘参数表紧接着第一个表的后面。
115 ; 中断向量0x46的向量值也指向这第二块硬盘的参数表首地址。
116 ; 表的长度为16字节( 0x10 )。
117 mov ax, 0x0000
118 mov ds, ax
119 lds si, [ 4 * 0x41 ]
120 mov ax, PARAM_ADDR
121 mov es, ax
122 mov di, 0x0080
123 mov cx, 0x10
124 rep movsb
125
126 ; 取第二块硬盘hd1的信息
127 mov ax, 0x0000
128 mov ds, ax
129 lds si, [ 4 * 0x46 ]
130 mov ax, PARAM_ADDR
131 mov es, ax
132 mov di, 0x0090
133 mov cx, 0x10
134 rep movsb
135
136 ; 检查系统是否存在第二块硬盘,不存在则将第二个表清零。
137 mov ax, 0x1500
138 mov dl, 0x81
139 int 0x13
140 jc no_disk1
141 cmp ah, 0x03
142 je is_disk1
143 no_disk1:
144 mov ax, PARAM_ADDR
145 mov es, ax
146 mov di, 0x0090
147 mov cx, 0x10
148 mov ax, 0x00
149 rep movsb
150
151 is_disk1:
152 pop es
153 pop ds
154 jmp setup_pm
155
156 no_kernel:
157 ; 获取当前光标位置
158 mov ah, 0x03
159 xor bh, bh
160 int 0x10
161
162 ; 打印一些信息
163 mov ax, cs
164 mov es, ax
165 mov cx, MSG2_LEN
166 mov bx, 0x0007
167 mov bp, MSG2 ; 显示字符串 " [[ NO KERNEL ]]\r\nPress any key to reboot now "
168 mov ax, 0x1301
169 int 0x10
170
171 xor ax, ax
172 int 0x16 ; 等待用户输入任意一个键盘字符
173 int 0x19 ; 系统重启
174
175 setup_pm:
176 ; 将保护模式代码段基地址填入GDT1描述符中
177 xor eax, eax
178 mov ax, cs
179 shl eax, 4
180 add eax, PMCODE
181 mov word [CS_TMP + 2 ], ax
182 shr eax, 16
183 mov byte [CS_TMP + 4 ], al
184 mov byte [CS_TMP + 7 ], ah
185
186 ; 将保护模式数据段基地址填入GDT1描述符中
187 xor eax, eax
188 mov ax, ds
189 shl eax, 4
190 add eax, PMDATA
191 mov word [DS_TMP + 2 ], ax
192 shr eax, 16
193 mov byte [DS_TMP + 4 ], al
194 mov byte [DS_TMP + 7 ], ah
195
196 ; 填充GDTPTR结构体
197 ; 该结构体将被加载进gdtr寄存器
198 xor eax, eax
199 mov ax, ds
200 shl eax, 4
201 add eax, GDT
202 mov dword [GDTPTR + 2 ], eax
203
204 lgdt [GDTPTR]
205
206 ; 关闭中断
207 cli
208
209 ; 通过设置键盘控制器的端口值来打开A20地址线
210 call empty_8042
211 mov al, 0xd1
212 out 0x64 , al
213 call empty_8042
214 mov al, 0xdf
215 out 0x60 , al
216 call empty_8042
217
218 ; 修改cr0寄存器的最后一位PE位
219 ; 注意lmsw指令仅仅对cr0寄存器的最后四位有影响
220 mov eax, cr0
221 or eax, 0x01
222 lmsw ax
223
224 ; 真正跳转到保护模式!!!!!!
225 jmp dword 0x08 : 0x00
226
227 empty_8042:
228 nop
229 nop
230 in al, 0x64
231 test al, 0x02
232 jnz empty_8042
233 ret
234
235 [section .pmcode]
236 align 32
237 [bits 32 ]
238 PMCODE:
239 mov ax, 0x10
240 mov ds, ax
241 xor ecx, ecx
242 mov word cx, [pm_PROG_SEG_NUM] ; 取得kernel文件中program segment的段数
243 mov dword esi, [pm_PROG_SEG_FIRST] ; 取得kernel文件中的第一个program segment的地址
244 mov ax, 0x20
245 mov ds, ax
246 mov es, ax
247 move_kernel:
248 push ecx
249 mov ecx, [esi] ; 取得program segment在文件中的大小
250 add esi, 4
251 mov edi, [esi] ; 取得段在内存中的地址
252 add esi, 4
253 cld
254 rep movsb
255 pop ecx
256 loop move_kernel
257
258 mov ax, 0x20
259 mov ds, ax
260 mov es, ax
261 mov ss, ax
262 mov fs, ax
263 mov gs, ax
264 jmp 0x18 :KERNEL_ADDR
265
266 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
267 ; GDT描述符中由低到高的含义如下:
268 ; BYTE7 || BYTE6 BYTE5 || BYTE4 BYTE3 BYTE2 || BYTE1 BYTE0 ||
269 ; 段基地址 || 属性 || 段基址 || 段界限 ||
270 ; ( 31 - 24 ) || || ( 23 - 0 ) || ( 15 - 0 ) ||
271 ;
272 ; 属性含义如下:
273 ; 7 || 6 || 5 || 4 || 3 2 1 0 || 7 || 6 || 5 || 4 3 2 1 0 ||
274 ; G || D / B || 0 || AVL || 段界限( 19 - 16 ) || P || DPL || S || TYPE ||
275 ;
276 ; G位:段界限的粒度,0为字节,1为4KB
277 ; D / B;在可执行代码段中,这一位叫做D位:
278 ; D = 1时指令使用32位地址及32位或8位操作数;D = 0时使用16位地址及16位或8位操作数
279 ; 在数据段描述符中,这一位叫做B位:
280 ; B = 1时段上部界限为4GB;B = 0时段上部界限为64KB
281 ; 在堆栈段描述符中,这一位叫做B位:
282 ; B = 1时隐式的堆栈访问指令(比如push、pop、call等)使用32位堆栈指针寄存器esp
283 ; B = 0时隐式的堆栈访问指令使用16位堆栈指针寄存器sp
284 ; AVL: 保留
285 ; P位: 1代表段在内存中存在;0代表段在内存中不存在
286 ; DPL:描述符特权级
287 ; S位:1代表是数据段或者代码段描述符;0代表是系统段或者门描述符
288 ;TYPE:说明描述符类型
289 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
290
291 [section .pmdata]
292 align 32
293 [bits 32 ]
294 PMDATA:
295 ; 第一个描述符空,不使用
296 GDT: db 0x00 , 0x00
297 db 0x00 , 0x00 , 0x00
298 db 0x00 , 0x00
299 db 0x00
300
301 ; 第二个描述符为保护模式代码所在的段
302 ; 段基地址在切换到保护模式前填入;
303 ; 界限为0xfffff,段界限粒度为4KB,因此段长为4GB;
304 ; 是可执行可读的32位代码段
305 CS_TMP: db 0xff , 0xff
306 db 0x00 , 0x00 , 0x00
307 db 0x9a , 0xcf
308 db 0x00
309
310 ; 第三个描述符为切换到保护模式用到的数据段
311 ; 段基地址为PMDATA,在切换到保护模式前填入;
312 ; 界限为0xfffff,段界限粒度为4KB,因此段长为4GB;
313 ; 是可读可写32位数据段
314 DS_TMP: db 0xff , 0xff
315 db 0x00 , 0x00 , 0x00
316 db 0x92 , 0xcf
317 db 0x00
318
319 ; 第四个描述符的段基地址为0;
320 ; 界限为0xfffff,段界限粒度为4KB;
321 ; 是可执行可读的32位代码段
322 CODE: db 0xff , 0xff
323 db 0x00 , 0x00 , 0x00
324 db 0x9a , 0xcf
325 db 0x00
326
327 ; 第五个描述符的段基地址为0;
328 ; 界限为0xfffff,段界限粒度为4KB;
329 ; 是可读可写32位数据段
330 DATA: db 0xff , 0xff
331 db 0x00 , 0x00 , 0x00
332 db 0x92 , 0xcf
333 db 0x00
334
335 GDT_LEN equ $ - GDT
336 GDTPTR dw GDT_LEN - 1
337 dd 0
338
339 ; 在实模式下直接使用LOADER_LEN等标号就能取出内存中的内容
340 ; 在保护模式下想要取LOADER_LEN等内存中的内容需要使用基于段基地址的偏移
341 pm_LOADER_LEN equ $ - PMDATA
342 LOADER_LEN: db 0 ; 记录loader文件的大小
343
344 pm_KERNEL_LEN equ $ - PMDATA
345 KERNEL_LEN: dw 0 ; 记录kernel文件的大小,注意,这里指的是从System.Image解压出来前的大小
346
347 pm_KERNEL_ENTRY equ $ - PMDATA
348 KERNEL_ENTRY: dd 0 ; 记录kernel的入口虚拟地址
349
350 pm_PROG_SEG_FIRST equ $ - PMDATA
351 PROG_SEG_FIRST dd 0 ; 记录kerne文件中第一个program segment在内存中的物理地址
352
353 pm_PROG_SEG_NUM equ $ - PMDATA
354 PROG_SEG_NUM dw 0 ; 记录kerne文件中program segment的数量
355
356 ; 以下为在实模式下需要打印的字符串
357 MSG1: db 13 , 10 , " Loading " , 13 , 10
358 MSG1_LEN equ $ - MSG1
359
360 MSG2: db 13 , 10 , " [[ NO KERNEL ]] " , 13 , 10 , " Press any key to reboot now " , 13 , 10
361 MSG2_LEN equ $ - MSG2
362
363 MSG3: db " kernel "
364 MSG3_LEN equ $ - MSG3
365
思路比较清晰,首先保存系统参数,这个步骤是从linux中拿过来的,会在以后分页机制以及tty中用到;其次是将内核移动到内存地址0x0的位置,这部分需要参照
http://www.cppblog.com/myjfm/archive/2011/11/20/160556.html和 http://www.cppblog.com/myjfm/archive/2011/11/20/160558.html两部分,这两部分是kernel的组织方式,将ELF格式的kernel分program segment移动到指定位置;最后是切换进入保护模式,这里需要加载gdt,loader准备了5个全局段描述符,其中两个是专门为刚刚切换到保护模式后运行所准备的代码段和数据段描述符。两外两个是从绝对物理地址0x0开始,大小为4G的代码段和数据段描述符,不过等到进入kernel还需要重新设置段描述符,因此这只是权易之计。