關於操作系統啟動的最重要的一個概念就是Boot Sector,也就是引導扇區,通常指設備的第一個扇區,用於載入並轉讓處理器控制權給操作系統。

硬碟的0柱面、0磁頭、1扇區稱為主引導扇區,也叫主引導記錄MBR,該記錄佔用512個位元組,它用於硬碟啟動時將系統控制權轉給用戶指定的、在分區表中登記了某個操作系統分區。MBR的內容是在硬碟分區時由分區軟體寫入該扇區的,MBR不屬於任何一個操作系統,不隨操作系統的不同而不同,即使不同,MBR也不會夾帶操作系統的性質,具有公共引導的特性。但安裝某些多重引導功能的軟體或LINUX的LILO時有可能改寫它,它先於所有的操作系統被調入內存並發揮作用,然後才將控制權交給活動主分區內的操作系統。

可以理解為:

pull oneself up by ones bootstraps

通過拉自己的鞋帶把自己拽起,而對計算機來說,boot拉起的是整個操作系統。

那麼如何編寫一個可以完成引導的MBR幫助我們載入操作系統呢?

附上正在開發中的操作系統源代碼 : github.com/CasterWx/Ant

一. MBR的構成

1.主引導程序代碼,佔446位元組

硬碟的主引導程序代碼是從偏移0000H開始到偏移01BDH結束的446位元組;主引導程序代碼包括一小段執行代碼。啟動PC 機時,系統首先對硬體設備進行測試,成功後進入自舉程序INT 19H;然後讀系統磁碟0柱面、0磁頭、1扇區的主引導扇區MBR的內容到內存指定單元0:7C00 首地址開始的區域,並執行MBR程序段。

2. 磁碟簽名

3.硬碟分區表DPT,佔64位元組

4.主引導扇區結束標誌55AAH,為什麼是55AA後面會解釋。

二. MBR的功能

1.掃描分區表查找活動分區;

 2.尋找活動分區的起始扇區;

 3.將活動分區的引導扇區讀到內存;

 4.執行引導扇區的運行代碼。

如果主引導代碼未完成這些功能,系統顯示下列錯誤信息。

      Invalid partition table

      Error loading operating system      Missing operating system

MBR是BIOS接力的第一棒,在他之後,會由分區引導扇區DBR接力,至於為什麼不直接給DBR。那是因為BIOS大小有限,無法完成所有操作,在給DBR之前會通過MBR完成。

三. 關於MBR的解釋

在BIOS自檢等一系列工作完成後,要開始引導了。計算機會將硬碟0面0道1扇區512位元組載入到07c00h(0000::7c00)處。

  1)為什麼是0面0道1扇區?

這個可以理解為是規定,當Bios工作完成後會去將硬碟0面0道1扇區512位元組進行載入。但是真實情況是根據「魔數」來確定的,魔數就是有特殊意義的數,更大作用是用來做標記,比如MBR就是在512個位元組的最後兩個位元組填入 0x55 ,0xaa來進行標記的。放在第一個扇區是因為0面0道1扇區是磁碟最開始的地方,一開始檢驗出有 0x55 ,0xaa就直接開始載入。

  2)為什麼是07c00h?

這個也可以當作是規定,在IBM文檔中沒有具體說明07c00h是為什麼,但是在世界上第一臺個人計算機誕生時,07c00h就是在它當中初次誕生的,那時的DOS最多也就是32K,為了實現MBR中的棧需要512B,為了滿足需求取最大為1K,也就是32K-1K,就是07c00h了。

四. 如何實現一個MBR?

下面是一個最簡單的MBR代碼。

org 07c00h
mov ax,cs
mov ds,ax
mov es,ax
call DispStr
jmp $

DispStr:
mov ax,BootMessage
mov bp,ax
mov cx,16
mov ax,01301h
mov bx,000ch
mov dl,0
int 10h
ret
BootMessage: db "Antz Uhl Kone"
times 510-($-$$) db 0

