上回说到操作系统上电到引导扇区之间的程序,但是在结尾只是随便用一个测试程序来代替引导程序(就是第一个扇区的程序),今天我们来讲讲真正的引导程序。
但是在讲引导程序之前我们需要先学习FAT12文件系统,学了之后才会了解数据是如何存储在硬盘上面的(注意:是硬盘,此时数据还没有加载到内存上面)。
所有的文件系统会把磁盘分为若干个层次方便组织和管理,包括
1.扇区:磁盘上最小的数据单元
2.簇:一个或多个扇区
3.分区:通常指整个文件系统
引导扇区位于整个磁盘的0柱面(磁道)0磁头第一个扇区,在这个扇区有个很重要的数据结构叫做BPB(BIOS ParameterBlock)
以上是第一个扇区内部的格式紧接着第一个扇区的是FAT1和FAT2,分别占用9个扇区,FAT2之后是根目录区的第一个扇区,根目录区后面是数据区,整个文件系统如下
软盘(用当前时代的物代替就是硬盘、U盘)的作用是用来放置数据,简单来说就是读数据和写数据。读和写只是一个标志位的问题(权限问题,暂且不表)。它们的共同点都是寻找数据,而数据都是以文件的形式存在,那么整个文件系统的主要职责便是查找文件、读写文件
首先来分析根目录,它位于FAT2之后,开始的扇区是19号,它由若干个目录条目组成,条目最多有BPB_RootEntCnt个(这个数据在引导扇区中),所以长度不固定。根目录区中每一个条目占用32字节,格式如下
名称 | 偏移 | 长度 | 描述 |
DIR-Name | 0 | 0xB | 长度名8字节,扩展名三字节 |
DIR-Attr | 0xB | 1 | 文件属性 |
保留位 | 0xC | 10 | 保留位 |
DIR_WrtTime | 0x16 | 2 | 最后一次写入时间 |
DIR_WrtDate | 0x18 | 2 | 最后一次写入日期 |
DIR_FstClus | 0x1A | 2 | 此条目对应开始簇号 |
DIR_FileSize | 0x1C | 4 | 文件大小 |
通过访问根目录,可以得到根目录下的文件名,还有文件属性和文件地址以及最后读写日期。
属性表示文件可读可写的权限。
查找文件从文件开始的簇号开始
这样就完成了文件系统的主要任务,查找文件,读写文件。
由于根目录区的大小是不确定的,所以我们需要算一算第一个数据区的是在第几个扇区
假设根目录区共占用RootDirSectors=[(BPB_RootEntCnt * 32) + (BPB_BytsPerSec-1)]/BPB_BytsPerSec
之所以要加上BPB_BytsPerSec-1就是为了保证此公式在根目录区无法填满整个扇区时仍然能够成立。
数据区开始扇区号=根目录区开始扇区号+14
接着来说数FAT1和FAT2的作用,上文已经讲到通过一些公式可以直接查找到数据,那FAT的作用岂不是多余了吗?
其实并不然,上文讲述的方法只能访问到小于512字节的文件,若是该文件大于一个扇区,这个时候就需要FAT出马了。
FAT有两个,FAT2可以看作是FAT1的备份,他们通常是一样的。
在FAT中,每12位称为一个FAT项,代表一个簇。第零个和第一个FAT项始终不用,从第2个FAT项开始表示数据区的每一个簇,也就是说,第2个FAT项表示数据区的第一个簇。这和前面提到的数据区的第一个簇号是2相呼应。
通常FAT项的值代表的是文件下一个簇号,但如果值大于或等于0xFF8,则表示当前簇已经是本文件最后一个簇,如果值是0xFF7,表示它是一个坏簇。
访问一个文件有如下流程,首先通过根目录文件查找文件名,确定是哪一个条目,接着在条目中访问DIR_FstClus,这个里面有对应的开始簇号,若是文件大于512字节,需要分好几个扇区来存储数据,当第一个扇区号访问完后,通过FAT可以访问下一个簇号,对应的则是下一个扇区。这样即实现了文件访问的原理。
FAT12文件系统原理讲完了,现在来具体实现,首先按照对数据进行填充,若是我们用U盘重装过操作系统,应该会了解,这里面就是从U盘启动的第一份代码,它的主要功能自然是知道这个磁盘的相关信息,为以后继续从磁盘读取数据作准备,毕竟,操作系统还在磁盘里没有加载进来呢。
这份代码里面呢,我继续偷个懒,只是让引导程序能够工作就行,打印个字符串就行,引导代码放在下章写(忘记了就不写了),
;======================================
;boot.asm
;编译方法:nasm boot.asm -o boot.bin
;======================================
;%define _BOOT_DEBUG_ ;做Boot Sector时要注释掉
%ifdef _BOOT_DEBUG_
org 0100h
%else
org 07c00h ;Boot状态 bios将把boot sector加载到0:07c00处开始执行
%endif
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 'OrangeS0.02'; 卷标, 必须 11 个字节
BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节
LABEL_START:
mov ax, cs
mov ds, ax
mov es, ax
Call DispStr ; 调用显示字符串例程
jmp $ ; 无限循环
DispStr:
mov ax, BootMessage
mov bp, ax ; ES:BP = 串地址
mov cx, 16 ; CX = 串长度
mov ax, 01301h ; AH = 13, AL = 01h
mov bx, 000ch ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
mov dl, 0
int 10h ; int 10h
ret
BootMessage: db "Hello, OS world!"
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ; 结束标志
将这个代码写入磁盘引导扇区后,软盘已经能够被DOS以及linux识别了,可以方便的往上添加或删除程序了。