为了尽快进入内核,并没有让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 思路比较清晰,首先保存系统参数,这个步骤是从linux中拿过来的,会在以后分页机制以及tty中用到;其次是将内核移动到内存地址0x0的位置,这部分需要参照
posted on 2011-11-22 20:08
myjfm 阅读(733)
评论(0) 编辑 收藏 引用 所属分类:
操作系统