总感觉小日本的书有点敷衍的感觉,很多重要的知识点的没有讲,后面发现国内也有一本不错的操作系统书,于渊写的,还不错,理论知识讲解的也很周到。所以下面打算先看于渊的书先。好了,先贴代码,加注释分析
;%define _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
%include "load.inc"
;================================================================================================
jmp short LABEL_START ; Start to boot.
nop ; 这个 nop 不可少
; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息
%include "fat12hdr.inc"
LABEL_START:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack ; 设置堆栈地址
; 清屏
mov ax, 0600h ; AH = 6, AL = 0h
mov bx, 0700h ; 黑底白字(BL = 07h)
mov cx, 0 ; 左上角: (0, 0)
mov dx, 0184fh ; 右下角: (80, 50)
int 10h ; int 10h
mov dh, 0 ; "Booting " 序号,根据序号显示不同的信息
call DispStr ; 显示字符串
xor ah, ah ;ah清0操作
xor dl, dl ;dl清0操作
int 13h ;进行软驱复位
;; ------------------------------------------------------------------------
;; 功能介绍
;; 在A盘的根目录寻找LOADER.BIN
;; 根目录的结构信息如下
;; 名称 偏移 长度 描述
;; DIR_NAME 0 0XB 文件名8字节,扩展名3字节
;; DIR_Attr 0xB 1 文件属性
;; 保留位 0xC 10 保留位
;; DIR_WrtTime 0x16 2 最后一次写入时间
;; DIR_WrtDate 0x16 2 最后一次写入日期
;; DIR_FstClus 0x1A 2 此条目对应的开始簇号
;; DIR_FileSize 0x1C 4 文件大小
;; 这里最重要的信息就是DIR_FstClus,即文件开始簇号,它告诉我们文件存放在磁盘的什么位置,从而让我们可以找到它。
;; -------------------------------------------------------------------------
mov word [wSectorNo], SectorNoOfRootDirectory ;SectorNoOfRootDirectory=19,将此值存入到wSectorNo内存处
;; 开始进行遍历搜索了
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootDirSizeForLoop], 0 ;根目录占用空间,占用了14个扇区
jz LABEL_NO_LOADERBIN ;判断根目录区是不是已经读完
dec word [wRootDirSizeForLoop] ;如果读完表示没找到LOADER.BIN
mov ax, BaseOfLoader ;09000h LOADER.BIN被加载到的位置--段地址
mov es, ax ;es <- BaseOfLoader
mov bx, OffsetOfLoader ;bx <- OffsetOfLoader 于是,es:bx=BaseOfLoader:OffsetOfLoader,0100h是LOADER.BIN被加载到的偏移地址
mov ax, [wSectorNo] ;ax <- Root Directory中的某Sector号
mov cl, 1 ;读取一个磁盘
call ReadSector ;读磁盘函数
mov si, LoaderFileName ;di:si -> "LOADER BIN"
mov di, OffsetOfLoader ;es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100
cld ;标志位df=0,为后面的loasb服务
mov dx, 10h ;每隔根目录占了32个字节,所以这里每个Sector需要循环16次
LABEL_SEARCH_FOR_LOADERBIN:
cmp dx, 0 ;循环次数控制
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;如果已经读完一个Sector
dec dx ;就跳到下一个Sector
mov cx, 11 ;根目录条目的前11个字节放着文件名
LABEL_CMP_FILENAME:
cmp cx, 0
jz LABEL_FILENAME_FOUND ;如果比较了11个字符都相等,表示找到了
dec cx
lodsb
cmp al, byte [es:di] ;取出每个字节进行比较
jz LABEL_GO_ON
jmp LABEL_DIFFERENT ;只要发现一个不一样的字符就表示本DirectoryEntry不是
;;我们要找的LOADER.BIN
LABEL_GO_ON:
inc di ;di++,让它指向下一个字符
jmp LABEL_CMP_FILENAME ;递归循环
;; 发现不符合情况下面的处理
LABEL_DIFFERENT:
and di, 0FFE0h ;di &= E0为了让它指向本条目开头
add di, 20h ; di += 20h 下一个目录条目
mov si, LoaderFileName
jmp LABEL_SEARCH_FOR_LOADERBIN
LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:
add word [wSectorNo], 1 ;根目录扇区+1
jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN ;继续读后续的扇区
;; 没有找到LAODER.BIN
LABEL_NO_LOADERBIN:
;xchg bx,bx
mov dh, 2 ;"NO LOADER."
call DispStr ;显示字符串
%ifdef _BOOT_DEBUG_
mov ax, 4c00h ;没有找到LOADER.BIN,回到DOS
int 21h
%else
jmp $
%endif
LABEL_FILENAME_FOUND:
jmp $
;; =========================================================================
;; 变量
;; =========================================================================
wRootDirSizeForLoop dw RootDirSectors ;Root Directory占用的扇区数,在循环中会递减为零
wSectorNo dw 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
;----------------------------------------------------------------------------
; 作用:
; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)
;AH=13H
;BH=页码
;BL=属性(若AL=00H或01H)
;CX=显示字符串长度
;(DH、DL)=坐标(行、列)
;ES:BP=显示字符串的地址 AL= 显示输出方式
;0—字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变
;1—字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
;2—字符串中含显示字符和显示属性。显示后,光标位置不变
;3—字符串中含显示字符和显示属性。显示后,光标位置改变
DispStr:
mov ax, MessageLength ;ax当前存储着字符串长度
mul dh ;dh显示字符串的序号(0*ax=0)
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, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h)
mov dl, 0
int 10h ; int 10h
ret
;; -------------------------------------------------------------------------
;; 函数名:ReadSector
;; -------------------------------------------------------------------------
;; 作用:
;; 从第ax个Sector开始,将cl个sector读入到es:bx中
;; bp串首地址
ReadSector:
; -----------------------------------------------------------------------
; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
; -----------------------------------------------------------------------
; 设扇区号为 x
; ┌ 柱面号 = y >> 1
; x ┌ 商 y ┤
; -------------- => ┤ └ 磁头号 = y & 1
; 每磁道扇区数 │
; └ 余 z => 起始扇区号 = z + 1
push bp
mov bp, sp ;sp堆栈地址
sub esp, 2 ;开辟出两个字节的堆栈区域保存要读的扇区数:byte [bp-2]
mov byte [bp-2], cl ;cl要读的Sector大小
push bx ;保存bx OffsetOfLoader=0100h
mov bl, [BPB_SecPerTrk] ;bl:除数 每隔磁道的扇区数
div bl ;y在al中,z在ah中
inc ah ;z++
mov cl, ah ;cl <- 起始扇区号
mov dh, al ;dh <- y
shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)
mov ch, al ; ch <- 柱面号
and dh, 1 ; dh & 1 = 磁头号
pop bx ; 恢复 bx
; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^
mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘)
.GoOnReading:
mov ah, 2 ; 读
mov al, byte [bp-2] ; 读 al 个扇区
int 13h
jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止
add esp, 2 ;恢复堆指针
pop bp ;恢复字符串首地址
ret
;----------------------------------------------------------------------------
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ; 结束标志
上面的代码是在实模式下面进行的,要看懂代码,需要掌握两个知识点:
第一:FAT12结构,需要按照FAT12的结构来进行操作
第二:要懂的如何加载磁盘内容到内存中
上面的代码大致做了一下的工作:
首先从软盘的19扇区,也就是根目录区开始,把扇区的内容复制到09000h:0100h处,每当复制一个扇区,那么就对这个扇区的根目录区进行检查,因为根目录区是由每一条为32个字节的条目组成,一个扇区有512个字节,那么我们要连续检查512/32=16次,因为这些条目中有一个字段是用来文件名的,这个文件名由11个字节组成,那么我们只要拿目标文件名称跟这11个字节相比,如果相同,那么就表示该根目录扇区中含有我们的目标文件,如果找到了就跳到LABEL_FILENAME_FOUND,简单进行jmp$操作,如果没有找到,那么继续遍历下一个条目,知道16个条目遍历完了,如果没有找到,那么继续加载下一个扇区的内容到09000h:0100h处,重复上面的检测,一直到14个扇区(因为我们在FAT32结构中定义了根目录扇区为14个),那么就结束。
具体分析见代码。
运行效果:
因为我们现在没有没有Loader.bin。这个bochs真不好用,一开起来我的linux就卡死住了。要不是为了调试我还真不想用你
下面给出调试的方法:
调试方式
1,首先要创建一个新的bm.img镜像文件,我们可以用bximage来进行创建
2,写入空白内容 dd if=/dev/null of=pm.img bs=512 count=1 conv=notrunc
3,使用 losetup 命令,将 pm.img.img 作为 loop device 使用: sudo losetup /dev/loop0 pm.img
4,然后,格式化这个 loop device: sudo mkfs.msdos /dev/loop0
5,检查文件系统 sudo fsck.msdos /dev/loop0
6,删除 loop device: sudo losetup -d /dev/loop0
7, sudo mount -o loop pm.img /mnt/floppy
8,sudo cp pmtest2.com /mnt/floppy/
9, sudo umount /mnt/floppy
10, bochsrc 末尾添加
#debug
magic_break: enabled=1
11,代码处在想调试的地方添加 xchg bx,bx