所以我們可以用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
沒有留言:
張貼留言