八、可以读 FAT12 了——BootSector 完工

    引导扇区只有 512 字节,太小,基本啥也干不了,我们只能利用引倒扇区把我们的操作系统内核载入内存。但是一下子把内核全部载入进来也不靠谱,还有很多事情没做呢——起码就还没转到安全模式,内核大的话,实模式可装不下。所以正确的姿势是:引导扇区载入一个装载程序,装载程序负责做好准备工作后,再载入真正的内核。

    那么引导扇区的任务就很明确了:找到装载程序(Loader.bin),然后把它载入内存。第十天初识FAT12时提到了:通过根目录表项找到文件的第 1 个扇区,再从 FAT 表中找到其他的扇区。

    今天的代码不是完全照书抄了,是按照我的理解重写的,代码里我很详细的注释了我的思路,而且结构也很清晰,子函数功能明确,基本是按照模块化设计的——我是深受 C 语言影响。引导扇区去掉填空的 0,大小只有 340 字节,还是比较满意的。如果有人有心在看的话,希望能提出意见。

; Constant.inc
; 常量
; 四彩
; 2015-11-12

%ifndef _CONSTANT_INC
%define _CONSTANT_INC

; ========================================================================================
; 内存中 0x0500 ~ 0x7BFF(29.75 KB) 段和 0x7E00 ~ 0xFFFF(32.5KB)段、
;        0x10000 ~ 0x9FBFF(575 KB)段可自由使用。引导扇区段在加载完 Loader 也可使用。
;
SEGMENTBASEOFTEMP       equ 0x7E0       ; 临时数据被加载到内存的段地址(最多 2 个扇区)
SEGMENTBASEOFLOADER     equ 0x4000      ; Loader.SYS 被加载到内存的段地址

STACKSIZE               equ 0x400       ; Loader 的堆栈大小
; ****************************************************************************************

%endif
; FAT12.inc
; FAT12 文件系统常量及宏定义
; 四彩
; 2015-11-08

%ifndef _FAT12_INC
%define _FAT12_INC

; ========================================================================================
BYTESPERSECTOR              equ 512 ; 每扇区字节数

IFATFIRSTSECTOR             equ 1   ; FAT 表的起始逻辑扇区号
IROOTDIRECTORYFIRSTSECTOR   equ 19  ; 根目录区的起始逻辑扇区号
IDATAFIRSTSECTOR            equ 33  ; 数据区的起始逻辑扇区号
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系统的引导扇区头部格式宏
; 调用格式:FAT12Head  Label_RealEntry, OEMName, VolLab
;           Label_RealEntry : 程序入口标签
;           OEMName         : 厂商名称(8 字节长,不够的填空格)
;           VolLab          : 卷标(11 字节长,不够的填空格)
%macro FAT12Head 3
;   名称                              偏移 长度 说明                      3.5英寸软盘内容
    jmp %1                          ; 0x00  3   跳转指令,指向程序入口     jmp RealEntry
    nop
    BS_OEMName      db %2           ; 0x03  8   厂商名称                   自行定义
    BPB_BytsPerSec  dw 512          ; 0x0B  2   每扇区字节数               512
    BPB_SecPerClus  db 1            ; 0x0D  1   每簇扇区数                 1
    BPB_RsvdSecCnt  dw 1            ; 0x0E  2   保留扇区数                 1
    BPB_NumFATs     db 2            ; 0x10  1   FAT表份数                  2
    BPB_RootEntCnt  dw 224          ; 0x11  2   根目录中最多容纳的文件数   224
    BPB_TotSec16    dw 2880         ; 0x13  2   扇区总数 (FAT12、16)     2880
    BPB_Media       db 0xF0         ; 0x15  1   介质描述符                 0xF0
    BPB_FATSz16     dw 9            ; 0x16  2   每个FAT表所占的扇区数      9
    BPB_SecPerTrk   dw 18           ; 0x18  2   每磁道扇区数               18
    BPB_NumHeads    dw 2            ; 0x1A  2   磁头数                     2
    BPB_HiddSec     dd 0            ; 0x1C  4   隐藏扇区数                 0
    BPB_TotSec32    dd 2880         ; 0x20  4   扇区总数(FAT32)          2880
    BS_DrvNum       db 0            ; 0x24  1   磁盘驱动器号               0
    BS_Reserved1    db 0            ; 0x25  1   保留(供NT使用)           0
    BS_BootSig      db 0x29         ; 0x26  1   扩展引导标记               0x29
    BS_VolD         dd 0            ; 0x27  4   卷标序列号                 0
    BS_VolLab       db %3           ; 0x2B  11  卷标                       自行定义
    BS_FileSysType  db 'FAT12'      ; 0x36  8   文件系统类型名             FAT12
                                    ; 0x3E  448 引导代码及其他填充字符
                                    ; 0x1FE 2   结束标志                   0xAA55
