第四天 找到加载文件,引导成功

第一章   引导程序

第四天   找到加载文件,引导成功

今天再讲一个子程序,这个程序的功能为:给定一个簇号,查找在FAT区该簇号对应的值,即下一簇号。在我们读取文件数据时要经常用到,所以把它做为一个子程序。

这个子程序的流程如下:

1、 准备一块内存,用于临时存放读取FAT区的数据,不小于1024 byte

2、 计算簇号对应FAT区开始处的总偏移量,同时保存簇号奇偶标志,以备后面取值用;

3、 按总偏移量计算所在的总扇区号与相对扇区偏移量;

4、 读取2个扇区数据,因为FAT区中的项目有可能会跨越两个扇区;

5、 按扇区偏移量取相应的FAT数据,组成下一个簇号。

按上面的流程来阅读下面的代码,应该不难理解。相关FAT表的数据结构在前面已经讲过了,如果忘了可以再把前面的内容回顾一下。

 

;##########################################################################

; 函数名: GetFATEntry

;----------------------------------------------------------------------------

; 参数  : ax    需要查找与读取的簇号

; 返回值: ax    为对应FAT项的值,即文件数据所在的下一簇号,或结束,坏区

;----------------------------------------------------------------------------

; 用到的常量: BaseOfLoader      ,文件 loader.com 加载位置的段地址 = 09000h

;             SectorNoOfFAT1    FAT1的第一个扇区号 = 1

; 用到的变量: [bOdd]            ,奇偶标志字节 初使为 0

;             [BPB_BytsPerSec]  ,每扇区字节数

;----------------------------------------------------------------------------

; 作用:

; 找到序号为 ax 的簇号在 FAT 中的对应数值即下一簇号, 结果放在 ax 中。

; 需要注意的是中间需要读 FAT 的扇区到 (es-100):bx , 所以函数一开始保存了 es bx

;----------------------------------------------------------------------------

