学习操作系统:自己动手写操作系统 ->chapter4_c Boot分析

 

boot.asm


;%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

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_SecPerTrk * 80 * BPB_NumHeads
    BPB_Media         DB 0xF0          ; 媒体描述符
    BPB_FATSz16       DW 9             ; 每个FAT表所占扇区数。有两个FAT表(BPB_NumFATs),分别FAT1和FAT2
    BPB_SecPerTrk     DW 18            ; 每磁道扇区数
    BPB_NumHeads      DW 2             ; 磁头数(面数)
    BPB_HiddSec       DD 0             ; 隐藏扇区数
    BPB_TotSec32      DD 0             ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数
    BS_DrvNum         DB 0             ; 中断 13 的驱动器号,0 表示 A 盘
    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
    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    ; ┓
    xor    dl, dl    ; ┣ 软驱复位
    int    13h       ; ┛
    
; 下面在 A 盘的根目录寻找 LOADER.BIN
    mov    word [wSectorNo], SectorNoOfRootDirectory ;指针变量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
    ;ds:si和es:di
    ;    DS叫做段寄存器, 指向当前运行着的程序的数据段. 你可以把它指向任何你想要的地方, 只要那个地方有你想要的数据.
    ;    ES叫做额外的段寄存器. 它通常跟DI一起用来做指针使用. DS:SI和ES:DI配对时通常用来执行一些字符串操作.
    ;    SI和DI两个寄存器叫做索引寄存器, 这两个寄存器通常用来处理数组或字符串.
    ;    SI叫做源索引寄存器, DI叫做目的索引寄存器. 正如它们的命名, SI通常指向源数组, DI通常指向目的数组. 
    ;    他们通常被用来成块地移动数据, 比如移动数组或结构体. SI和DI通常和DS和ES一起使用.
    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的解析
; ds:si是"LOADER  BIN",也就是变量LoaderFileName
; es:di是根目录区的文件名 + 扩展名
; 以上定义是在函数LABEL_SEARCH_IN_ROOT_DIR_BEGIN中完成
; 比较LoaderFileName的每一个文字
; fileName:
;     ds:si -> al
;     si = si+1
;     cmp al, byte [es:di]
;     di = di+1
;     跳转到fileName, 继续比较下一个字母
LABEL_CMP_FILENAME:
    cmp    cx, 0
    jz    LABEL_FILENAME_FOUND  ; 如果比较了 11 个字符都相等, 表示找到,并跳到LABEL_FILENAME_FOUND
    dec    cx                   ; 比较字符不相等, 表示没找到,cx = cx - 1, 继续找
    lodsb                       ; ds:si -> al,然后esi = esi+1;
                                ; lodsb指令: 将esi指向的地址处的数据取出来赋给AL寄存器,然后esi = esi+1;
                                ; lodsw指令: 则取得是一个字。
                                ; lodsd指令: 取得是双字节,即mov eax,[esi],esi=esi+4;
                                ; stosb指令: 将AL寄存器的值取出来赋给edi所指向的地址处。mov [edi],AL; edi = edi+1;
                                ; stosw指令: 去的是一个字。
                                ; stosd指令: 取得是双字节,mov [edi],eax;edi=edi+4;
    cmp    al, byte [es:di]     ; al的值在前一个命令lodsb中赋值
    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            ; 显示字符串
%ifdef    _BOOT_DEBUG_
    mov    ax, 4c00h    ; ┓
    int    21h          ; ┛没有找到 LOADER.BIN, 回到 DOS
%else
    jmp    $            ; 没有找到 LOADER.BIN, 死循环在这里
%endif