;
; BPB:BIOS Parameter Block,BIOS 参数块
; BS:Boot Sector,引导扇区
%endmacro
; ****************************************************************************************


; ========================================================================================
; 目录表项结构
struc DirectoryItem
;    字段名                           偏移 长度 说明
    .DIR_Name       resb    11      ; 0x00  11  文件名 8 + 3(大写,不够长度末尾填空格)
    .DIR_Attr       resb    1       ; 0x0B  1   文件属性
                    resb    10      ; 0x0C  10  保留
    .DIR_WrtTime    resw    1       ; 0x16  2   最后修改时间
    .DIR_WrtDate    resw    1       ; 0x18  2   最后修改日期
    .DIR_FstClus    resw    1       ; 0x1A  2   此条目对应的开始簇号(即 FAT 表项序号)
    .DIR_FileSize   resd    1       ; 0x1C  4   文件大小
endstruc
; ****************************************************************************************

%endif
; BootSector.asm
; 引导扇区
; 四彩
; 2015-11-12

; ========================================================================================
; 电脑的启动过程:
; 1、80x86 CPU 启动后(加电或复位),CS : IP 被设置为 0xFFFF : 0x0,CPU 从此处读取指令
;    开始执行。该单元在基本输入输出系统(Basic Input/Output System,BIOS)的地址范围内,
;    这里是一条跳转到 BIOS 中真正启动代码处的指令。
; 2、BIOS 首先进行加电自检(Power-On Self-Test,POST),然后进行更完整的硬件检测,并加载
;    相关设备。
; 3、接下来按启动顺序(Boot Sequence)读取第一个设备的第一个扇区,如果该扇区最后两个字节
;    是 0x55 和 0xAA,表明这个设备可以用于引导;如果不是,表明这个设备不能用于引导,BIOS
;    继续读取启动顺序中的下一个设备……直到找到启动设备。BIOS 把第一个启动设备的第一个扇区
;    读到内存 0x7C00 处,然后把控制权交给该处。
; 4、操作系统通过改写启动设备的第一个扇区,被读入内存后,从内存 0x7C00 处开始接管电脑。
; ****************************************************************************************


; ========================================================================================
; 头文件及常量定义
; ----------------------------------------------------------------------------------------
%include "./INC/Constant.inc"
%include "./INC/FAT12.inc"
; ----------------------------------------------------------------------------------------
    org 0x7C00
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系统引导扇区的头部(前 62 字节)
    FAT12Head _main, "NASM+GCC", "TestX_v0.01"
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系统引导扇区的引导代码(从第 62 字节开始)
; ----------------------------------------------------------------------------------------
; 程序入口
_main:
    cli
    cld
    xor eax, eax

    ; 初始化寄存器
    mov ax, cs
    mov ds, ax
    mov ss, ax
    mov ax, 0x7C00
    mov bp, ax
    mov sp, ax

    mov si, strBootMsg
    call PrintStr

    ; 寻找 Loader
    mov si, LoaderFileName
    call SearchFile

    ; 加载 Loader
    push SEGMENTBASEOFLOADER
    pop es
    mov bx, STACKSIZE
    call LoadFile

    ; 控制权交给已加载到内存的 loader
    jmp SEGMENTBASEOFLOADER : STACKSIZE


; 以下定义子函数
; ----------------------------------------------------------------------------------------
; 函数功能:寻找文件的起始位置
; 入口参数:ds : si = 文件名的存放地址
; 出口参数:ax = loader 文件的起始 FAT 表项序号
SearchFile:
    push bp
    mov bp, sp
    sub sp , 2 * 2                          ; 为局部变量分配空间

    push di
    push si
    push dx
    push cx
    push bx

    ; 待读取的根目录区逻辑扇区号
    mov word[bp - 2], IROOTDIRECTORYFIRSTSECTOR
    ; 待查找的根目录区扇区数
    mov word[bp - 2 * 2], IDATAFIRSTSECTOR - IROOTDIRECTORYFIRSTSECTOR
    mov di, si

    ; 逐个扇区寻找
    push SEGMENTBASEOFTEMP                  ; Read1Sector 要用到 es
    pop es
