<自己动手写操作系统>2011032101

阅读<使用开源软件-自己动手写操作系统>一书中关于“让启动扇区加载引导文件”,从理论上实践了如何做,由于此书中的代码是:AT&T编写的,一时转不过语法;需要手工转换成INTER下的nasm。

自己转换一小部分代码,感觉有些晦涩,看来自己还在汇编上功夫不够深,需继续努力!

好了,在网络上找到一份nasm代码,和书中代码功能一致的,地址:http://blog.csdn.net/DL88250/archive/2007/03/22/1537377.aspx

在此转来这份代码:

; loader.asm

org    0100h

mov    ax, 0B800h;0B800h是PC机显存的开始地址,http://hi.baidu.com/numax/blog/item/9c51e464f8d736f8f6365439.html

mov    gs, ax

mov    ah, 0Fh                ; 0000: 黑底    1111: 白字

mov    al, 'L'

mov    [gs:((80 * 0 + 39) * 2)], ax    ; 屏幕第 0 行, 第 39 列。

jmp    $        ; Start

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

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

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

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

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

RootDirSectors        equ    14    ; 根目录占用空间

SectorNoOfRootDirectory    equ    19    ; Root Directory 的第一个扇区号

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        ; Start to boot.

    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 'Tinix0.01  '; 卷标, 必须 11 个字节

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



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



    mov    dh, 0                ; "Booting  "

    call    DispStr            ; 显示字符串

   

    xor    ah, ah    ; ┓

    xor    dl, dl    ; ┣ 软驱复位

    int    13h        ; ┛

   

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

    mov    word [wSectorNo], SectorNoOfRootDirectory

LABEL_SEARCH_IN_ROOT_DIR_BEGIN:

    cmp    word [wRootDirSizeForLoop], 0    ; ┓

    jz    LABEL_NO_LOADERBIN                    ; ┣ 判断根目录区是不是已经读完

    dec    word [wRootDirSizeForLoop]        ; ┛ 如果读完表示没有找到 LOADER.BIN

    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

    call    ReadSector



    mov    si, LoaderFileName    ; ds:si -> "LOADER  BIN"

    mov    di, OffsetOfLoader    ; es:di -> BaseOfLoader:0100 = BaseOfLoader*10h+100

    cld

    mov    dx, 10h

LABEL_SEARCH_FOR_LOADERBIN:

    cmp    dx, 0                                ; ┓循环次数控制,

    jz    LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR    ; ┣如果已经读完了一个 Sector,

    dec    dx                                            ; ┛就跳到下一个 Sector

    mov    cx, 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 不是

; 我们要找的 LOADER.BIN

LABEL_GO_ON:

    inc    di

    jmp    LABEL_CMP_FILENAME    ;    继续循环



LABEL_DIFFERENT:

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

    add    di, 20h                            ;     ┃

    mov    si, LoaderFileName                    ;     ┣ di += 20h  下一个目录条目

    jmp    LABEL_SEARCH_FOR_LOADERBIN;    ┛



LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR:

    add    word [wSectorNo], 1

    jmp    LABEL_SEARCH_IN_ROOT_DIR_BEGIN



LABEL_NO_LOADERBIN:

    mov    dh, 2                ; "No LOADER."

    call    DispStr            ; 显示字符串
    jmp    $                    ; 没有找到 LOADER.BIN, 死循环在这里



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

    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            ; ┃ 每读一个扇区就在 "Booting  " 后面打一个点, 形成这样的效果:

    mov    al, '.'            ; ┃

    mov    bl, 0Fh            ; ┃ Booting ......

    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:



    mov    dh, 1                ; "Ready."

    call    DispStr            ; 显示字符串



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

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

                        ; 开始执行 LOADER.BIN 的代码

                        ; Boot Sector 的使命到此结束

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


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

;变量

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

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

wSectorNo        dw    0        ; 要读取的扇区号

bOdd            db    0        ; 奇数还是偶数


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

;字符串

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

LoaderFileName        db    "LOADER  BIN", 0    ; LOADER.BIN 之文件名

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

MessageLength        equ    9

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

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

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

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





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

; 函数名: DispStr

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

; 作用:

;    显示一个字符串, 函数开始时 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

    ret





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

; 函数名: ReadSector

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

; 作用:

;    从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中

ReadSector:

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

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

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

    ; 设扇区号为 x

    ;                           ┌ 柱面号 = y >> 1

    ;       x           ┌ 商 y ┤

    ; -------------- => ┤      └ 磁头号 = y & 1

    ;  每磁道扇区数     │

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

    push    bp

    mov    bp, sp

    sub    esp, 2            ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2]



    mov    byte [bp-2], cl

    push    bx            ; 保存 bx

    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



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

; 函数名: GetFATEntry

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

; 作用:

;    找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中

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

GetFATEntry:

    push    es

    push    bx

    push    ax

    mov    ax, BaseOfLoader    ; ┓

    sub    ax, 0100h            ; ┣ 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT

    mov    es, ax                ; ┛

    pop    ax

    mov    byte [bOdd], 0

    mov    bx, 3

    mul    bx            ; dx:ax = ax * 3

    mov    bx, 2

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

    cmp    dx, 0

    jz    LABEL_EVEN

    mov    byte [bOdd], 1

LABEL_EVEN:;偶数

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

    mov    bx, [BPB_BytsPerSec]

    div    bx            ; dx:ax / BPB_BytsPerSec  ==>    ax <- 商   (FATEntry 所在的扇区相对于 FAT 来说的扇区号)

                    ;                dx <- 余数 (FATEntry 在扇区内的偏移)。

    push    dx

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

    add    ax, SectorNoOfFAT1    ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号

    mov    cl, 2

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

    pop    dx

    add    bx, dx

    mov    ax, [es:bx]

    cmp    byte [bOdd], 1

    jnz    LABEL_EVEN_2

    shr    ax, 4

LABEL_EVEN_2:

    and    ax, 0FFFh


LABEL_GET_FAT_ENRY_OK:

    pop    bx

    pop    es

    ret

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

times     510-($-$$)    db    0    ; 填充剩下的空间,使生成的二进制代码恰好为512字节

dw     0xaa55                ; 结束标志

/*************************************************************/

编译后,挂接到xp虚拟机上,把loader.bin拷贝到软驱里面。另外启动空系统,挂接boot,这时候会出现“Ready...”之类的字符,说明加载loader.bin成功

在此,仅仅是以试验方式观察了loader.bin,还需要从代码上继续学习个部分,后续blog学习代码的应用,至少要达到正确且犹如“盲打”一般了解各语句的实际意义(Why,What,How)~
posted @ 2011-03-21 14:51  西就东城  阅读(593)  评论(2编辑  收藏  举报