所以我們可以用NASM編譯出一個boot.img放到磁區中,記得0x01fe~0x0200要填入0xaa55標記為開機磁區,bios先把0x0000~0x0200的資料填入0x7c00~0x7dfe,檢查0x7dfe是否為0xaa55,若是,則到0x7c00執行開機程序
依此,我們可以寫一段範例如下
;%define _BOOT_DEBUG_ ; 設為DEBUG的話輸出成COM檔 %ifdef _BOOT_DEBUG_ org 0100h %else org 07c00h %endif ;================================================================================================ %ifdef _BOOT_DEBUG_ BaseOfStack equ 0100h ; DEBUG下stack位置為0x100 %else BaseOfStack equ 07c00h ; Boot下stack位置為0x7c00 %endif BaseOfLoader equ 09000h ; 設定LOADER.BIN被載到的段位地址 OffsetOfLoader equ 0100h ; 設定LOADER.BIN被載到的偏移地址 RootDirSectors equ 14 ; 根目錄佔用磁區數 RootDirSectors = ((BPB_RootEntCnt * 32) + (BPB_BytesPerSec - 1)) / BPB_BytsPerSec SectorNoOfRootDirectory equ 19 ; Root Directory的第一個磁區號碼FirstRootDirSecNum = BPB_RsvdSecCnt + (BPB_NumFATs * BPB_FATSz16) => (有2個FAT表1個佔9所以從19開始) SectorNoOfFAT1 equ 1 ; FAT1 的第一个磁區號碼 = BPB_RsvdSecCnt DeltaSectorNo equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2 ; 文件的開始Sector號碼 = DirEntry中的開始Sector号 + 根目錄佔用磁區數 + DeltaSectorNo ;================================================================================================ ;磁片1.44MB=2(磁頭)x80(磁道)x18(磁區)x512 bytes(磁區的大小)=2880x512 bytes = 1440 KB jmp short LABEL_START ; Start to boot. nop ; 必加nop ; 下面是 FAT12宣告 BS_OEMName DB ' NEO OS ' ; OEM String, 佔8個字節 BPB_BytsPerSec DW 512 ; 每磁區字節數 BPB_SecPerClus DB 1 ; 每簇多少磁區 BPB_RsvdSecCnt DW 1 ; Boot記錄佔用多少扇區 BPB_NumFATs DB 2 ; 共有幾張FAT表 BPB_RootEntCnt DW 224 ; 根目錄文件最大值 BPB_TotSec16 DW 2880 ; 邏輯磁區總數(2x80x18)=(磁頭數x磁道數x磁區數)=磁區號碼 BPB_Media DB 0xF0 ; 煤體描述符 BPB_FATSz16 DW 9 ; 每FAT磁區數目 BPB_SecPerTrk DW 18 ; 每磁道磁區數目 BPB_NumHeads DW 2 ; 磁頭數(幾面) BPB_HiddSec DD 0 ; 隱藏磁區數 BPB_TotSec32 DD 0 ; 如果wTotalSectorCount是0由這裡記錄磁區數 BS_DrvNum DB 0 ; 中斷13時的驅動裝置號碼 BS_Reserved1 DB 0 ; 未使用 BS_BootSig DB 29h ; 擴展影導標記 (29h) BS_VolID DD 0 ; 標籤序號 BS_VolLab DB 'neo boot1.0'; 標籤,必須11個字節 BS_FileSysType DB 'FAT12 ' ; 文件系統類型必須8個字節 ;============================================================================ ;變量 ;---------------------------------------------------------------------------- wRootDirSizeForLoop dw RootDirSectors ; Root Directory 要讀的磁區數,在循環中會減少至0. wSectorNo dw 0 ; 要讀的磁區號碼 bOdd db 0 ; 奇數或偶數 ;============================================================================ ;字符串 ;---------------------------------------------------------------------------- LoaderFileName db "LOADER BIN",0 ; LOADER.BIN 之文件名 ; 为简化代码, 下面每个字符串的长度均为 MessageLength BootMessage: db "Find->",0; 13游標移到開頭 10游標往下移一行 FinishLoaderMessage db 13,10,"Loading...",0; NoLoaderMessage db 13,10,"No LOADER",0; ;============================================================================ LABEL_START: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack ; 清除畫面 mov ax, 0002h int 10h mov ax, 0600h ; AH = 06, AL = 00h mov bx, 0700h ; 黑底白字(BL = 07h) mov cx, 0 ; 左上角: (0, 0) mov dx, 0184fh ; 右下角: (80, 50) int 10h ; int 10h mov si,BootMessage ; "Find->..." call Print mov si,LoaderFileName ; "LOADER.BIN" call Print xor ah, ah ; xor dl, dl ; 軟碟復位 int 13h ; ┛ ; 捜尋LOADER.BIN mov word [wSectorNo], SectorNoOfRootDirectory ;wSectorNo填入19表示根目錄從第19磁區開始 LABEL_SEARCH_IN_ROOT_DIR_BEGIN: cmp word [wRootDirSizeForLoop], 0 ;wRootDirSizeForLoop為剩下多少根目錄磁區未讀 jz LABEL_NO_LOADERBIN ; 判斷根目錄磁區是否完全讀完 dec word [wRootDirSizeForLoop] ; wRootDirSizeForLoop-1 mov ax, BaseOfLoader ;09000h mov es, ax ; es <- BaseOfLoader mov bx, OffsetOfLoader ; bx <- OffsetOfLoader 於是, es:bx = BaseOfLoader:OffsetOfLoader(9000h:0100h) mov ax, [wSectorNo] ; 把ax指向某個根目錄區磁區,ReadSector會用到 mov cl, 1 ;cl=1一次讀一個磁區 call ReadSector ;把磁區號瑪為[wSectorNo]的磁區讀到es:bx mov si, LoaderFileName ; ds:si -> "LOADER BIN" mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100 cld ; clears the direction flag mov dx, 10h ;根目錄文件最大值224根目錄佔14個磁區所以每個磁區16個檔案名 LABEL_SEARCH_FOR_LOADERBIN: cmp dx, 0 jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; dx=0表示讀完這磁區換下個磁區 dec dx mov cx, 11 ;"LOADER BIN"共11個字節 LABEL_CMP_FILENAME: cmp cx, 0 jz LABEL_FILENAME_FOUND ; 如果11個字符都相等表示找到 dec cx lodsb ; ds:si -> al 即"LOADER BIN"字串地址放入al cmp al, byte [es:di] ;[es:di]為ReadSector的檔案名 jz LABEL_GO_ON jmp LABEL_DIFFERENT ;不一樣就跳出 LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME ; LABEL_DIFFERENT: ;根目錄的格式.共32bytes= 20h ;名稱 |偏移(bytes)| 長度(bytes)| 描述 |舉例(loader.bin)| ;DIR_Name | 0 | 0xb |檔案名8byte,擴展名3byte| "LOADER BIN" | ;DIR_Attr | 0xb | 1 | 檔案屬性 | 0x20 | ;保留位 | 0xc | 10 | 保留位 | 0 | ;DIR_WrtTime | 0x16 | 2 | 最後一次寫入時間 | 0x7a5a | ;DIR_WrtDate | 0x18 | 2 | 最後一次寫入日期 | 0x3188 | ;DIR_FstClus | 0x1a | 2 | 此目錄項的開始簇編號 | 0x0002 | ;DIR_FileSize| 0x1c | 4 | 檔案大小 | 0x000000f | and di, 0FFE0h ; di -> 當前目錄項的開始 add di, 20h ; 每個檔案資訊佔32bytes=20h mov si, LoaderFileName ; jmp LABEL_SEARCH_FOR_LOADERBIN; LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: add word [wSectorNo], 1 ;word [wSectorNo]號碼+1指向root下一個磁區 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN LABEL_NO_LOADERBIN: mov si,NoLoaderMessage ; "No LOADER." call Print ; 显示字符串 %ifdef _BOOT_DEBUG_ mov ax, 4c00h ; int 21h ;DEBUG下没有找到 LOADER.BIN, 回到 DOS %else jmp $ ;root下没有找到 LOADER.BIN, 死循環 %endif LABEL_FILENAME_FOUND: ; 找到 LOADER.BIN mov ax, RootDirSectors ;根目錄佔用磁區數 and di, 0FFE0h ; di -> 當前目錄項的開始 add di, 01Ah ; di -> 此目錄項的開始簇編號 mov cx, word [es:di] ;word [es:di]指向LOADER.BIN的開始簇編號 push cx ; 保存此Sector在FAT中的序號 ;文件的開始Sector號碼 = DirEntry中的開始Sector號 + 根目錄佔用磁區數 + DeltaSectorNo add cx, ax ; cx = DirEntry中的開始Sector号 + 根目錄佔用磁區數 add cx, DeltaSectorNo ; cx = ... + DeltaSectorNo = 文件的開始Sector號碼 mov ax, BaseOfLoader mov es, ax ;es <- BaseOfLoader=9000h mov bx, OffsetOfLoader ; bx <- OffsetOfLoader = 0100h mov ax, cx ; ax <- 文件的開始Sector號碼 LABEL_GOON_LOADING_FILE: push ax ; | push bx ; | mov ah, 0Eh ; | mov al, '.' ; | 每讀一個磁區打一個點 mov bl, 0Fh ; | Booting ...... int 10h ; | pop bx ; | pop ax ; | mov cl, 1 call ReadSector ;把ax=磁區號瑪的磁區讀到es:bx AX是文件的開始Sector號碼 pop ax ; 取出此 Sector 在 FAT 中的序號 AX是DirEntry中的開始Sector號 call GetFATEntry cmp ax, 0FFFh jz LABEL_FILE_LOADED push ax ; 保存 Sector 在 FAT 中的序號 mov dx, RootDirSectors add ax, dx add ax, DeltaSectorNo add bx, [BPB_BytsPerSec] jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: mov si,FinishLoaderMessage ; "Ready." call Print ; 印出字串 ; ***************************************************************************************************** jmp BaseOfLoader:OffsetOfLoader ; 跳轉到LOADER.BIN的開始處 ; ***************************************************************************************************** ;---------------------------------------------------------------------------- ; 函數名: Print ;---------------------------------------------------------------------------- ; 作用: ; 將DS:SI地址字串印出 Print: lodsb ;從SI地址處加載一個字節到al or al,al ;檢查要顯示的串是否結束,如果結束,si給加載0×0進入al jz pe ;判斷是否相等即zf=0,則結束,跳至pe結束輸出 mov ah,0x0e ;10號中斷初始化,功能0EH 在Teletype模式下顯示字符 mov bx,0x007 ;顯示方式 int 0x10 ;打開中斷 jmp Print ;中斷響應,跳至print輸出 pe: ret ;---------------------------------------------------------------------------- ; 函數名: ReadSector ;---------------------------------------------------------------------------- ; 作用: ; 從第 ax 個 Sector 開始, 將 cl 個 Sector 讀入 es:bx 中 ReadSector: ; ----------------------------------------------------------------------- ; 2880個磁區號碼如何轉成磁盤位置(磁區號碼=(磁道號,起始磁區號,磁頭號)) ; ----------------------------------------------------------------------- ; 設磁區號碼為x ; ┌ 磁道號 = y >> 1 其實是(y/磁頭數)剛好磁頭數=2 ; x ┌ 商 y ┤ ; -------------- => ┤ └ 磁頭號 = y & 1 ; 每磁道磁區數量 │ ; └ 餘數 z => 起始磁區號 = z + 1 push bp mov bp, sp sub esp, 2 ; 劈出兩個字節的stack區放要讀磁區數: byte [bp-2] mov byte [bp-2], cl ;byte [bp-2] 放 1 表一次一個磁區 push bx mov bl, [BPB_SecPerTrk] ; bl為除數即每磁道磁區數量 div bl ; ax/bl後y在al中, z在ah中 inc ah ; z++ = 起始磁區號 mov cl, ah ; cl <- 起始磁區號 mov dh, al ; dh <- y shr al, 1 ; y >> 1 mov ch, al ; ch <- 磁道號 and dh, 1 ; dh & 1 = 磁頭號 pop bx mov dl, [BS_DrvNum] ; 驅動裝置號 (0 表示 軟碟) .GoOnReading: mov ah, 2 mov al, byte [bp-2] ; byte [bp-2]=1放入al表一次讀一個磁區 int 13h jc .GoOnReading ; 如果讀取錯誤CF->1跳回GoOnReading add esp, 2 ;esp加回2,stack區還原 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ;---------------------------------------------------------------------------- ; 作用: ; 找到序號为 ax 的 Sector 在 FAT 中的項目, 结果放在 ax 中 ; 需要注意的是, 中間需要讀 FAT 的磁區到 es:bx 處, 所以函數一開始保存了 es 和 bx GetFATEntry: push es push bx push ax mov ax, BaseOfLoader ;9000h sub ax, 0100h ; 在 BaseOfLoader 後面留出 4K 空間用來存放 FAT mov es, ax ; es<-8F00h pop ax mov byte [bOdd], 0 mov bx, 3 mul bx ; dx:ax = ax * 3 mov bx, 2 div bx ; dx:ax / 2 ==> ax <- 商, dx <- 餘數 cmp dx, 0 jz LABEL_EVEN mov byte [bOdd], 1 LABEL_EVEN:;偶數 ; 現在 ax 中是 FATEntry 在 FAT 中的偏移量,下面來 ; 計算 FATEntry 在哪個磁區中(FAT佔用不止一個磁區) xor dx, dx mov bx, [BPB_BytsPerSec] div bx ; dx:ax / BPB_BytsPerSec ; ax <- 商 (FATEntry 所在的磁區相對於 FAT 的磁區號) ; dx <- 餘數 (FATEntry 在磁區内的偏移)。 push dx mov bx, 0 ; bx <- 0 於是, es:bx = (BaseOfLoader - 100):00 add ax, SectorNoOfFAT1 ; 此句之后的 ax 就是 FATEntry 所在的磁區號 mov cl, 2 call ReadSector ; 讀取 FATEntry 所在的磁區, 一次讀兩個, 避免在邊界出錯, 因為 FATEntry 可能跨越兩個磁區 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 ; 填滿填到510 dw 0xaa55 ; 開機標記 times 1474560-($-$$) db 0找到LOADER.BIN時會載入到0x9000:0x0100 此時再跳轉到0x9000:0x0100 LOADER.BIN範例如下
org 0x100 mov ax,cs ;設置數據段 mov ds,ax mov es,ax jmp start ;跳到開始處執行 kelstr db 13,10,'Now Its in kernel',13,10,0 ;歡迎字符串定義 chbuf db '',0,0 ;臨時緩衝區定義,用來裝載輸入字符 ;-------------------------------------------------------------- ;將SI位置的字元印出,BL為顏色值(背景+前景)h print: lodsb ;從SI地址處加載一個字節到al cmp al,10 jz print10 cmp al,13 jz print13 or al,al ;檢查要顯示的串是否結束,如果結束,si給加載0×0進入al jz pe ;判斷是否相等即zf=0,則結束,跳至pe結束輸出 mov ah,0x09 ;10號中斷初始化,功能0EH 在Teletype模式下顯示字符 mov bh,0 ;顯示方式 mov cx,1 int 0x10 ;打開中斷 mov ah,03 int 0x10 inc dl mov ah,02 mov bh,0 int 0x10 jmp print ;中斷響應,跳至print輸出 print10: mov ah,03 int 0x10 mov ah,02 mov bh,0 mov dl,0 int 0x10 jmp print print13: mov ah,03 int 0x10 mov ah,02 mov bh,0 inc dh int 0x10 jmp print pe: ret ;-------------------------------------------------------------------- echo: ;回顯功能塊 mov bl,0xb ;顏色 mov ah,0 ;准備接受鍵盤字符,存入到al中 int 0x16 ;開16號鍵盤中斷 cmp al,13 jz press13 or al,al ;檢查要顯示的串是否結束,如果結束,si給加載0×0進入al mov ah,0x09 ;10號中斷初始化,功能0EH 在Teletype模式下顯示字符 mov bh,0 ;顯示方式 mov cx,1 int 0x10 ;打開中斷 mov ah,03 int 0x10 inc dl mov ah,02 mov bh,0 int 0x10 jmp echo ;無窮迴圈 press13: mov ah,03 int 0x10 mov ah,02 mov bh,0 inc dh mov dl,0 int 0x10 jmp echo ;-------------------------------------------------------------------- start: mov si,kelstr mov bl,0xf call print call echo
沒有留言:
張貼留言