GetFATEntry:

        push    es

        push    bx

        ;-------------------------------

        push    ax

        mov     ax, BaseOfLoader       ; BaseOfLoader为一个常量,=9000H,作为加载配置程序的内存目标段地址。

        sub     ax, 0100h              ; BaseOfLoader 前面留出 4K 空间用于存放 FAT(这里是准备段数据,实际的偏移为减少1000h,为4K

        mov     es, ax                 ; loader.com 加载位置前4K的空间地址段保存到 ES 中,此空间用于存放读取的FAT表数据。

        pop     ax

        ;-------------------------------

 

        mov     byte [bOdd], 0         ; 清奇偶校检标志位,bOdd 标号定义了一个全局变量,也可以用上篇学习的局部变量代替。

        mov     bx, 3                  ; 此时AX为需要查找与读取的簇号

        mul     bx                     ; ax * 3 ==> dx:ax

        mov     bx, 2

        div     bx                     ; dx:ax / 2  ==>  ax <- , dx <- 余数

                                       ; 此时AX为需要查找的簇号在FAT表的总偏移量(以字节为单位)。

 

        cmp     dx, 0                  ; 余数是否为0,

        jz      .LABEL_EVEN            ; 等于0为偶数,否则为奇数.

        mov     byte [bOdd], 1         ; 奇数,保存标志.

 

.LABEL_EVEN:                           ;

        xor     dx, dx                 ; 现在ax中是 簇号 在 FAT 中的总偏移量. 下面来计算 簇号 在哪个扇区中(FAT区占用不止一个扇区)

        mov     bx, [BPB_BytsPerSec]   ; 取扇区的字节数,一般情况下=512

        div     bx                     ; dx:ax / 512  ==>   ax <-    (所查的簇号位于FAT区的第几个扇区)

                                       ; dx <- 余数 (簇号在该扇区内的偏移量),得到需要查找的簇号在FAT区中的扇区号ax和偏移量dx

 

        ;-------------------------------

        push    dx                     ; 暂存偏移量

        mov     bx, 0                  ; 数据保存位置偏移量,段为 ES

                                       ; bx <- 0    于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h

        add     ax, SectorNoOfFAT1     ; FAT 开始扇区号一般为 1 . SectorNoOfFAT1是一个常量,=1

        ; 此句执行之后的 ax 就是 FAT项 所在的实际总扇区号

 

        mov     cl, 2                  ; 读取 2 个扇区

        call    ReadSector             ; 读取 FAT项 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FAT项 可能跨越两个扇区

        pop     dx

        ;-------------------------------

 

        mov     bx, dx                 ; dx FAT项目所扇区起始偏移量

        mov     ax, [es:bx]            ; 从该扇区的偏移量读取一个字(16位)

        cmp     byte [bOdd], 1         ; 是否是奇数

        jnz     .LABEL_EVEN_2          ; 不等于则为偶数,跳转

        shr     ax, 4                  ; 如果是奇数,右移4.

 

.LABEL_EVEN_2:

        and     ax, 0FFFh              ; 如果是偶数,取后12.

 

        pop     bx

        pop     es

        ret

;============================================================================

 

我本想结束今天的内容,但是我想大家也许士气正旺,那我们今天就把引导代码结束,从明天开始就是一个新的天地了,努力吧!

 

所有涉及的知识在前面都已讲过了,需要的子程序也已准备好了,接下来就是将指定的文件从软盘读取到内存中,当你理解了上面的代码,你一定就会有信心,因为这已不是什么困难的事了。

先看一下程序流程:

1、 读取根目录区一个扇区数据到内存,如果到结束则跳转9

2、 查找该扇区是否存在目标文件名;

3、 如果没有则跳转至1

4、 取该文件保存数据的开始簇号;

5、 按簇号读取数据到指定内存;

6、 取下簇号并检查是否结束;

7、 未结束则跳转至5

8、 打印信息并结束;

9、 打印错误信息并结束;

 

;############################################################

; 下面在 A 盘的根目录寻找 LOADER.COM

;------------------------------------------------------------

        mov     word [wSectorNo], SectorNoOfRootDirectory   ; 保存根目录的第一个扇区号到扇区计数变量中。

 

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:

        cmp     word [wRootDirSizeForLoop], 0   ; 初使值为根目录所占扇区数,必须在使用前设初使值,初使值为14,为循环计数器。

        jz      LABEL_NO_LOADERBIN              ; 判断根目录区是不是已经读完,如果读完表示没有找到 LOADER.com

        dec     word [wRootDirSizeForLoop]      ; 不等于0,则计数器减一,继续。

 

; 读取目录区一个扇区的数据

        mov     ax, BaseOfLoader        ; 设目标段基址。

        mov     es, ax                  ; es <- BaseOfLoader   读取目标数据保存内存的基址

        mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader 保存数据内存的偏移。于是, es:bx = BaseOfLoader:OffsetOfLoader

        mov     ax, [wSectorNo]         ; ax <- Root Directory 根目录区中的 Sector 号的扇区数据到指定内存。

        mov     cl, 1                   ; 读取目录区的1个扇区数据

        call    ReadSector              ; 调用 读取扇区 子程序

; 参数为:ax起始扇区号,cl读取扇区数,es:bx读取数据保存地址。

 

; 判断读取的目录项中文件名是否是所要查找的文件名。

        mov     si, LoaderFileName      ; ds:si -> "LOADER  com" 源文件名字符串指针

        mov     di, OffsetOfLoader      ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100 目标文件名字符串指针

        cld                             ; 清标志位CF,即设置串操作增量向后移动。

        mov     dx, 10h                 ; 每个扇区共有16个目录项。

 

LABEL_SEARCH_FOR_LOADERBIN:

        cmp     dx, 0                   ; 循环次数控制,16个目录项是否都比较完了。

        jz      LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR      ; 如果已经比较完了,就读取下一个扇区数据。

        dec     dx                      ; 目录项计数器减一

        mov     cx, 11                  ; 设置字符串长度,文件名(8)及扩展名(3)字符长度(共11)。

 

LABEL_CMP_FILENAME:

        cmp     cx, 0

        jz      LABEL_FILENAME_FOUND    ; 如果比较了 11 个字符都相等, 表示找到

        dec     cx

        lodsb                           ; ds:si -> al 读取一个字符。

        cmp     al, byte [es:di]        ; 比较字符

        jz      LABEL_GO_ON             ; 相等则跳转到继续。

        jmp     LABEL_DIFFERENT         ; 只要发现不一样的字符就表明本 DirectoryEntry(目录项) 不是。

 

LABEL_GO_ON:

        inc     di                      ; 目标文件名字符串指针增一,指向下一个字符。

        jmp     LABEL_CMP_FILENAME      ; 继续循环

 

LABEL_DIFFERENT:

        and     di, 0FFE0h              ; di &= E0 为了让它指向本条目开头

        add     di, 20h                 ; 增加偏移量,di += 20h 指向下一个目录条目。

        mov     si, LoaderFileName      ; LoaderFileName 为加载程序文件名地址指针。

        jmp     LABEL_SEARCH_FOR_LOADERBIN

 

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:     ; 读取下一个根目录扇区

        add     word [wSectorNo], 1     ; 根目录扇区号加一

        jmp     LABEL_SEARCH_IN_ROOT_DIR_BEGIN  ; 跳转回

 

;############################################################

; 没有找的 LOADER.BIN 文件,显示信息并死循环。

;------------------------------------------------------------

LABEL_NO_LOADERBIN:

        mov     dh, 2                   ; 显示编号2字符串:"No LOADER."

        call    DispStr                 ; 调用字符串子程序

        jmp     $                       ; 到这里死循环

 

;############################################################

; 我们要找的 LOADER.BIN 文件,读取文件数据。

;------------------------------------------------------------

LABEL_FILENAME_FOUND:                   ; 找到 LOADER.com 后便来到这里继续

        mov     ax, RootDirSectors      ; 主目录

        and     di, 0FFE0h              ; di -> 当前条目的开始

        add     di, 01Ah                ; di -> Sector

        mov     cx, word [es:di]

        push    cx                      ; 保存此 Sector FAT 中的序号

        add     cx, ax

        add     cx, DeltaSectorNo       ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 ( 0 开始数的序号)

        mov     ax, BaseOfLoader

        mov     es, ax                  ; es <- BaseOfLoader

        mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader  于是, es:bx = BaseOfLoader:OffsetOfLoader

; = BaseOfLoader * 10h + OffsetOfLoader

        mov     ax, cx                  ; ax <- Sector

 

LABEL_GOON_LOADING_FILE:

        push    ax

        push    bx

        mov     ah, 0Eh

        mov     al, '.'                 ; 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果: Booting ......

        mov     bl, 0Fh

        int     10h 

        pop     bx  

        pop     ax  

 

        mov     cl, 1

        call    ReadSector              ; 读取一个扇区

        pop     ax                      ; 取出此 Sector FAT 中的序号

        call    GetFATEntry

        cmp     ax, 0FFFh

        jz      LABEL_FILE_LOADED

        push    ax                      ; 保存 Sector FAT 中的序号

        mov     dx, RootDirSectors

        add     ax, dx

        add     ax, DeltaSectorNo

        add     bx, [BPB_BytsPerSec]

        jmp     LABEL_GOON_LOADING_FILE

 

LABEL_FILE_LOADED:

;-----------------------------------------------------------------------------------------------------

; 从软盘读取文件 loader.bin 结束

;==================================================================

 

现在把前面的代码组合起来就完成了我们的全面引导工作,这是一个支持DOSWINDOWSLinuxFAT12文件格式的引导系统。完整的代码如下:

; boot_A.asm

;###########################################################################################

;

; 这是一段引导程序,本程序是将加载程序加载至 9000:0100 处,将控制权转移到加载程序。

;

;###########################################################################################

 

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

; *                              *

; * 本文档代码仅适用 [软驱] 启动 *

; *                              *

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

 

;-----------------------------------------------------------------------------------------

org  07c00h                     ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行

[bits 16]                       ; 定义以下代码生成为16位代码。

 

;================================================================================================

; 常量定义

;================================================================================================

 

BaseOfStack             equ     07c00h  ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长)

 

    ; 加载程序地址

    ;-------------

