WinixJ---boot/loader.s文件详解

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 

     思路比较清晰,首先保存系统参数,这个步骤是从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还需要重新设置段描述符,因此这只是权易之计。

你可能感兴趣的:(WinixJ---boot/loader.s文件详解)