2.Boot启动 解决boot过大的问题
上一贴中知道了boot只能在启动扇区的前面512字节中。在项目初期,需要的准备东西并不太多,但到了后面,初期启动准备的东西就越来越多,如果大于了512字节怎么办?
余渊版本的boot是这样的:设置段寄存器,寻找存储器中的loader并加载运行,在loader寻找存储器中的kernel代码并加载,设置GDT切换到保护模式,在保护模式中跳转到kernel。因此,实际加载过程中,还是需要loader这个模块来做跳板的。
但是使用loader解决问题以后,又会出现另外一个问题。loader和kernel究竟放在哪儿?在loader中要调用循环来读取kernel到内存,如果loader和kernel的数据在地址上重叠,那么loader会不能正常执行。
在linux 0.11版本的内核中,boot一开始只把自身从0x7c000复制到0x90000处继续运行,然后将setup放到0x90200处,而system加载到0x10000,这样一来system就不能超过0x80000 / 1024 = 512KB。这里setup就相当于loader,而system相当于kernel代码。因为linux 0.11在诞生的时候可能内存并不大,所以考虑到内存使用,尽量将system往前靠,然而又不能覆盖掉当时正在调用读磁盘的BIOS中断区,所以选择了0x10000。
在我们自己写的操作系统中,使用了另外一种折衷方案,就是将loader加载到0x80000处,而将kernel加载到0x90000处,这样kernel就没有大小的限制,而loader大小被限制在0x10000 / 1024 = 64KB大小。我们没有使用linux那样使用低地址放置kernel,而是放在0x90000高地址处。首先,当时linux 0.11内存缺紧,要考虑到将内核放在低区,然而现在我们自己写的操作系统则不需要考虑这个问题。其次,不对内核大小进行限制,可以方便以后扩展系统,而且64KB对于loader来说远远足够了。当然,现在新版本的linux内核加载模式不在我们讨论之中。
在内存布局设计好之后,我们的代码就可以写成这样:
boot.asm
; a boot sector that loads a loader and kernel in 32-bit protected mode
[org 0x7c00]
LOADER_BASE equ 0x800 ; this is the memory base to which we will load our loader
; loader is 512(0x200) ahead of kernel, and do not over 0xFFFF
KERNEL_BASE equ 0x900 ; this is the memory base to which we will load our kernel
; (KERNEL_BASE * 16 + OFFSET)
mov [BOOT_DRIVE], dl ; BIOS stores our boot driver in dl, so it's best to remember
; this for later
mov ax, cs
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov bp, 0x9000 ; set the stack
mov sp, bp
mov si, MSG_REAL_MODE ; announce that we are starting
call print_string ; booting from 16-bit real mode
call load_loader ; load our loader
call load_kernel ; load our kernel
call vga_start ; start VGA mode
call switch_to_pm ;note that we never return from here
jmp $
; include our useful, hard-earned routine
%include "print_string.asm"
%include "disk_load.asm"
%include "gdt.asm"
%include "print_string_pm.asm"
%include "switch_to_pm.asm"
%include "vga_start.asm"
[bits 16]
; load loader
load_loader:
mov si, MSG_LOAD_LOADER ; print a message to say we are loading the loader
call print_string
mov bx, LOADER_BASE<<4 ; set up parameters for our disk_load routine, so
mov dh, 1 ; that we load the first 1 sectors (excluding
mov cl, 0x02 ; start reading from second sector (i.e.after the boot sector)
mov dl, [BOOT_DRIVE] ; the boot sector) from the boot disk (i.e our
call disk_load ; loader code) to the right address
ret
; load kernel
load_kernel:
mov si, MSG_LOAD_KERNEL ; print a message to say we are loading the kernel
call print_string
mov ax, BUFFER_ADDR
mov es, ax
mov ax, 0 ; ES:BX buffer
mov ax, 0
mov di, ax
mov si, ax
mov ch, 0 ; CH cylind
mov dh, 0 ; DH head
mov cl, 2 ; CL sector, kernel starts from the 3rd sector
readFloppy:
cmp byte [LOAD_CYLINDERS], 0
je fin
mov bx, 0
inc cl
mov ah, 0x02 ; AH read floppy
mov al, 1 ; AL sectors
mov dl, 0 ; DL driver
int 0x13 ; load 1 sector into buffer
jc error
mov si, MSG_DOT
call print_string
; copy from buffer to real address
copySector:
push si
push di
push cx
mov cx, 0x200 ; buffer size 512 byte
mov di, 0
mov si, 0
mov ax, word [LOAD_SECTION]
mov ds, ax ; copy to
copy:
cmp cx, 0
je copyEnd
mov al, byte [es:si]; buffer address
mov byte [ds:di], al
inc di
inc si
dec cx
jmp copy
copyEnd:
pop cx
pop di
pop si
mov bx, ds
add bx, 0x20 ; add address base by 0x200 / 16
mov ax, 0
mov ds, ax
mov word [LOAD_SECTION], bx ; save address base
mov bx, 0
; end of copy sector
cmp cl, 18 ; every cylinder has 18 sectors
jb readFloppy
cmp dh, 0
je changeHead ; read from head 1 after head 0
mov dh, 0 ; read from head 0
inc ch ; and from the next cylinder
mov cl, 0
dec byte [LOAD_CYLINDERS]
jmp readFloppy
changeHead:
inc dh ; read from head 1
mov cl, 0
jmp readFloppy
fin:
mov ax, 0
mov ds, ax
ret
error:
add ah, 48
mov byte [DISK_ERROR_MSG], ah
mov si, DISK_ERROR_MSG
call print_string
jmp $
BUFFER_ADDR equ 0x7e0
; global variables
LOAD_CYLINDERS db 10 ; total size = 512 * cycliners * head(2) * 18 (sectors per cylinder)
LOAD_SECTION dw KERNEL_BASE
[bits 32]
; this is where we arrive after switching to and initialising protected mode.
BEGIN_PM:
mov ebx, MSG_PORT_MODE
call print_string_pm ; use out 32-bit print routine.
call LOADER_BASE<<4 ; now jump to the address of our loaded
; loader code, assume the brace position,
; and cross you finger, here we go!
jmp $ ; Hang.
; global variables
BOOT_DRIVE db 0
MSG_LOAD_LOADER db "Loading loader", 0
MSG_LOAD_KERNEL db "Loading kernel", 0
MSG_REAL_MODE db "Real Mode", 0
MSG_PORT_MODE db "Protected Mode", 0
MSG_DOT db "."
; bootsector padding
times 510 - ( $ - $$) db 0
dw 0xaa55
由原来的call load_kernel变为 call load_loader和call load_kernel,然后将BEIGN_PM的代码全部移到loader中。这样一个启动顺序就是:boot加载loader和kernel分别到0x8000和0x9000然后设置GDT跳入PM,即进入loader,loader在这里的作用就是提供一些保护模式下的初期设置,然后调用kernel的地址进入kernel。(其他模块没有改变,参见上一个帖子)
loader.asm
; loader.asm
[org 0x8000] ; where loader will be loaded to
KERNEL_OFFSET equ 0x9000 ; this is the memory offset to which we have loaded our kernel
KERNEL_REAL equ 0x10000 ; this is the real memory addres to which we started our kernel
LOAD_CYLINDERS equ 10
; total size = 512 * cycliners * head(2) * 18 (sectors per cylinder)
TOTAL_SIZE equ 512 * LOAD_CYLINDERS * 2 * 18
[bits 32]
; this is where we arrive after switching to and initialising protected mode.
BEGIN_LOADER:
mov ebx, LOADER_MSG
call print_string_pm ; use out 32-bit print routine.
; move kernel to KERNEL_REAL
; in protect mode, you can use over 1M address
; (we do not need to move at this time)
;mov cx, TOTAL_SIZE
;mov esi, KERNEL_OFFSET
;mov edi, KERNEL_REAL
;cld ; increase
;rep movsb
call KERNEL_OFFSET ; now jump to the address of our loaded
jmp $ ; Hang.
%include "print_string_pm.asm"
; global variables
LOADER_MSG db "####Loading Kernel#####", 0
; bootsector padding
times 510 - ( $ - $$) db 0
dw 0xaa55
将以上代码放置到boot文件夹中,写一个Makefile,这个Makefile和上一贴的Makefile是几乎一样的,不过添加了loader.bin。
IMAGE_DIR =
IMAGE = ${IMAGE_DIR}nano
image : boot.bin loader.bin
cat $^ > ${IMAGE}.bin
dd if=${IMAGE}.bin of=${IMAGE}.img bs=1440K count=1 conv=notrunc
%.bin : %.asm
nasm $< -f bin -o $@ -I boot/
# White image
raw :
dd if=/dev/zero of=${IMAGE}.img bs=1440K count=1
就可以使用nasm编译出boot.bin,一个512字节的可启动扇区。loader.bin会被编译并写到img的512字节之后的位置。
此时依旧没有kernel,会显示
####Loading Kernel#####
的字样,这样我们的跳板loader就成功了。