dw 0xaa55

 第1行的org 07c00h已經做出講解了,它規定了程序載入的區域。

 第2-4行是將ds,es和cs指向相同的地址

 第5行call DispStr是調用了子程序實現字元串顯示。

 在子程序DispStr中:    

    mov ax,BootMessage 取得顯示字元串的地址

    mov bp,ax      es:ax 串地址

    mov cx,16      cx,串長度    mov ax,01301h    ah = 13 h al=01h    mov bx,000ch    bh = 00 頁號 bl = 0ch 字色    mov dl,0    int 10h        10h中斷    ret

 第6行的 jmp $是為了進行無限循環。可以理解為當前行命令的起始地址,$是表示當前段的起始地址。

第18行的510-($-$$) db 0是將剩下的地址全部填0,在512個位元組中,0x55,0xaa佔兩個位元組,剩下的510個位元組減去之前用過的($-$$)個位元組,剩下的512-2-$-$$個位元組全部填0。

之後便可以使用 nasm os.asm -o os.img 命令將彙編代碼轉變為一個鏡像文件,然後在虛擬機中使用了。

五. 編寫內核

在這裡需要懂得如何編寫彙編和c語言代碼並且將它們共同使用執行。

給一個簡單的demo,c語言代碼如下:

void myprint(char* msg, int len);