BaseOfLoader            equ     09000h  ; LOADER.com 被加载到的位置 ---- 段地址

OffsetOfLoader          equ      0100h  ; LOADER.com 被加载到的位置 ---- 偏移地址

 

    ; 读取 FAT 软盘所需的常量

    ;------------------------

RootDirSectors          equ     14      ; 根目录占用空间

SectorNoOfRootDirectory equ     19      ; 根目录的第一个扇区号

SectorNoOfFAT1          equ     1       ; FAT1 的第一个扇区号   = BPB_RsvdSecCnt

DeltaSectorNo           equ     17      ; DeltaSectorNo = BPB_RsvdSecCnt +(BPB_NumFATs * FATSz)-2

                                        ; 文件的开始Sector = DirEntry中的开始Sector + 根目录

                                        ; 占用Sector数目 + DeltaSectorNo

;================================================================================================

 

 

 

;================================================================================================

        jmp short LABEL_START           ; 跳转至程序开始处。

        nop                             ; 这个 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 'Xos System '; 卷标, 必须 11 个字节

        BS_FileSysType  DB 'FAT12   '   ; 文件系统类型, 必须 8个字节

 

 

 

;       #############################

;       #                           #

;       #       主程序代码开始      #

;       #                           #

;       #############################

 

LABEL_START:

        mov     ax, cs                  ; 取代码所在段,其他段均与代码段一致。因为在16位下,只能寻址64K范围。

        mov     ds, ax                  ; 数据段

        mov     es, ax                  ; 附加段

        mov     ss, ax                  ; 堆栈段

        mov     sp, BaseOfStack         ; 堆栈基地址。一般情况,此时的堆栈只是临时的,到了加载模块时还要重新设置,

                                        ;   由其是进入32位代码模块后,才是最重要的。

        mov     [0523h], dl             ; 保存启动盘号,A盘为0,硬盘C80

 

        ;####################################################

        ; 清屏

        ;----------------------------------------------------

        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清零

        xor     dl, dl  ; ┣ 软驱复位   dl清零

        int     13h     ;             调用 13h BIOS中断服务

        ;============================

 