.Search_NextSector:
    mov ax, [bp - 2]
    xor bx, bx
    call Read1Sector
                                            ; cx 统计一个扇区内未匹配的表项数
    mov cx, 16                              ; = [BPB_BytsPerSec] / DirectoryItem_size
.Search_ThisSector:
    ; 匹配文件名
    mov si, di
    mov dx, 11                              ; dx 统计未匹配的文件名字符数
.Match_FileName:
    lodsb
    cmp al, byte[es : bx]
    jnz .Match_NextItem
    dec dx
    jz .Found
    inc bx
    jmp .Match_FileName

.Match_NextItem:
    and bx, 0b1111111111100000              ; 回当前表项的开始处
    add bx, 32                              ; 指向下一个表项(一个表项 32 字节,占用 5 位)
    loop .Search_ThisSector

    ; 判断是否读完根目录区所有扇区:读完说明没找到,没读完就继续下一个
    dec word[bp - 2 * 2]
    jz .NotFound
    inc word[bp - 2]
    jmp .Search_NextSector

.NotFound:
    mov si, strNotFoundFile
    call PrintStr
    jmp $

.Found:
    mov ax, word[es : bx + 0x1A - 11 + 1]   ; 指向当前表项中的 .DIR_FstClus

    pop bx
    pop cx
    pop dx
    pop si
    pop di

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函数功能:从软盘装载文件到内存
; 入口参数:ax = 该文件的起始 FAT 表项序号
;           es : bx = 存放数据的内存缓冲区地址
; 出口参数:无
LoadFile:
    push bp
    mov bp, sp

    push dx
    push cx
    push bx
    push ax

.Load:
    push bx
    push ax

    add ax, IDATAFIRSTSECTOR - 2            ; FAT 表项序号转换为逻辑扇区号
    call Read1Sector

    pop ax
    call GetEntryValue
    pop bx
    cmp ax, 0xFF8                           ; FAT 表项的值大于等于 0xFF8,表示文件结束
    jae .Return                             ; 未检查坏扇区 —— 虚拟的不会坏的

    add bx, BYTESPERSECTOR
    jmp .Load

.Return:
    POP ax
    pop bx
    pop cx
    pop dx

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函数功能:取得 FAT 表中指定序号表项的值
; 入口参数:ax = FAT 表项序号
; 出口参数:ax = 对应的 FAT 表项值(即下一个扇区的 FAT 表项序号)
GetEntryValue:
    push bp
    mov bp, sp

    push es                                 ; 读取 FAT 表时要使用 es 暂存数据
    push dx
    push cx
    push bx

    ; 计算该表项序号所在的逻辑扇区号和在该扇区的偏移量
    xor dx, dx                              ; 字节号(ax * 12 / 8)
    mov bx, 3
    mul bx
    mov bx, 2
    div bx

    mov cx, dx                              ; 保存字节号的奇偶性(0 = 偶数,1 = 奇数)

    xor dx, dx
    mov bx, BYTESPERSECTOR
    div bx
    add ax, IFATFIRSTSECTOR                 ; 逻辑扇区号
    push dx                                 ; 保存在该扇区的偏移量

    ; 读取连续 2 个扇区(表项可能跨扇区)
    push cx                                 ; Read1Sector 函数改变了 cx、ax
    push ax

    push SEGMENTBASEOFTEMP
    pop es
    xor bx, bx
    call Read1Sector
    pop ax
    inc ax
    mov bx, BYTESPERSECTOR
    call Read1Sector
    pop cx

    ; 读出 16 位,奇数项取高 12 位、偶数项取低 12 位(低低高高存放原则),得到项值
    pop bx                                  ; 偏移量(上面压进去的 dx 值)
    mov ax, [es : bx]
    jcxz .Even
    shr ax, 4
.Even:
    and ax, 0b0000111111111111              ; 奇数项高 4 位已为 0 执行此操作值也不变

    pop bx
    pop cx
    pop dx
    pop es

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函数功能:从软盘读取 1 个逻辑扇区
; 入口参数:ax = 逻辑扇区号
;           es : bx = 存放数据的内存缓冲区地址
; 出口参数:同 ah = 2、int 0x13
Read1Sector:
    push bp
    mov bp, sp

    push dx
    push cx

    ; 由 LBA 计算 CHS
    mov dl, 18
    div dl
    mov ch, al
    mov dh, al
    mov cl, ah
    shr ch, 1
    inc cl
    and dh, 1

    ; 读一个扇区
    mov ax, 0x0201
    xor dl, dl
    int 0x13

;    cmp ah, 0                               ; 虚拟软盘不会出错
;    jz .Return

