1. 程式人生 > >主引導程式控制權的交接(六)

主引導程式控制權的交接(六)

        我們在上節部落格中學習瞭如何進行主載入程式的 512 位元組的擴充套件,那麼我們本節就繼續來學習下如何進行控制權的交接。就是控制權由主載入程式交由下一個將要執行的程式,類似於嵌入式中的 uboot 在啟動核心的時候將控制權由 uboot 交由 kernel。下來我們先來看看 BootLoader 的記憶體佈局,如下所示

圖片.png

        我們看到在 0x7c00 前還有一段預留的空間,那麼這段空間就是用來存放棧資訊的。在主載入程式的 512 位元組之後,緊接著就是 Fat 表,它大小為 4kb。從 0x9000

地址之後便全部為 Loader 了,也就是我們交由控制權的地方了。我們來看看通過 FAT 表是如何來載入檔案內容的,如下圖所示

圖片.png

        我們看到,先指定 FAT 表的地址,然後指定 DIR_FstClus 成員的入口地址,再間接賦給 dx 暫存器。這個 0xFF7 是哪來的呢?在我們之前用 Qt 編寫的程式碼中就指定了這個大小,這是規定的,後面的流程也是我們之前實現過的流程。我們就來做個實驗:1、在虛擬軟盤中建立體積較大的文字檔案(Loader);2、將 Loader 的內容載入到 BaseOfLoader 地址處;3、列印 Loader 中的文字(用來判斷載入是否完全)

。具體原始碼如下

start:
    mov ax, cs
    mov ss, ax
    mov ds, ax
    mov es, ax
    mov sp, BaseOfStack
    
    mov ax, RootEntryOffset
    mov cx, RootEntryLength
    mov bx, Buf
    
    call ReadSector
    
    mov si, Target
    mov cx, TarLen
    mov dx, 0
    
    call FindEntry
    
    cmp dx, 0
    jz output
    
    mov si, bx ; 將起始地址放到 si 中
    mov di, EntryItem
    mov cx, EntryItemLength
    
    call MemCpy
    
    ; 計算 Fat 表所佔用的記憶體
    mov ax, FatEntryLength
    mov cx, [BPB_BytsPerSec]
    mul cx ; 將所佔用的記憶體大小結果儲存到 ax 中
    mov bx, BaseOfLoader
    sub bx, ax ; bx 就是 Fat 表在記憶體中的起始位置了
    
    mov ax, FatEntryOffset
    mov cx, FatEntryLength
    
    call ReadSector
    
    mov dx, [EntryItem + 0x1A] ; 獲取目標起始處的位置
    mov si, BaseOfLoader
    
loading:
    mov ax, dx
    add ax, 31
    mov cx, 1
    push dx
    push bx
    mov bx, si
    call ReadSector
    pop bx
    pop cx
    call FatVec
    cmp dx, 0xFF7
    jnb output
    add si, 512
    jmp loading

output:
    mov bp, BaseOfLoader
    mov cx, [EntryItem + 0x1c]
    call Print
    
last:
    hlt
    jmp last

        我們來編譯看看

圖片.png

        我們看到 make 直接報錯,原因是整個主載入程式的大小超出 512 位元組的範圍了。那麼此時我們該怎麼辦呢?我們就有必要將之前的 push 和 pop 入棧出棧的操作進行刪除了,那麼我們之前為何要這樣做呢?是為了遵守彙編程式碼的約定,有操作相關暫存器的值就要進行入棧出棧操作。那麼我們這塊記憶體已經不夠,因此沒必要進行這個操作了。我們將下面的入棧出棧操作進行刪除,但是要在 FindEntry 這個函式保留 cx 暫存器的入棧出棧的操作,原因是下面不停在改變 cx 暫存器的值。我們在 find 操作中,call MemCmp 操作前後有必要再加上 si 暫存器的入棧出棧操作。我們修改完再來進行 make 看看,是否還會出問題

圖片.png

        我們看到已經編譯成功,我們來 bochs 除錯下,看看 loader 文字的內容是什麼

圖片.png

        我們看到列印了好多的 D.T.Software。我們來掛載下 data.img 看看 loader 文字的內容是否如此

