随笔-80  评论-24  文章-0  trackbacks-0
为了尽快进入内核,并没有让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 1310"Loading "1310
358 MSG1_LEN            equ $ - MSG1
359 
360 MSG2:                db 1310"[[ NO KERNEL ]]"1310"Press any key to reboot now "1310
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.htmlhttp://www.cppblog.com/myjfm/archive/2011/11/20/160558.html两部分,这两部分是kernel的组织方式,将ELF格式的kernel分program segment移动到指定位置;最后是切换进入保护模式,这里需要加载gdt,loader准备了5个全局段描述符,其中两个是专门为刚刚切换到保护模式后运行所准备的代码段和数据段描述符。两外两个是从绝对物理地址0x0开始,大小为4G的代码段和数据段描述符,不过等到进入kernel还需要重新设置段描述符,因此这只是权易之计。
posted on 2011-11-22 20:08 myjfm 阅读(726) 评论(0)  编辑 收藏 引用 所属分类: 操作系统

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理