;############################################################

; 下面在 A 盘的根目录寻找 LOADER.COM

;------------------------------------------------------------

        mov     word [wSectorNo], SectorNoOfRootDirectory   ; 保存根目录的第一个扇区号到扇区计数变量中。

 

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:

        cmp     word [wRootDirSizeForLoop], 0   ; 初使值为根目录所占扇区数,必须在使用前设初使值,初使值为14,为循环计数器。

        jz      LABEL_NO_LOADERBIN              ; 判断根目录区是不是已经读完,如果读完表示没有找到 LOADER.com

        dec     word [wRootDirSizeForLoop]      ; 不等于0,则计数器减一,继续。

 

; 读取目录区一个扇区的数据

        mov     ax, BaseOfLoader        ; 设目标段基址。

        mov     es, ax                  ; es <- BaseOfLoader   读取目标数据保存内存的基址

        mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader 保存数据内存的偏移。于是, es:bx = BaseOfLoader:OffsetOfLoader

        mov     ax, [wSectorNo]         ; ax <- Root Directory 根目录区中的 Sector 号的扇区数据到指定内存。

        mov     cl, 1                   ; 读取目录区的1个扇区数据

        call    ReadSector              ; 调用 读取扇区 子程序

; 参数为:ax起始扇区号,cl读取扇区数,es:bx读取数据保存地址。

 