;    call PrintMsg
;    db "Error to read Floppy Disk !", `\r\n`, 0
;    jmp $

.Return:
    pop cx
    pop dx

    mov sp, bp
    pop bp
    ret

; ----------------------------------------------------------------------------------------
; 函数功能:显示字符串
; 入口参数:ds : si = 字符串地址
; 出口参数:无
PrintStr:
    push bp
    mov bp, sp

    push si
    push ax

    mov ah, 0x0E                            ; 功能号,0x0E:显示一个字符,光标跟随字符移动
.Print:
    lodsb
    cmp al, 0                               ; 字符串以 0 结尾
    je .Return
    int 0x10
    jmp .Print

.Return:
    pop ax
    pop si

    mov sp, bp
    pop bp
    ret
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系统引导扇区引导数据部分(字符串)
    strNotFoundFile db "Error 404", `\r\n`, 0
    strBootMsg      db "TestX is booting ...", `\r\n`, 0
    LoaderFileName  db "LOADER  SYS", 0, 0  ; loader 文件名(8 + 3格式,长度不够的填空格)
; ****************************************************************************************


; ========================================================================================
; FAT12 文件系统引导扇区引导代码的剩余部分用 0 填满,最后两个字节置结束标志(0xAA55)
    times 510 - ($ - $$) db 0
    dw 0xAA55
; ****************************************************************************************
; Loader.asm
; 加载程序
; 四彩
; 2015-11-12

[SECTION .text]
; ========================================================================================
; 常量定义及其他头文件
; ----------------------------------------------------------------------------------------
%include "./INC/Constant.inc"

; ----------------------------------------------------------------------------------------
    org STACKSIZE
; ****************************************************************************************


; 程序入口
; ========================================================================================
_main:
   ; 初始化寄存器
    mov ax, cs
    mov ds, ax
    mov ss, ax
    mov bp, STACKSIZE
    mov sp, STACKSIZE

    call PrintMsg
    db "Loader is loaded ...", `\r\n`, 0

    mov si, strHelloWorld
    mov cl, 0b00000010
    mov dx, 0x0510
    call ShowStr
    jmp $

    strHelloWorld db "Hello World !", 0

; ----------------------------------------------------------------------------------------
; 函数功能:显示紧跟在调用指令后定义的字符串
; 入口参数:无
; 出口参数:无
; 注意:本函数改变了寄存器 ax、si 的值,如有必要,父函数应在调用前自行保存
PrintMsg:
    pop si                                  ; si = ip

    mov ah, 0x0E                            ; 功能号,0x0E:显示一个字符,光标跟随字符移动
.Loop:
    lodsb
    cmp al, 0                               ; 字符串以 0 结尾
    je .Return

    int 0x10
    jmp .Loop

.Return:
    push si                                 ; 恢复 ip
    ret


; ----------------------------------------------------------------------------------------
; 函数功能:直接写显存显示字符串
; 入口参数:cl = 颜色属性
;           dh、dl = 屏幕行(0 ~ 24)、列坐标(0 ~ 79)
;           ds : si = 待显示字符串地址
; 出口参数:无
; 80 * 25 彩色字模式的显存第一页(共 4 页)在内存中的地址为 B8000H ~ B8F9FH,向该地址写入
; 内容将立即显示在屏幕上,共可显示 25 行、80 列,屏幕左上角为原点(0,0)。
; 每个字符在显存中占两个字节,第一个字节是 ASCII 码,第二字节是颜色属性(共 256 种):
;  位:   7     6 5 4     3     2 1 0
; 含义:  BL    R G B     I     R G B
;        闪烁  背景颜色  高亮  前景颜色
ShowStr:
    push bp
    mov bp, sp

    push es
    push di
    push si
    push dx
    push cx
    push ax

    mov ax, 0x0B800
    mov es, ax

    ; 由行列坐标计算显存偏移量
    mov al, 160
    mul dh
    mov di, ax
    mov al, 2
    mul dl
    add di, ax

.Loop:
    mov al, [ds : si]
    cmp al, 0
    jz .Return
    mov [es : di], al
    mov [es : di + 1], cl
    inc si
    add di, 2
    jmp .Loop

.Return:
    pop ax
    pop cx
    pop dx
    pop si
    pop di
    pop es

    mov sp, bp
    pop bp
    ret

; ****************************************************************************************

   看下运行效果,还不错,今天的任务结束!


八、可以读 FAT12 了——BootSector 完工_第1张图片






你可能感兴趣的:(八、可以读 FAT12 了——BootSector 完工)