int choose(int a, int b)
{
if(a >= b){
myprint("the 1st one
", 13);
}
else{
myprint("the 2nd one
", 13);
}

return 0;
}

彙編代碼:

extern choose

[section .data] ; 數據在此

num1st dd 3
num2nd dd 4

[section .text] ; 代碼在此

global _start
global myprint

_start:
push num2nd
push num1st
call choose
add esp, 4

mov ebx, 0
mov eax, 1
int 0x80

; void myprint(char* msg, int len)
myprint:
mov edx, [esp + 8] ; len
mov ecx, [esp + 4] ; msg
mov ebx, 1
mov eax, 4 ; sys_write
int 0x80
ret

  第一行的extern choose就是指c代碼中定義的choose函數。

  後面的global導出了函數入口,_start和在c文件中沒有定義的myprint函數,myprint在彙編代碼中完成了定義。

  _start中調用了choose,choose中又調用了myprint函數。

  這樣這兩個代碼文件內容就很清晰了吧。

  在Linux終端下查看結果:

nasm -f elf foo.asm -o foo.o
gcc -c bar.c -o bar.o
ld -s hello.o bar.o -o foobar
./foobar

接下來就可以使用c語言來編寫內核啦。

我們需要在MBR中指定內核載入的代碼地址。

BS_OEMName DB Antz__Os

BPB_BytsPerSec DW 512
BPB_SecPerClus DB 1
BPB_RsvdSecCnt DW 1
BPB_NumFATs DB 2
BPB_RootEntCnt DW 224
BPB_TotSec16 DW 2880
BPB_Media DB 0xF0
BPB_FATSz16 DW 9
BPB_SecPerTrk DW 18
BPB_NumHeads DW 2
BPB_HiddSec DD 0
BPB_TotSec32 DD 0

BS_DrvNum DB 0
BS_Reserved1 DB 0
BS_BootSig DB 29h
BS_VolID DD 0
BS_VolLab DB Tinix0.01
BS_FileSysType DB FAT12

FATSz equ 9
RootDirSectors equ 14

SectorNoOfRootDirectory equ 19
SectorNoOfFAT1 equ 1
DeltaSectorNo equ 17

MBR修改如下:

org 07c00h

BaseOfStack equ 07c00h
BaseOfLoader equ 09000h
OffsetOfLoader equ 0100h

jmp short LABEL_START
nop

%include "fat12hdr.inc"

LABEL_START:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack

mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 0184fh
int 10h

mov dh, 0
call DispStr

xor ah, ah
xor dl, dl
int 13h

mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootDirSizeForLoop], 0
jz LABEL_NO_LOADERBIN
dec word [wRootDirSizeForLoop]
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, [wSectorNo]
mov cl, 1
call ReadSector

mov si, LoaderFileName
mov di, OffsetOfLoader
cld
mov dx, 10h
LABEL_SEARCH_FOR_LOADERBIN:
cmp dx, 0
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec dx
mov cx, 11
LABEL_CMP_FILENAME:
cmp cx, 0
jz LABEL_FILENAME_FOUND
dec cx
lodsb
cmp al, byte [es:di]
jz LABEL_GO_ON
jmp LABEL_DIFFERENT

LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME

LABEL_DIFFERENT:
and di, 0FFE0h
add di, 20h
mov si, LoaderFileName
jmp LABEL_SEARCH_FOR_LOADERBIN

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
add word [wSectorNo], 1
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN

LABEL_NO_LOADERBIN:
mov dh, 2
call DispStr
%ifdef _BOOT_DEBUG_
mov ax, 4c00h
int 21h
%else
jmp $
%endif

LABEL_FILENAME_FOUND:
mov ax, RootDirSectors
and di, 0FFE0h
add di, 01Ah
mov cx, word [es:di]
push cx
add cx, ax
add cx, DeltaSectorNo
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, cx

LABEL_GOON_LOADING_FILE:
push ax
push bx
mov ah, 0Eh
mov al, .
mov bl, 0Fh
int 10h
pop bx
pop ax

mov cl, 1
call ReadSector
pop ax
call GetFATEntry
cmp ax, 0FFFh
jz LABEL_FILE_LOADED
push ax
mov dx, RootDirSectors
add ax, dx
add ax, DeltaSectorNo
add bx, [BPB_BytsPerSec]
jmp LABEL_GOON_LOADING_FILE
LABEL_FILE_LOADED:

mov dh, 1
call DispStr

jmp BaseOfLoader:OffsetOfLoader

wRootDirSizeForLoop dw RootDirSectors
wSectorNo dw 0
bOdd db 0

LoaderFileName db "LOADER BIN", 0

MessageLength equ 9
BootMessage: db "Booting ";
Message1 db "Ready. ";
Message2 db "No LOADER";

DispStr:
mov ax, MessageLength
mul dh
add ax, BootMessage
mov bp, ax
mov ax, ds
mov es, ax
mov cx, MessageLength
mov ax, 01301h
mov bx, 0007h
mov dl, 0
int 10h
ret

ReadSector:
push bp
mov bp, sp
sub esp, 2

mov byte [bp-2], cl
push bx
mov bl, [BPB_SecPerTrk]
div bl
inc ah
mov cl, ah
mov dh, al
shr al, 1
mov ch, al
and dh, 1
pop bx

mov dl, [BS_DrvNum]
.GoOnReading:
mov ah, 2
mov al, byte [bp-2]
int 13h
jc .GoOnReading

add esp, 2
pop bp

ret

GetFATEntry:
push es
push bx
push ax
mov ax, BaseOfLoader
sub ax, 0100h
mov es, ax
pop ax
mov byte [bOdd], 0
mov bx, 3
mul bx
mov bx, 2
div bx
cmp dx, 0
jz LABEL_EVEN
mov byte [bOdd], 1
LABEL_EVEN:
xor dx, dx
mov bx, [BPB_BytsPerSec]
div bx

push dx
mov bx, 0
add ax, SectorNoOfFAT1
mov cl, 2
call ReadSector
pop dx
add bx, dx
mov ax, [es:bx]
cmp byte [bOdd], 1
jnz LABEL_EVEN_2
shr ax, 4
LABEL_EVEN_2:
and ax, 0FFFh

LABEL_GET_FAT_ENRY_OK:

pop bx
pop es
ret

times 510-($-$$) db 0
dw 0xaa55

出現了Ready,說明我們的kernel內核已經載入成功了。


推薦閱讀:
相關文章