LABEL_FILENAME_FOUND:        ; 找到 LOADER.BIN 后便来到这里继续
    mov    ax, RootDirSectors
    and    di, 0FFE0h          ; di -> 当前条目的开始
    add    di, 01Ah            ; di -> 首扇区(Sector)
    mov    cx, word [es:di]    ; 扇区(Sector)在 FAT 中的序号
    push   cx                  ; 保存此 Sector 在 FAT 中的序号
    add    cx, ax
    add    cx, DeltaSectorNo   ; cl <- LOADER.BIN的起始扇区号(0-based)
    mov    ax, BaseOfLoader
    mov    es, ax                ; es <- BaseOfLoader
    mov    bx, OffsetOfLoader    ; bx <- 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        ; 显示字符串

; *****************************************************************************************************
; *******************LOADER.BIN 的开始, Boot Sector 的使命到此结束***********************
; *****************************************************************************************************
    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            ; int 10h
    ret


;----------------------------------------------------------------------------
; 函数名: ReadSector
; 入  参: ax(扇区号)
; 处理逻辑:
;       设置13号中断的入参AX、CX和DX: cx(ch:柱面号,cl:起始扇区号), dx(dh:磁头号,dl:驱动器号)
;       并调用13号中断:int    13h
;----------------------------------------------------------------------------
; 作用:
;    从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中
; 例子:从第 1 个 扇区 开始, 将 4 个 扇区 读入 es:bx 中
ReadSector:
    ; -----------------------------------------------------------------------
    ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号)
    ; -----------------------------------------------------------------------
    ; 设扇区号为 x(ax)
    ;                          ┌ 柱面号 = y >> 1
    ;       x(ax)       ┌ 商 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                ; div指令:被除数:(默认)放在 AX或 DX和AX中。商(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:;0是偶数,1是单数
    ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来
    ; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区)
    xor    dx, dx            
    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
    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                ; 结束标志

loader.asm

; nasm loader.asm -o loader.bin 
org	0100h

	mov cx, 20
	xor ebx,ebx 
	mov	ax, 0B800h
	mov	gs, ax
	mov	ah, 0Fh				; 0000: 黑底    1111: 白字
printHello:
	mov	al, [helloMessage - 25 + ebx]
	mov	[gs:((80 * 0 + 39 + ebx) * 2)], ax	; 屏幕第 0 行, 第 39 + ebx 列。
	
	inc ebx
	loop    printHello
	jmp	$		; Start
helloMessage:   db    's0ke4or92xap3fv8giuzjcy5l1m7hd6bnqtw'; 
MessageLength       equ    $ - helloMessage

loader.asm 

; nasm loader.asm -o loader.bin 
org	0100h

	mov	ax, 0B800h
	mov	gs, ax
	mov	ah, 0Fh				; 0000: 黑底    1111: 白字
	mov	al, 'H'
	mov	[gs:((80 * 0 + 39) * 2)], ax	; 屏幕第 0 行, 第 39 列。
	mov	al, 'e'
	mov	[gs:((80 * 0 + 40) * 2)], ax	; 屏幕第 0 行, 第 40 列。
	mov	al, 'l'
	mov	[gs:((80 * 0 + 41) * 2)], ax	; 屏幕第 0 行, 第 41 列。
	mov	al, 'l'
	mov	[gs:((80 * 0 + 42) * 2)], ax	; 屏幕第 0 行, 第 42 列。
	mov	al, 'o'
	mov	[gs:((80 * 0 + 43) * 2)], ax	; 屏幕第 0 行, 第 43 列。
	
	
	mov	al, 'H'
	mov	[gs:((80 * 1 + 39) * 2)], ax	; 屏幕第 0 行, 第 39 列。
	mov	al, 'e'
	mov	[gs:((80 * 1 + 40) * 2)], ax	; 屏幕第 0 行, 第 40 列。
	mov	al, 'l'
	mov	[gs:((80 * 1 + 41) * 2)], ax	; 屏幕第 0 行, 第 41 列。
	mov	al, 'l'
	mov	[gs:((80 * 1 + 42) * 2)], ax	; 屏幕第 0 行, 第 42 列。
	mov	al, 'o'
	mov	[gs:((80 * 1 + 43) * 2)], ax	; 屏幕第 0 行, 第 43 列。

	jmp	$		; Start

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值