总感觉小日本的书有点敷衍的感觉,很多重要的知识点的没有讲,后面发现国内也有一本不错的操作系统书,于渊写的,还不错,理论知识讲解的也很周到。所以下面打算先看于渊的书先。好了,先贴代码,加注释分析
;%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