; 判断读取的目录项中文件名是否是所要查找的文件名。

        mov     si, LoaderFileName      ; ds:si -> "LOADER  com" 源文件名字符串指针

        mov     di, OffsetOfLoader      ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100 目标文件名字符串指针

        cld                             ; 清标志位CF,即设置串操作增量向后移动。

        mov     dx, 10h                 ; 每个扇区共有16个目录项。

 

LABEL_SEARCH_FOR_LOADERBIN:

        cmp     dx, 0                   ; 循环次数控制,16个目录项是否都比较完了。

        jz      LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR      ; 如果已经比较完了,就读取下一个扇区数据。

        dec     dx                      ; 目录项计数器减一

        mov     cx, 11                  ; 设置字符串长度,文件名(8)及扩展名(3)字符长度(共11)。

 

LABEL_CMP_FILENAME:

        cmp     cx, 0

        jz      LABEL_FILENAME_FOUND    ; 如果比较了 11 个字符都相等, 表示找到

        dec     cx

        lodsb                           ; ds:si -> al 读取一个字符。

        cmp     al, byte [es:di]        ; 比较字符

        jz      LABEL_GO_ON             ; 相等则跳转到继续。

        jmp     LABEL_DIFFERENT         ; 只要发现不一样的字符就表明本 DirectoryEntry(目录项) 不是。

 

LABEL_GO_ON:

        inc     di                      ; 目标文件名字符串指针增一,指向下一个字符。

        jmp     LABEL_CMP_FILENAME      ; 继续循环

 

LABEL_DIFFERENT:

        and     di, 0FFE0h              ; di &= E0 为了让它指向本条目开头

        add     di, 20h                 ; 增加偏移量,di += 20h 指向下一个目录条目。

        mov     si, LoaderFileName      ; LoaderFileName 为加载程序文件名地址指针。

        jmp     LABEL_SEARCH_FOR_LOADERBIN

 

LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:     ; 读取下一个根目录扇区

        add     word [wSectorNo], 1     ; 根目录扇区号加一

        jmp     LABEL_SEARCH_IN_ROOT_DIR_BEGIN  ; 跳转回

 

;############################################################

; 没有找的 LOADER.BIN 文件,显示信息并死循环。

;------------------------------------------------------------

LABEL_NO_LOADERBIN:

        mov     dh, 2                   ; 显示编号2字符串:"No LOADER."

        call    DispStr                 ; 调用字符串子程序

        jmp     $                       ; 到这里死循环

 

;############################################################

; 我们要找的 LOADER.BIN 文件,读取文件数据。

;------------------------------------------------------------

LABEL_FILENAME_FOUND:                   ; 找到 LOADER.com 后便来到这里继续

        mov     ax, RootDirSectors      ; 主目录

        and     di, 0FFE0h              ; di -> 当前条目的开始

        add     di, 01Ah                ; di -> Sector

        mov     cx, word [es:di]

        push    cx                      ; 保存此 Sector FAT 中的序号

        add     cx, ax

        add     cx, DeltaSectorNo       ; 这句完成时 cl 里面变成 LOADER.BIN 的起始扇区号 ( 0 开始数的序号)

        mov     ax, BaseOfLoader

        mov     es, ax                  ; es <- BaseOfLoader

        mov     bx, OffsetOfLoader      ; bx <- OffsetOfLoader  于是, es:bx = BaseOfLoader:OffsetOfLoader

; = BaseOfLoader * 10h + OffsetOfLoader

        mov     ax, cx                  ; ax <- Sector

 