圖片.png

        那麼我們將 loader 文字的內容改為我們剛才編寫的 boot.asm 的內容,順便看看它的檔案所佔記憶體是多大的,如果大於 512 位元組還能正常進行讀取並顯示,那麼就說明我們所編寫的功能是沒有問題的。

圖片.png

        我們看到這個文字的記憶體是 8 kb,那麼它早就超過 512  位元組了。看看最後列印的是不是 db 0x55, 0xaa,結果如下

圖片.png

        我們看到列印的確實是我們剛才改的內容,也就證明了我們編寫的程式碼是正確的。下來我們來講講由 boot 主引導程交由後的第一個程式 Loader:它的起始地址是 0x9000(org 0x9000),通過 int 0x10 號中斷在螢幕上列印字串。在編寫 loader.asm 原始碼之前,我們先來介紹下相關的彙編知識。我們在之前使用了 jz 指令,表示跳轉,其實 jxx 代表了一個指令族,功能是根據標誌位進行調整,具體如下

圖片.png

loader.asm 原始碼如下

org 0x9000

begin:
    mov si, msg
    
print:
    mov al, [si]
    add si, 1
    cmp al, 0x00
    je end
    mov ah, 0x0E
    mov bx, 0x0F
    int 0x10
    jmp print

end:
    hlt
    jmp end
    
msg:
    db 0x0a, 0x0a
    db "Hello, D.T.OS!"
    db 0x0a, 0x0a
    db 0x00

        我們在上面的程式碼中使用了 je,我們通過反彙編來看看它在內部是怎樣實現的,如下

圖片.png

        我們看到在編譯器的內部是將 je 指令當做 jz 指令使用了。我們將 loader.asm 編譯成 loader,然後將它拷貝至 data.img 中。看看執行的效果

圖片.png

        我們看到已經打印出 Hell, D.T.OS! ;我們再將此時的 data.img 放在 window 中,將它作為軟盤在我們所建立的虛擬機器上,看看效果

圖片.png

        我們看到已經成功輸出我們所列印的字串了,雖然效果和我們之前所實現的是一樣的。但是此時已經發生了質的變化,此時的 loader.asm 文字大小不再有 512 位元組的限制。換句話說,我們可以編寫更多的核心機制了。那麼我們在完成之後也要將之前的 makefile 重寫下,要不然每次都要手動編譯 loader.asm 程式。改動後的具體原始碼如下

.PHONY : all clean rebuild

BOOT_SRC := boot.asm
BOOT_OUT := boot.bin

LOADER_SRC := loader.asm
LOADER_OUT := loader

IMG := data.img
IMG_PATH := /root/DT/wei

RM := rm -rf

all : $(BOOT_OUT) $(LOADER_OUT)
    @echo "Build Success ==> D.T.OS!"
    
$(IMG) :
    bximage [email protected] -q -fd -size=1.44
    
$(BOOT_OUT) : $(BOOT_SRC)
    nasm $^ -o [email protected]
    dd if=$(BOOT_OUT) of=$(IMG) bs=512 count=1 conv=notrunc
    
$(LOADER_OUT) : $(LOADER_SRC)
    nasm $^ -o [email protected]
    mount -o loop $(IMG) $(IMG_PATH)
    cp [email protected] $(IMG_PATH)/[email protected]
    umount $(IMG_PATH)

clean :
    $(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
    
rebuild :
    @$(MAKE) clean
    @$(MAKE) all

        我們再來重新 make  下,看看效果

圖片.png

        我們將輸出字串在  loader.asm 中改為 Hello, world!看看效果

圖片.png

        我們看到已經成功改寫,那麼我們再來測試下對原來的功能有沒有造成什麼影響。將原來的 LOADER 在後面加個 a ,看看是否會因查詢不到而輸出 No LOADER ... 提示性字串

圖片.png

        我們看到已經輸出了提示性的字串,證明我們新增的功能以及改寫的 makefile 對原來功能並沒有造成任何影響。通過今天對控制權交接的學習,總結如下:1、boot 需要在進行重構保證在 512 位元組內完成功能;2、在彙編程式中儘量確保函式呼叫前後通用暫存器的狀態不變;3、boot 成功載入 loader 後將控制權轉移;4、loader 程式沒有程式碼體積的限制。