引导程序在引导扇区中,只有512字节的大小,引导程序难以完成许多工作,比如进入保护模式,加载内核,因此引导程序需要将这个工作交给一个拥有更大空间的程序,那就是Loader,引导程序只需将Loader加载入内存就好了,剩下的事情就由Loader来完成了。在《Linux内核完全剖析中》中可以看到,bootsect.S就是引导程序,而Setup.S则充当了Loader的功能。无论是加载Loader还是内核,都需要在实模式下调用BIOS中断来完成。源代码如下:
;�fine _BOOT_DEBUG_ ; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试
%ifdef _BOOT_DEBUG_
org 0100h ; 调试状态, 做成 .COM 文件, 可调试
%else
org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行
%endif
%ifdef _BOOT_DEBUG_
BaseOfStack equ 0100h ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长)
%else
BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)
%endif
BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址
OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址
RootDirSectors equ 14 ; 根目录占用空间
SectorNoOfRootDirectory equ 19 ; Root Directory 的第一个扇区号
DeltaSectorNo equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2
jmp short LABEL_START
nop
; 下面是 FAT12 磁盘的头
BS_OEMName DB 'ForrestY' ; 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 ; 逻辑扇区总数
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 'Tinix0.01 '; 卷标, 必须 11 个字节
BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节
;引导代码
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
;现在在根目录中寻找Loader.bin
mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootDirSizeForLoop], 0 ;判断是否遍历完了整个根目录
jz LABEL_NO_LOADERBIN
dec dword [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 ;需要遍历每个扇区的16个条目
;在目录的一个扇区中寻找Loader.bin
;这个扇区已经被加载到内存的BaseOfLoader:OffsetOfLoader处
LABEL_SEARCH_FOR_LOADERBIN:
cmp dx, 0 ;读完一个扇区,则读下一个扇区
jz LABEL_GOTO_NEXT_SECTOT_IN_ROOT_DIR
dec dx
mov cx, 11 ;文件名称+后缀一共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, 0FFF0h
add di, 20h
mov si, LoaderFileName
jmp LABEL_SEARCH_FOR_LOADERBIN ;在下一个目录中查找
LABEL_GOTO_NEXT_SECTOT_IN_ROOT_DIR:
inc word [wSectorNo]
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN
;没有找到Loader.bin
LABEL_NO_LOADERBIN:
mov dh, 2
call DispStr
%ifdef _BOOT_DEBUG_
mov ax, 4c00h
int 21h
%else
jmp $
%endif
;找到Loader.bin
LABEL_FILENAME_FOUND:
mov ax, RootDirSectors; //根目录扇区数
and di, 0FFF0h
add di, 01Ah
mov cx, word [es:di];将Loader.bin对应的开始簇号存入cx
push cx
add cx, ax
add cx, DeltaSectorNo ;此时cx变成了Loader.bin的起始扇区号
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 ;//将Loader.bin所在的扇区载入内容
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] ;bx=bx+512
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 ; LOADER.BIN 之文件名
MessageLength equ 9
BootMessage: db "Booting "; 9字节, 不够则用空格补齐. 序号 0
Message1 db "Ready. "; 9字节, 不够则用空格补齐. 序号 1
Message2 db "No LOADER"; 9字节, 不够则用空格补齐. 序号 2
DispStr:
mov ax, MessageLength
mul dh
add ax, BootMessage
mov bp, ax ;┓
mov ax, ds ;┣ es:bp=串地址
mov es, ax ;┛
mov cx, MessageLength ; cx = 串长度
mov ax, 01301h ; ah = 13, al = 01h
mov bx, 000ch ;页号为0(bh = 0) 黑底红字(bl = 0ch,高亮)
mov dl, 0
int 10h ; 10h 中断
ret
ReadSector: ;从ax个扇区开始,将cl个扇区读入到es:bx中
push bp
mov bp, sp
sub esp, 2
mov byte [bp-2], cl ;保存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 ;//读取错误时,CF置为1
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;判断余数是否为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, 1
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
pop bx
pop es
ret
times 510-($-$$) db 0 ;填充剩余的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ;结束标志