LABEL_GOON_LOADING_FILE:

        push    ax

        push    bx

        mov     ah, 0Eh

        mov     al, '.'                 ; 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果: Booting ......

        mov     bl, 0Fh

        int     10h 

        pop     bx  

        pop     ax  

 

        mov     cl, 1

        call    ReadSector              ; 读取一个扇区

        pop     ax                      ; 取出此 Sector FAT 中的序号

        call    GetFATEntry

        cmp     ax, 0FFFh

        jz      LABEL_FILE_LOADED

        push    ax                      ; 保存 Sector FAT 中的序号

        mov     dx, RootDirSectors

        add     ax, dx

        add     ax, DeltaSectorNo

        add     bx, [BPB_BytsPerSec]

        jmp     LABEL_GOON_LOADING_FILE

 

LABEL_FILE_LOADED:

;-----------------------------------------------------------------------------------------------------

; 从软盘读取文件 loader.bin 结束

;==================================================================

 

 

        ;##############################################

        ; 显示字符串

        ;----------------------------------------------

        mov     dh, 1                   ; "Ready."

        call    DispStr                 ; 显示字符串

        ;==============================================

 

 

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

        jmp     BaseOfLoader:OffsetOfLoader     ; 这一句正式跳转到已加载到内存中的 LOADER.COM 的开始处

                                                ; 开始执行 LOADER.BIN 的代码

                                                ; Boot Sector 的使命到此结束

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

 

;############################################################

;

;       主程序代码结束

;

;############################################################

 

;============================================================================

;变量定义

;----------------------------------------------------------------------------

wRootDirSizeForLoop     dw      RootDirSectors  ; 根目录占用的扇区数, 在循环中会递减至零.

wSectorNo               dw      0               ; 要读取的扇区号

bOdd                    db      0               ; 奇数还是偶数

;----------------------------------------------------------------------------

 

;============================================================================

;字符串定义

;----------------------------------------------------------------------------

LoaderFileName          db      "LOADER  COM", 0        ; LOADER.com 之文件名在磁盘目录项中记录文本。

; 为简化代码, 下面每个字符串的长度均为 MessageLength

MessageLength           equ     9

BootMessage:            db      "Booting  "; 9字节, 不够则用空格补齐. 序号 0

Message1                db      "Boot OK! "; 9字节, 不够则用空格补齐. 序号 1

Message2                db      "No LOADER"; 9字节, 不够则用空格补齐. 序号 2

;============================================================================

 

;############################################################

;

;       用到的几个子程序

;

;############################################################

 

;############################################################################

; 函数名: DispStr

; 参数  : dh 为需显示字符串的序号

;----------------------------------------------------------------------------

; 作用:

;       显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based)

;----------------------------------------------------------------------------

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, 0007h               ; 页号为0(BH = 0) 黑底白字(BL = 07h)

        mov     dl, 0

        int     10h                     ; int 10h

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

                ;功能描述: 在Teletype模式下显示字符串

                ;入口参数: AH13H     BH=页码   BL=属性(AL=00H01H)

                ;           CX=显示字符串长度     (DHDL)=坐标(行、列)

                ;           ES:BP=显示字符串的地址

                ;           AL= 显示输出方式

                ; 0——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置不变

                ; 1——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变

                ; 2——字符串中含显示字符和显示属性。显示后,光标位置不变

                ; 3——字符串中含显示字符和显示属性。显示后,光标位置改变

                ;出口参数: 无

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

        ret

;============================================================================

 

;##########################################################################

; 函数名: ReadSector

; 参数  : ax 需读取开始位置的总扇区号,这时的参数的总扇区号从0开始编号,以便统一。

;         cl 需读取的扇区数

;         es:bx 读取取的数据保存位置

; 返回值:

 

; 可能改变的寄存器有: AX , BX CX , DX

; 用到的变量: [BPB_SecPerTrk] 每磁道扇区数,

;             [BS_DrvNum]     当前驱动器号

;----------------------------------------------------------------------------

