可以参照Nick Blundell版本的代码,写一个基本的启动代码(局部文件):
[org 0x7c00]
[bits 16]
KERNEL_OFFSET equ 0x9000 ; this is the memory offset to which we will load our kernel
mov ax, cs
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov [BOOT_DRIVE], dl ; BIOS stores our boot driver in dl, so it's best to remember
; this for later
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 vga_start ; start VGA modl
call load_kernel ; load our kernel
call switch_to_pm ;note that we never return from here
jmp $
从这段代码可以看出很清晰的启动顺序:首先设置段寄存器,然后将kernel加载到指定位置,设置GDT然后切换到保护模式,在保护模式中最后跳转到kernel。一个更高级的启动顺序应该是:设置段寄存器,寻找存储器中的loader并加载运行,在loader寻找存储器中的kernel代码并加载,设置GDT切换到保护模式,在保护模式中跳转到kernel。这个启动顺序是余渊版本的Boot,可以在上面的Nick Blundell 的版本上添加代码,或者直接一直余渊版本的Boot都可以达到要求。但对于希望了解系统的人来说,使用Nick Blundell 版本的简单启动代码应该就足够了。
boot.asm
; boot.asm
; author stophin
;
; a boot sector that boots a c kernel in 32-bit protected mode
[org 0x7c00]
[bits 16]
KERNEL_OFFSET equ 0x9000 ; this is the memory offset to which we will load our kernel
mov ax, cs
mov ds, ax
mov ss, ax
mov es, ax
mov fs, ax
mov gs, ax
mov [BOOT_DRIVE], dl ; BIOS stores our boot driver in dl, so it's best to remember
; this for later
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 vga_start ; start VGA mode
call load_kernel ; load our kernel
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 kernel
load_kernel:
mov si, MSG_LOAD_KERNEL ; print a message to say we are loading the kernel
call print_string
mov bx, KERNEL_OFFSET ; set up parameters for our disk_load routine, so
mov dh, 56 ; that we load the first n sectors (excluding
mov dl, [BOOT_DRIVE] ; the boot sector) from the boot disk (i.e our
call disk_load ; kernel code) to address KERNEL_OFFSET
ret
[bits 32]
; this is where we arrive after switching to and initialising protected mode.
BEGIN_PM:
mov ebx, MSG_PROTECT_MODE
call print_string_pm ; use out 32-bit print routine.
call KERNEL_OFFSET ; now jump to the address of our loaded
; kernel code, assume the brace position,
; and cross you finger, here we go!
jmp $ ; Hang.
; global variables
BOOT_DRIVE db 0
MSG_LOAD_KERNEL db "Loading kernel into memory", 0
MSG_REAL_MODE db "Started in 16-bit Real Mode", 0
MSG_PROTECT_MODE db "Successfully landed in 32-bit Protected Mode", 0
; bootsector padding
times 510 - ( $ - $$) db 0
dw 0xaa55
print_string.asm
; print_strin.asm
; author stophin
;
[bits 16]
; print_string(SI)
print_string:
mov ax, [si]
mov bp, ax
mov cx, 36
mov ax, 01301h
mov bx, 000ch
mov dl, 0
int 10h
ret
print_string_pm.asm
; print_string_pm.asm
; author stophin
;
[bits 32]
; define some constants
VIDEO_MEMORY equ 0xb8000
WHITE_ON_BLACK equ 0x0f
; prints a null-terminated string pointed to by EDX
print_string_pm:
pusha
mov edx, VIDEO_MEMORY ; set edx to the start of vid mem
print_string_pm_loop:
mov al, [ebx] ; store the char at EBX in AL
mov ah, WHITE_ON_BLACK ; store the attributes in AH
cmp al, 0 ; if (al == 0), at end of string, so
je print_string_pm_done ; jump to done
mov [edx], ax ; store char and attributes at current
; character cell
add ebx, 1 ; increment EBX to the next char in string
add edx, 2 ; move to next character cell in vid mem
jmp print_string_pm_loop ; loop around to print the next char
print_string_pm_done:
popa
ret ; return from the function
disk_load.asm
; disk_load.asm
; author stophin
;
[bits 16]
; load dh sectors to ES:BX from drive dl
disk_load:
push dx ; store dx on stack so later we can recall
; how many sectors we request to be read,
; even if it is altered in the meantime
mov ah, 0x02 ; BIOS read sector function
mov al, dh ; read dh sectors
mov ch, 0x00 ; select cylinder 0
mov dh, 0x00 ; select head 0
mov cl, 0x02 ; start reading from second sector (i.e.
; after the boot sector)
int 0x13 ; BIOS interrupt
jc disk_error ; jump if error (i.e. carry flag set)
pop dx ; read dx from the stack
cmp dh, al ; if al (sectors read) != dh (sectors expected)
jne disk_error ; display error message
ret
disk_error:
mov bx, DISK_ERROR_MSG
call print_string
jmp $
; variables
DISK_ERROR_MSG:
db "Disk read error!", 0
; gdt.asm
; author stophin
;
[bits 16]
; global descriptor table
gdt_start:
gdt_null: ; the mandatory null discriptor
dd 0x0 ; 'dd' means define double word (i.e. 4 bytes)
dd 0x0
gdt_code: ; the code segment descriptor
; base 0x0, limit 0xfffff
; 1st flags: (present) 1 (pricilege) 00 (descriptor type) 1 -> 1001b
; type flags : (code) 1 (confroming) 0 (readable) 1 (accessed) 0 -> 1010b
; 2nd flags : (granularity) 1 (32-bit default) 1 (64-bit seg) 0 (AVL) 0 ->1100b
dw 0xffff ; limit (bits 0 - 15)
dw 0x0 ; base (bits 0 - 15)
db 0x0 ; base (bits 16 - 23)
db 10011010b ; 1st flags, type flags
db 11001111b ; 2nd flags, limit (bits 16 - 19)
db 0x0 ; base (bits 24 - 31)
gdt_data: ; the data segment descriptor
; same as code segment except for the type flags
; type flags : (code) 0 (expand down) 0 (writable) 1 (accessed) 0 -> 0010b
dw 0xffff ; limit (bits 0 - 15)
dw 0x0 ; base (bits 0 - 15)
db 0x0 ; base (bits 16 - 23)
db 10010010b ; 1st flags, type flags
db 11001111b ; 2nd flags, limit (bits 16 - 19)
db 0x0 ; base (bits 24 - 31)
gdt_end: ; the reason for putting a label at the end of the
; GDT is so we can have the assembler calculate
; the size of the GDT for the gdt descriptor (below)
; GDT descriptor
gdt_descriptor:
dw gdt_end - gdt_start - 1 ; size of our GDT, always less one
dd gdt_start ; start address of our GDT
; deing some handy constants for the GDT segment descriptor offsets, which
; are what segment registers must contain when in protected mode. For example,
; when we set DS = 0x10 in PM, the CPU knows that we mean it to use the
; segment described at offset 0x10 (i.e. 16 bytes) in out GDT, which in our
; case is the DATA segment (0x0 -> NULL; 0x08 -> CODE; 0x10 ->DATA)
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start
; vga_start.asm
; author stophin
;
[bits 16]
; remember VGA info
; note the start address will be used in kernel
VMODE equ 0x0ff0 ; VGA mode
SCRNX equ 0x0ff2 ; screen X
SCRNY equ 0x0ff4 ; screen Y
VRAM equ 0x0ff8 ; memory cache
; vga start
vga_start:
mov al, 0x13 ; VGA card, 320*200*8bit
; other:
; 0x03: 16bit character 80 * 25, initial mode
; 0x12: VGA card, 640*480*4bit
; 0x6a: extended VGA card, 800*600*4
mov ah, 0x00
int 0x10
mov byte [VMODE], 8
mov word [SCRNX], 320
mov word [SCRNY], 200
mov dword [VRAM], 0xa0000
ret
; switch_to_pm.asm
; author stophin
;
[bits 16]
; switch to protected mode
switch_to_pm:
cli ; we must switch off interrupts until we have
; setup the protected mode interrupt vector
; otherwise interrupts will run riot
lgdt [gdt_descriptor] ; load out global descriptor table, which defines
; the protected mode segments (e.g. for code and data)
mov eax, cr0 ; to make the switch to protected mode, we set
or eax, 0x1 ; the first bit of CR0, a control register
mov cr0, eax
jmp CODE_SEG:init_pm ; make a far jump (i.e. to a new segment) to our 32-bit
; code. This also forces the CPU to flush its cache of
; pre-fetched and real-mode decoded instructions, which
; cause problems
[bits 32]
; initialise registers and the stack once in PM.
init_pm:
mov ax, DATA_SEG ; now in PM, out old segments are meaningless.
mov ds, ax ; so we point out segment registers to the
mov ss, ax ; data selector we defined in our GDT
mov es, ax
mov fs, ax
mov gs, ax
mov ebp, 0x090000 ; update our stack position so it is right
mov esp, ebp ; at the top of the free space
call BEGIN_PM ; finally, call some well-known label
IMAGE_DIR =
IMAGE = ${IMAGE_DIR}nano
image : boot.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
先使用make raw新建一个1.44M软盘img,然后直接make就可以将asm编译成bin并写入img中。
使用nasm编译出boot.bin,一个512字节的可启动扇区。先将boot.bin文件写入IMAGE.bin,这里由于只有boot,如果有kernel,可以将kernel通过cat一并写入IMAGE.bin中,并通过dd创建1.44M软盘,之后设置好bochs就可以运行了。
这里由于没有kernel,运行会不成功。可以将
call KERNEL_OFFSET
换成jmp $来停止继续运行到未知的地方。