一步步写操作系统(二) Boot启动 解决boot过大的问题

一步步写操作系统(二)

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就成功了。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值