📄 loader.asm
字号:
;=============================================================================
; Kitnix ver 0.01
; Write by Kit
; 2008-10-31
;
; Loader.asm
; Show the menu
; Control the computer
; Open the user file
; Restart or Shutdown
;=============================================================================
;==============================================================================
;%define _BOOT_DEBUG_
org 0100h
BaseOfStack equ 0100h
BaseOfUserFile equ 08000h
OffsetOfUserFile equ 0100h
;==============================================================================
jmp LABEL_START
%include "fat12hdr.inc"
;==============================================================================
LABEL_START:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, BaseOfStack
again:
mov ax, RootDirSectors ; 还原 wRootDirSizeForLoop
mov [wRootDirSizeForLoop], ax ; Root Directory 占用的扇区数, 在循环中会递减至零.
mov ax, BootMessage
mov bp, ax
mov ax, ds
mov es, ax
mov cx, 086H
mov ax, 01301h
mov bx, 000Bh
mov dx, 0300H
int 10H
mov ah, 00H ; ┓
int 16H ; ┣ 键盘读入 + 回显
mov ah, 0EH ; ┃
int 10H ; ┛
n1:
cmp al, '1'
jnz n2
mov bx, FirstFileName
mov [choice], bx
jmp Find
n2:
cmp al, '2'
jnz n3
mov bx, SecondFileName
mov [choice], bx
jmp Find
n3:
cmp al, '3'
jnz n4
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
jmp again
n4:
cmp al, '4'
jnz n5
jmp 0ffffh:0000h ; 重启
n5:
cmp al, '5'
jnz again
%ifdef _BOOT_DEBUG_
mov ax, 4c00h
int 21h
%else
; 关机实现
mov ax,5301h ;Function 5301h: APM Connect real-mode interface
xor bx,bx ;Device ID: 0000h (=system BIOS)
int 15h ;Call interrupt: 15h
mov ax,530eh ;Function 530Eh: APM Driver version
mov cx,0102h ;Driver version: APM v1.2
int 15h ;Call interrupt: 15h
mov ax,5307h ;Function 5307h: APM Set system power state
mov bl,01h ;Device ID: 0001h (=All devices)
mov cx,0003h ;Power State: 0003h (=Off)
int 15h ;Call interrupt: 15h
%endif
;----------------------------------------------------------------------------
; 函数名: Find
;----------------------------------------------------------------------------
; 作用:
; 在软盘中寻找 用户文件
Find:
xor ah, ah ; ┓
xor dl, dl ; ┣ 软驱复位
int 13h ; ┛
mov dh, 0
call DispStr
; 下面在 A 盘的根目录寻找 用户文件
mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
cmp word [wRootDirSizeForLoop], 0 ; ┓
jz LABEL_NO_LOADERBIN ; ┣ 判断根目录区是不是已经读完
dec word [wRootDirSizeForLoop] ; ┛ 如果读完表示没有找到 用户文件
mov ax, BaseOfUserFile
mov es, ax ; es <- BaseOfUserFile
mov bx, OffsetOfUserFile ; bx <- OffsetOfUserFile 于是, es:bx = BaseOfUserFile:OffsetOfUserFile
mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号
mov cl, 1
call ReadSector
mov si, [choice] ; ds:si -> 用户文件名
mov di, OffsetOfUserFile ; es:di -> BaseOfUserFile:0100 = BaseOfUserFile*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 不是我们要找的 用户文件
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME ; 继续循环
LABEL_DIFFERENT:
and di, 0FFE0h ; else ┓ di &= E0 为了让它指向本条目开头
add di, 20h ; ┃
mov si, [choice] ; ┣ 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 USER FILE."
call DispStr ; 显示字符串
jmp again
LABEL_FILENAME_FOUND: ; 找到 用户文件 后便来到这里继续
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 里面变成 用户文件 的起始扇区号 (从 0 开始数的序号)
mov ax, BaseOfUserFile
mov es, ax ; es <- BaseOfUserFile
mov bx, OffsetOfUserFile ; bx <- OffsetOfUserFile 于是, es:bx = BaseOfUserFile:OffsetOfUserFile = BaseOfUserFile * 10h + OffsetOfUserFile
mov ax, cx ; ax <- Sector 号
LABEL_GOON_LOADING_FILE:
push ax ; ┓
push bx ; ┃
mov ah, 0Eh ; ┃ 每读一个扇区就在 "Loading " 后面打一个点, 形成这样的效果:
mov al, '.' ; ┃
mov bl, 0Fh ; ┃ Loading ......
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 BaseOfUserFile:OffsetOfUserFile ; 这一句正式跳转到已加载到内存中的 用户文件的开始处
; 开始执行 用户文件 的代码
; Find 的使命到此结束
; *****************************************************************************************************
ret
;----------------------------------------------------------------------------
; 函数名: DispStr
;----------------------------------------------------------------------------
; 作用:
; 显示一个字符串
DispStr:
mov ax, MessageLength
mul dh
add ax, LoadMessage
mov bp, ax
mov ax, ds
mov es, ax
mov cx, MessageLength
mov ax, 01301h
mov bx, 000Bh
or dh, dh
jz prn
mov dh, 1
prn:
add dh, 10
xor dl, dl
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, BaseOfUserFile ; ┓
sub ax, 0100h ; ┣ 在 BaseOfUserFile 后面留出 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 = (BaseOfUserFile - 100):00 = (BaseOfUserFile - 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
;============================================================================
;字符串
;----------------------------------------------------------------------------
BootMessage: db "Hello, this is Kit's World!", 13, 10
db "1. First Program", 13, 10
db "2. Second Program", 13, 10
db "3. Clean Screen", 13, 10
db "4. Restart", 13, 10
db "5. Shutdown", 13, 10
db "Please input your choose: "
FirstFileName db "FIRST BIN", 0 ; 用户文件名1
SecondFileName db "SECOND BIN", 0 ; 用户文件名2
LoadMessage: db "Loading "
SuccessMessage db "File Ready "
FailMessage db "NO USER FILE"
;============================================================================
;变量
;----------------------------------------------------------------------------
wRootDirSizeForLoop dw 0
wSectorNo dw 0 ; 要读取的扇区号
bOdd db 0 ; 奇数还是偶数
choice dw 0
MessageLength equ 12
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -