欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-24 11:55:10
...

 

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        ; 显示字符串

; *****************************************************************************************************
    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(扇区号)
;----------------------------------------------------------------------------
; 作用:
;    从第 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:;偶数
    ; 现在 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