引導扇區只有 512 字節,過小,基本啥也幹不了,咱們只能利用引倒扇區把咱們的操做系統內核載入內存。可是一會兒把內核所有載入進來也不靠譜,還有不少事情沒作呢——起碼就還沒轉到安全模式,內核大的話,實模式可裝不下。因此正確的姿式是:引導扇區載入一個裝載程序,裝載程序負責作好準備工做後,再載入真正的內核。
安全
那麼引導扇區的任務就很明確了:找到裝載程序(Loader.bin),而後把它載入內存。第十天初識FAT12時提到了:經過根目錄表項找到文件的第 1 個扇區,再從 FAT 表中找到其餘的扇區。模塊化
今天的代碼不是徹底照書抄了,是按照個人理解重寫的,代碼裏我很詳細的註釋了個人思路,並且結構也很清晰,子函數功能明確,基本是按照模塊化設計的——我是深受 C 語言影響。引導扇區去掉填空的 0,大小隻有 340 字節,仍是比較滿意的。若是有人有心在看的話,但願能提出意見。函數
; Constant.inc ; 常量 ; 四彩 ; 2015-11-12 %ifndef _CONSTANT_INC %define _CONSTANT_INC ; ======================================================================================== ; 內存中 0x0500 ~ 0x7BFF(29.75 KB) 段和 0x7E00 ~ 0xFFFF(32.5KB)段、 ; 0x10000 ~ 0x9FBFF(575 KB)段可自由使用。引導扇區段在加載完 Loader 也可以使用。 ; SEGMENTBASEOFTEMP equ 0x7E0 ; 臨時數據被加載到內存的段地址(最多 2 個扇區) SEGMENTBASEOFLOADER equ 0x4000 ; Loader.SYS 被加載到內存的段地址 STACKSIZE equ 0x400 ; Loader 的堆棧大小 ; **************************************************************************************** %endif
; FAT12.inc ; FAT12 文件系統常量及宏定義 ; 四彩 ; 2015-11-08 %ifndef _FAT12_INC %define _FAT12_INC ; ======================================================================================== BYTESPERSECTOR equ 512 ; 每扇區字節數 IFATFIRSTSECTOR equ 1 ; FAT 表的起始邏輯扇區號 IROOTDIRECTORYFIRSTSECTOR equ 19 ; 根目錄區的起始邏輯扇區號 IDATAFIRSTSECTOR equ 33 ; 數據區的起始邏輯扇區號 ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系統的引導扇區頭部格式宏 ; 調用格式:FAT12Head Label_RealEntry, OEMName, VolLab ; Label_RealEntry : 程序入口標籤 ; OEMName : 廠商名稱(8 字節長,不夠的填空格) ; VolLab : 卷標(11 字節長,不夠的填空格) %macro FAT12Head 3 ; 名稱 偏移 長度 說明 3.5英寸軟盤內容 jmp %1 ; 0x00 3 跳轉指令,指向程序入口 jmp RealEntry nop BS_OEMName db %2 ; 0x03 8 廠商名稱 自行定義 BPB_BytsPerSec dw 512 ; 0x0B 2 每扇區字節數 512 BPB_SecPerClus db 1 ; 0x0D 1 每簇扇區數 1 BPB_RsvdSecCnt dw 1 ; 0x0E 2 保留扇區數 1 BPB_NumFATs db 2 ; 0x10 1 FAT表份數 2 BPB_RootEntCnt dw 224 ; 0x11 2 根目錄中最多容納的文件數 224 BPB_TotSec16 dw 2880 ; 0x13 2 扇區總數 (FAT十二、16) 2880 BPB_Media db 0xF0 ; 0x15 1 介質描述符 0xF0 BPB_FATSz16 dw 9 ; 0x16 2 每一個FAT表所佔的扇區數 9 BPB_SecPerTrk dw 18 ; 0x18 2 每磁道扇區數 18 BPB_NumHeads dw 2 ; 0x1A 2 磁頭數 2 BPB_HiddSec dd 0 ; 0x1C 4 隱藏扇區數 0 BPB_TotSec32 dd 2880 ; 0x20 4 扇區總數(FAT32) 2880 BS_DrvNum db 0 ; 0x24 1 磁盤驅動器號 0 BS_Reserved1 db 0 ; 0x25 1 保留(供NT使用) 0 BS_BootSig db 0x29 ; 0x26 1 擴展引導標記 0x29 BS_VolD dd 0 ; 0x27 4 卷標序列號 0 BS_VolLab db %3 ; 0x2B 11 卷標 自行定義 BS_FileSysType db 'FAT12' ; 0x36 8 文件系統類型名 FAT12 ; 0x3E 448 引導代碼及其餘填充字符 ; 0x1FE 2 結束標誌 0xAA55 ; ; BPB:BIOS Parameter Block,BIOS 參數塊 ; BS:Boot Sector,引導扇區 %endmacro ; **************************************************************************************** ; ======================================================================================== ; 目錄表項結構 struc DirectoryItem ; 字段名 偏移 長度 說明 .DIR_Name resb 11 ; 0x00 11 文件名 8 + 3(大寫,不夠長度末尾填空格) .DIR_Attr resb 1 ; 0x0B 1 文件屬性 resb 10 ; 0x0C 10 保留 .DIR_WrtTime resw 1 ; 0x16 2 最後修改時間 .DIR_WrtDate resw 1 ; 0x18 2 最後修改日期 .DIR_FstClus resw 1 ; 0x1A 2 此條目對應的開始簇號(即 FAT 表項序號) .DIR_FileSize resd 1 ; 0x1C 4 文件大小 endstruc ; **************************************************************************************** %endif
; BootSector.asm ; 引導扇區 ; 四彩 ; 2015-11-12 ; ======================================================================================== ; 電腦的啓動過程: ; 一、80x86 CPU 啓動後(加電或復位),CS : IP 被設置爲 0xFFFF : 0x0,CPU 今後處讀取指令 ; 開始執行。該單元在基本輸入輸出系統(Basic Input/Output System,BIOS)的地址範圍內, ; 這裏是一條跳轉到 BIOS 中真正啓動代碼處的指令。 ; 二、BIOS 首先進行加電自檢(Power-On Self-Test,POST),而後進行更完整的硬件檢測,並加載 ; 相關設備。 ; 三、接下來按啓動順序(Boot Sequence)讀取第一個設備的第一個扇區,若是該扇區最後兩個字節 ; 是 0x55 和 0xAA,代表這個設備能夠用於引導;若是不是,代表這個設備不能用於引導,BIOS ; 繼續讀取啓動順序中的下一個設備……直到找到啓動設備。BIOS 把第一個啓動設備的第一個扇區 ; 讀到內存 0x7C00 處,而後把控制權交給該處。 ; 四、操做系統經過改寫啓動設備的第一個扇區,被讀入內存後,從內存 0x7C00 處開始接管電腦。 ; **************************************************************************************** ; ======================================================================================== ; 頭文件及常量定義 ; ---------------------------------------------------------------------------------------- %include "./INC/Constant.inc" %include "./INC/FAT12.inc" ; ---------------------------------------------------------------------------------------- org 0x7C00 ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系統引導扇區的頭部(前 62 字節) FAT12Head _main, "NASM+GCC", "TestX_v0.01" ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系統引導扇區的引導代碼(從第 62 字節開始) ; ---------------------------------------------------------------------------------------- ; 程序入口 _main: cli cld xor eax, eax ; 初始化寄存器 mov ax, cs mov ds, ax mov ss, ax mov ax, 0x7C00 mov bp, ax mov sp, ax mov si, strBootMsg call PrintStr ; 尋找 Loader mov si, LoaderFileName call SearchFile ; 加載 Loader push SEGMENTBASEOFLOADER pop es mov bx, STACKSIZE call LoadFile ; 控制權交給已加載到內存的 loader jmp SEGMENTBASEOFLOADER : STACKSIZE ; 如下定義子函數 ; ---------------------------------------------------------------------------------------- ; 函數功能:尋找文件的起始位置 ; 入口參數:ds : si = 文件名的存放地址 ; 出口參數:ax = loader 文件的起始 FAT 表項序號 SearchFile: push bp mov bp, sp sub sp , 2 * 2 ; 爲局部變量分配空間 push di push si push dx push cx push bx ; 待讀取的根目錄區邏輯扇區號 mov word[bp - 2], IROOTDIRECTORYFIRSTSECTOR ; 待查找的根目錄區扇區數 mov word[bp - 2 * 2], IDATAFIRSTSECTOR - IROOTDIRECTORYFIRSTSECTOR mov di, si ; 逐個扇區尋找 push SEGMENTBASEOFTEMP ; Read1Sector 要用到 es pop es .Search_NextSector: mov ax, [bp - 2] xor bx, bx call Read1Sector ; cx 統計一個扇區內未匹配的表項數 mov cx, 16 ; = [BPB_BytsPerSec] / DirectoryItem_size .Search_ThisSector: ; 匹配文件名 mov si, di mov dx, 11 ; dx 統計未匹配的文件名字符數 .Match_FileName: lodsb cmp al, byte[es : bx] jnz .Match_NextItem dec dx jz .Found inc bx jmp .Match_FileName .Match_NextItem: and bx, 0b1111111111100000 ; 回當前表項的開始處 add bx, 32 ; 指向下一個表項(一個表項 32 字節,佔用 5 位) loop .Search_ThisSector ; 判斷是否讀完根目錄區全部扇區:讀完說明沒找到,沒讀完就繼續下一個 dec word[bp - 2 * 2] jz .NotFound inc word[bp - 2] jmp .Search_NextSector .NotFound: mov si, strNotFoundFile call PrintStr jmp $ .Found: mov ax, word[es : bx + 0x1A - 11 + 1] ; 指向當前表項中的 .DIR_FstClus pop bx pop cx pop dx pop si pop di mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函數功能:從軟盤裝載文件到內存 ; 入口參數:ax = 該文件的起始 FAT 表項序號 ; es : bx = 存放數據的內存緩衝區地址 ; 出口參數:無 LoadFile: push bp mov bp, sp push dx push cx push bx push ax .Load: push bx push ax add ax, IDATAFIRSTSECTOR - 2 ; FAT 表項序號轉換爲邏輯扇區號 call Read1Sector pop ax call GetEntryValue pop bx cmp ax, 0xFF8 ; FAT 表項的值大於等於 0xFF8,表示文件結束 jae .Return ; 未檢查壞扇區 —— 虛擬的不會壞的 add bx, BYTESPERSECTOR jmp .Load .Return: POP ax pop bx pop cx pop dx mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函數功能:取得 FAT 表中指定序號表項的值 ; 入口參數:ax = FAT 表項序號 ; 出口參數:ax = 對應的 FAT 表項值(即下一個扇區的 FAT 表項序號) GetEntryValue: push bp mov bp, sp push es ; 讀取 FAT 表時要使用 es 暫存數據 push dx push cx push bx ; 計算該表項序號所在的邏輯扇區號和在該扇區的偏移量 xor dx, dx ; 字節號(ax * 12 / 8) mov bx, 3 mul bx mov bx, 2 div bx mov cx, dx ; 保存字節號的奇偶性(0 = 偶數,1 = 奇數) xor dx, dx mov bx, BYTESPERSECTOR div bx add ax, IFATFIRSTSECTOR ; 邏輯扇區號 push dx ; 保存在該扇區的偏移量 ; 讀取連續 2 個扇區(表項可能跨扇區) push cx ; Read1Sector 函數改變了 cx、ax push ax push SEGMENTBASEOFTEMP pop es xor bx, bx call Read1Sector pop ax inc ax mov bx, BYTESPERSECTOR call Read1Sector pop cx ; 讀出 16 位,奇數項取高 12 位、偶數項取低 12 位(低低高高存放原則),獲得項值 pop bx ; 偏移量(上面壓進去的 dx 值) mov ax, [es : bx] jcxz .Even shr ax, 4 .Even: and ax, 0b0000111111111111 ; 奇數項高 4 位已爲 0 執行此操做值也不變 pop bx pop cx pop dx pop es mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函數功能:從軟盤讀取 1 個邏輯扇區 ; 入口參數:ax = 邏輯扇區號 ; es : bx = 存放數據的內存緩衝區地址 ; 出口參數:同 ah = 二、int 0x13 Read1Sector: push bp mov bp, sp push dx push cx ; 由 LBA 計算 CHS mov dl, 18 div dl mov ch, al mov dh, al mov cl, ah shr ch, 1 inc cl and dh, 1 ; 讀一個扇區 mov ax, 0x0201 xor dl, dl int 0x13 ; cmp ah, 0 ; 虛擬軟盤不會出錯 ; jz .Return ; call PrintMsg ; db "Error to read Floppy Disk !", `\r\n`, 0 ; jmp $ .Return: pop cx pop dx mov sp, bp pop bp ret ; ---------------------------------------------------------------------------------------- ; 函數功能:顯示字符串 ; 入口參數:ds : si = 字符串地址 ; 出口參數:無 PrintStr: push bp mov bp, sp push si push ax mov ah, 0x0E ; 功能號,0x0E:顯示一個字符,光標跟隨字符移動 .Print: lodsb cmp al, 0 ; 字符串以 0 結尾 je .Return int 0x10 jmp .Print .Return: pop ax pop si mov sp, bp pop bp ret ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系統引導扇區引導數據部分(字符串) strNotFoundFile db "Error 404", `\r\n`, 0 strBootMsg db "TestX is booting ...", `\r\n`, 0 LoaderFileName db "LOADER SYS", 0, 0 ; loader 文件名(8 + 3格式,長度不夠的填空格) ; **************************************************************************************** ; ======================================================================================== ; FAT12 文件系統引導扇區引導代碼的剩餘部分用 0 填滿,最後兩個字節置結束標誌(0xAA55) times 510 - ($ - $$) db 0 dw 0xAA55 ; ****************************************************************************************
; Loader.asm ; 加載程序 ; 四彩 ; 2015-11-12 [SECTION .text] ; ======================================================================================== ; 常量定義及其餘頭文件 ; ---------------------------------------------------------------------------------------- %include "./INC/Constant.inc" ; ---------------------------------------------------------------------------------------- org STACKSIZE ; **************************************************************************************** ; 程序入口 ; ======================================================================================== _main: ; 初始化寄存器 mov ax, cs mov ds, ax mov ss, ax mov bp, STACKSIZE mov sp, STACKSIZE call PrintMsg db "Loader is loaded ...", `\r\n`, 0 mov si, strHelloWorld mov cl, 0b00000010 mov dx, 0x0510 call ShowStr jmp $ strHelloWorld db "Hello World !", 0 ; ---------------------------------------------------------------------------------------- ; 函數功能:顯示緊跟在調用指令後定義的字符串 ; 入口參數:無 ; 出口參數:無 ; 注意:本函數改變了寄存器 ax、si 的值,若有必要,父函數應在調用前自行保存 PrintMsg: pop si ; si = ip mov ah, 0x0E ; 功能號,0x0E:顯示一個字符,光標跟隨字符移動 .Loop: lodsb cmp al, 0 ; 字符串以 0 結尾 je .Return int 0x10 jmp .Loop .Return: push si ; 恢復 ip ret ; ---------------------------------------------------------------------------------------- ; 函數功能:直接寫顯存顯示字符串 ; 入口參數:cl = 顏色屬性 ; dh、dl = 屏幕行(0 ~ 24)、列座標(0 ~ 79) ; ds : si = 待顯示字符串地址 ; 出口參數:無 ; 80 * 25 彩色字模式的顯存第一頁(共 4 頁)在內存中的地址爲 B8000H ~ B8F9FH,向該地址寫入 ; 內容將當即顯示在屏幕上,共可顯示 25 行、80 列,屏幕左上角爲原點(0,0)。 ; 每一個字符在顯存中佔兩個字節,第一個字節是 ASCII 碼,第二字節是顏色屬性(共 256 種): ; 位: 7 6 5 4 3 2 1 0 ; 含義: BL R G B I R G B ; 閃爍 背景顏色 高亮 前景顏色 ShowStr: push bp mov bp, sp push es push di push si push dx push cx push ax mov ax, 0x0B800 mov es, ax ; 由行列座標計算顯存偏移量 mov al, 160 mul dh mov di, ax mov al, 2 mul dl add di, ax .Loop: mov al, [ds : si] cmp al, 0 jz .Return mov [es : di], al mov [es : di + 1], cl inc si add di, 2 jmp .Loop .Return: pop ax pop cx pop dx pop si pop di pop es mov sp, bp pop bp ret ; ****************************************************************************************
看下運行效果,還不錯,今天的任務結束!oop