; 作用:  从第 ax 个扇区开始, 读取 cl 个扇区数据到内存 es:bx

;----------------------------------------------------------------------------

 

        ; -----------------------------------------------------------------------

        ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 磁道号, 起始扇区, 盘面号)

        ; -----------------------------------------------------------------------

        ; 设总扇区号为 x

        ;                           ┌ 磁道号 = y >> 1

        ;       x           ┌ 商 y

        ; -------------- =>       └ 盘面号 = y & 1

        ;  每磁道扇区数    

        ;                   └ 余 z => 起始扇区号 = z + 1

        ; -----------------------------------------------------------------------

 

ReadSector:

        push    bp                      ; 保存指针

        mov     bp, sp                  ;

        sub     sp, 2                   ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2],即为局部变量。

        mov     byte [bp-2], cl         ; 保存需读取的扇区数,后面计算时需要用到CL寄存器。

 

        ;-------------------------------

        push    bx                      ; 保存 bx,下面的计算会占用BX寄存号。

        mov     bl, [BPB_SecPerTrk]     ; bl: 除数 , 每磁道扇区数

        div     bl                      ; y al , z ah

;       此时AX为开始的扇区号,bl为每磁道扇区数,除出来的结果商:al 余数:ah

 

        inc     ah                      ; z++, 余数加1 ,得到在磁道中的开始扇区号,因为中断扇区号是从1开始编号。

        mov     cl, ah                  ; cl <- 起始扇区号, 将在磁道中开始扇区号保存到 cl

 

        ; 下面计算磁道号与盘面号

        mov     dh, al                  ; dh <- y    将余数存入 dh .

        shr     al, 1                   ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2)  右移一位,等于除以2

        mov     ch, al                  ; ch <- 磁道号

        and     dh, 1                   ; dh & 1 = 盘面号

        pop     bx                      ; 恢复 bx

        ;-------------------------------

 

        ; 至此, "磁道号, 起始扇区, 盘面号" 全部得到 - 磁道号: ch   盘面号: dh   开始扇区号: cl

 

        mov     dl, [BS_DrvNum]         ; 取出启动的驱动器号,(0 表示 A )

 

.GoOnReading:

        mov     ah, 2                   ; 读取功能

        mov     al, byte [bp-2]         ; al 个扇区

        int     13h                     ; 调用中断,进行磁盘扇区读取.

 

        jc      .GoOnReading            ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止

        ;--------------------------------------------------------------------

 

        add     sp, 2                   ; 释放局部变量

        pop     bp                      ; 恢复环境并退出

        ret

;==================================================================

 

;##########################################################################

; 函数名: GetFATEntry

;----------------------------------------------------------------------------

; 参数  : ax    需要查找与读取的簇号

; 返回值: ax    为对应FAT项的值,即文件数据所在的下一簇号,或结束,坏区

;----------------------------------------------------------------------------

; 用到的常量: BaseOfLoader      ,文件 loader.com 加载位置的段地址 = 09000h

;             SectorNoOfFAT1    FAT1的第一个扇区号 = 1

; 用到的变量: [bOdd]            ,奇偶标志字节 初使为 0

;             [BPB_BytsPerSec]  ,每扇区字节数

;----------------------------------------------------------------------------

; 作用:

; 找到序号为 ax 的簇号在 FAT 中的对应数值即下一簇号, 结果放在 ax 中。

; 需要注意的是中间需要读 FAT 的扇区到 (es-100):bx , 所以函数一开始保存了 es bx

;----------------------------------------------------------------------------

GetFATEntry:

        push    es

        push    bx

        ;-------------------------------

        push    ax

        mov     ax, BaseOfLoader       ; BaseOfLoader为一个常量,=9000H,作为加载配置程序的内存目标段地址。

        sub     ax, 0100h              ; BaseOfLoader 前面留出 4K 空间用于存放 FAT(这里是准备段数据,实际的偏移为减少1000h,为4K

        mov     es, ax                 ; loader.com 加载位置前4K的空间地址段保存到 ES 中,此空间用于存放读取的FAT表数据。

        pop     ax

        ;-------------------------------

 

        mov     byte [bOdd], 0         ; 清奇偶校检标志位,bOdd 标号定义了一个全局变量,也可以用上篇学习的局部变量代替。

        mov     bx, 3                  ; 此时AX为需要查找与读取的簇号

        mul     bx                     ; ax * 3 ==> dx:ax

        mov     bx, 2

        div     bx                     ; dx:ax / 2  ==>  ax <- , dx <- 余数

                                       ; 此时AX为需要查找的簇号在FAT表的总偏移量(以字节为单位)。

 

        cmp     dx, 0                  ; 余数是否为0,

        jz      .LABEL_EVEN            ; 等于0为偶数,否则为奇数.

        mov     byte [bOdd], 1         ; 奇数,保存标志.

 

.LABEL_EVEN:                           ;

        xor     dx, dx                 ; 现在ax中是 簇号 在 FAT 中的总偏移量. 下面来计算 簇号 在哪个扇区中(FAT区占用不止一个扇区)

        mov     bx, [BPB_BytsPerSec]   ; 取扇区的字节数,一般情况下=512

        div     bx                     ; dx:ax / 512  ==>   ax <-    (所查的簇号位于FAT区的第几个扇区)

                                       ; dx <- 余数 (簇号在该扇区内的偏移量),得到需要查找的簇号在FAT区中的扇区号ax和偏移量dx

 

        ;-------------------------------

        push    dx                     ; 暂存偏移量

        mov     bx, 0                  ; 数据保存位置偏移量,段为 ES

                                       ; bx <- 0    于是, es:bx = (BaseOfLoader - 100):00 = (BaseOfLoader - 100) * 10h

        add     ax, SectorNoOfFAT1     ; FAT 开始扇区号一般为 1 . SectorNoOfFAT1是一个常量,=1

        ; 此句执行之后的 ax 就是 FAT项 所在的实际总扇区号

 

        mov     cl, 2                  ; 读取 2 个扇区

        call    ReadSector             ; 读取 FAT项 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FAT项 可能跨越两个扇区

        pop     dx

        ;-------------------------------

 

        mov     bx, dx                 ; dx FAT项目所扇区起始偏移量

        mov     ax, [es:bx]            ; 从该扇区的偏移量读取一个字(16位)

        cmp     byte [bOdd], 1         ; 是否是奇数

        jnz     .LABEL_EVEN_2          ; 不等于则为偶数,跳转

        shr     ax, 4                  ; 如果是奇数,右移4.

 

.LABEL_EVEN_2:

        and     ax, 0FFFh              ; 如果是偶数,取后12.

 

        pop     bx

        pop     es

        ret

;============================================================================

 

 

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

times   510-($-$$)      db      0       ; 填充剩下的空间,使生成的二进制代码恰好为512字节,因为引导代码只能限定在512个字节即一个扇区内。

; 如果以上代码超过510个字节,编译程序会发出错误警告。

                                        ; 需要预留2个字节,以便生成引导的标志。

dw      0xaa55                          ; 结束标志,生成引导标志。

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

 

将上述代码保存到文件boot_A.asm中,并编译生成二进制代码文件,写入软盘映像0扇区。(以后所有计数将以0开始,但在编程时我们要注意,有些地方是以1开始计数的,比如软盘的扇区号,在BIOS中断服务中就是以1开始计数的。)

编译命令为:“nasm boot_A.asm –o boot_A.bin

再用FloppyWriter.exe用入软盘映像。

VDM1.exe加载软盘映像,(加载前如果你有软驱,请在硬件管理里将软驱卸载。)你就可以在WINDOWS下将所需的文件复制到软盘映像里了,就像是真的软盘一样,是不是很方便。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值