1. 引言
上一篇文章中,我们详细介绍了 FAT12 文件系统的构成,并且在 linux 环境下构建了我们自己的软盘,虽然这在此前我们已经实现过了很多次。 实战 FAT12 文件系统
本文,我们就来通过上述原理,实现软盘读取,并且加载并让我们的启动盘跳转进入到软盘上文件所写的程序。 这样,我们就终于可以和长期以来伴随我们的 freedos 系统说再见了,终于可以使用原生的 bochs 调试功能了,再也不用使用蹩脚的 magic break 了。
2. 通过 BIOS 中断读取软盘
BIOS 中断对我们来说已经不陌生了,我们曾经使用过 10H 号中断来实现一个字符串的打印。 BIOS 的 13H 号中断就是用来操作软盘的,他通过触发时寄存器的值,实现了下面两个功能。
2.1. 复位软盘
触发时,如果 ah = 0,dl 为 驱动器号,则中断将造成软驱复位。
2.2. 读取软盘
触发时,如果寄存器值如下,则读取相应数据到 es:bx 缓冲区中:
- ah = 02h
- al = 读取扇区数
- ch = 柱面/磁道号
- cl = 起始扇区号
- dh = 磁头号
- dl = 驱动器号
- es:bx = 数据缓冲区
2.3. 通过扇区号计算柱面号与磁头号
根据上一篇文章中介绍的原理,我们可以知道,对于一个 1.44M 的软盘来说,总共有两个盘面,每面 80 个磁道,每个磁道 18 个扇区,因此 2 18 80 * 512 = 1.44M。 那么,如何获取柱面号、磁头号、盘片起始扇区号呢,根据上面的原理,也很简单:
柱面号 = 扇区号 / 每磁道扇区数 / 2 磁头号 = 扇区号 / 每磁道扇区数 & 1 起始扇区号 = 扇区号 % 每磁道扇区数 1
2.4. 通过汇编程序读取软盘扇区
下面的函数实现了一个软盘指定数量扇区的读取:
代码语言:javascript复制; ----- 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 -----
ReadSector:
push bp
mov bp, sp
sub esp, 2 ; 开辟两个字节的堆栈区域保存要读的扇区数: byte [bp-2]
mov byte [bp-2], cl
push bx
mov bl, [BPB_SecPerTrk] ; bl: 每磁道扇区数
div bl ; 商保存在 al 中,余数保存在 ah 中
inc ah ; 获取其实扇区号
mov cl, ah ; cl <- 起始扇区号
mov dh, al
shr al, 1 ; 获取柱面号
mov ch, al ; ch <- 柱面号
and dh, 1 ; 获取磁头号
pop 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
3. 软盘数据的读取
通过上面的 ReadSector 函数,我们已经可以实现软盘上一个指定扇区的读取了,但是,一个文件只要大于 512 字节,就会被存储在多个扇区上,此时我们就需要去解析 FAT 扇区中存储的 FAT 项,从而循环跳转到下一个存储文件内容的扇区中,最终实现整个文件的读取。
如上图所示,FAT 项存在一个问题,那就是他是 12 字节的,我们每次读取一个字节的话,要读取三次才能取到两个 FAT 项,因此解析起来存在一定的复杂性。 下面的函数实现了指定 FAT 项的读取,结果存放在 ax 中:
代码语言:javascript复制GetFATEntry:
push es
push bx
push ax
; 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
mov ax, BaseOfLoader
sub ax, 0100h
mov es, ax
; 判断 ax 奇偶性,赋值 bOdd 变量
pop ax
mov byte [bOdd], 0 ; bOdd 变量用于存放当前是奇数次读取还是偶数次读取
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:
; 计算 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 = FAT1 起始扇区号 指定读取扇区号 = FATEntry 所在的扇区号
mov cl, 2
call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个
; 赋值结果给 ax 并矫正结果
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
4. 创建 loader demo
我们在系列之初,就完成了一个启动盘的创建,输出了 hello world 字符串: 实现一个操作系统
那么,接下来我们要做的就是创建一个 loader.bin 程序,让我们的启动程序找到并加载 loader.bin,由 loader.bin 负责拉起我们后面要写的操作系统。
4.1. loader demo
由于启动程序一旦将控制权交给 loader.bin,loader 就不再受任何限制,我们就可以自由的编写我们所需要的 loader 程序了,因此,本文我们重点在启动程序将控制权交给 loader 的过程,因此 loader 只要能显示一行字符串就可以了。
代码语言:javascript复制org 0100h
mov ax, cs
mov ds, ax
mov di, (80*3 2) * 2 ; 屏幕第 3 行, 第 2 列
mov ax, 0B800h
mov gs, ax
mov ah, 0Fh ; 0000: 黑底 1111: 白字
mov si, BootMessage
xor cx, cx
mov cl, byte [MessageLength]
print_loop:
lodsb
mov [gs:di], ax
add bl, 1
add di, 2
loop print_loop
jmp $
BootMessage: db "techlog loader"
MessageLength: db $-BootMessage
4.2. 放入软盘
通过上一篇文章中所介绍的,我们直接通过命令将编译后的 loader.bin 放入软盘即可:
nasm -o loader.bin loader.asm mount -o loop boot.img /mnt/floppy cp loader.bin /mnt/floppy umount /mnt/floppy
5. 让启动程序找到 loader demo
5.1. 复位软驱
首先,我们要执行复位中断,复位软驱:
代码语言:javascript复制xor ah, ah
xor dl, dl
int 13h
5.2. 循环读取根目录区找到元信息
接下来,我们要循环读取根目录区,找到 loader.bin 在根目录区中的元数据信息:
代码语言:javascript复制; 在根目录区寻找 LOADER.BIN
; wSectorNo 为根目录区扇区号,初始为 19
mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
; 根目录区已读完,则说明未找到
cmp word [wRootDirSizeForLoop], 0
jz LABEL_NO_LOADERBIN
dec word [wRootDirSizeForLoop]
; 读取扇区
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, [wSectorNo]
mov cl, 1
call ReadSector
mov si, LoaderFileName ; ds:si = "LOADER BIN"
mov di, OffsetOfLoader ; es:di = BaseOfLoader:0100
cld ; df = 0
; 循环读取目录条目
mov dx, 10h ; 当前扇区所有目录条目循环次数
LABEL_SEARCH_FOR_LOADERBIN:
; 循环结束,已完成当前扇区目录条目读取
cmp dx, 0
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec dx
; 比较目录条目中 DIR_Name 是否与 LOADER.BIN 相同
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 ; 字符不同,说明当前非目录条目
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME
; 非当前条目,跳至下一条目
LABEL_DIFFERENT:
and di, 0FFE0h ; 让 es:di 指向当前条目起始位置
add di, 20h ; 跳至下一条目
mov si, LoaderFileName
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:
5.3. 获取文件在数据区中起始扇区号
代码语言:javascript复制; 获取 loader.bin 对应的数据区簇号,保存在栈中
and di, 0FFE0h ; di = 当前条目起始位置
add di, 01Ah ; es:di 指向 DIR_FstClus,对应数据区簇号
mov cx, word [es:di]
push cx
; 获取文件所在扇区号,保存在 cx 中
mov ax, RootDirSectors ; 根目录扇区数
add cx, ax ; 因为 BPB_SecPerClus 为 1,每簇 1 扇区
add cx, DeltaSectorNo ; 所以,文件所在扇区号 = 根目录起始扇区号 根目录扇区数 文件数据区簇号 - 2
5.4. 读取文件并载入内存
现在,我们已经有了文件在数据区中的起始扇区号,通过 FAT 区中的 FAT 项,我们就可以递归获取整个文件了。
代码语言:javascript复制 ; es:bx = loader.bin 将要被加载到的内存物理地址
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
; 循环读取 loader.bin
mov ax, cx ; ax <- Sector 号
LABEL_GOON_LOADING_FILE:
; 打点,表示准备读取一个扇区,展示 Booting....
push ax
push bx
mov ah, 0Eh
mov al, '.'
mov bl, 0Fh
int 10h
pop bx
pop ax
mov cl, 1
call ReadSector
pop ax ; 取出此 Sector 在 FAT 中的序号
call GetFATEntry ; 读取 FAT 项值
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 ; 显示字符串
6. 将控制权交给 loader
既然整个 loader.bin 已经被加载到了内存中,那么,我们只需要通过一个跳转指令,跳转到被加载到内存的起始地址,就可以完成控制权的转移,也就是开始 loader 的执行了:
代码语言:javascript复制jmp BaseOfLoader:OffsetOfLoader
7. 程序运行结果
运行我们的镜像,可以看到:
8. 加载其他 loader
本文重点就在于我们对启动程序的修改,loader 其实并不重要,也就是说,我们可以把此前我们写过的任何程序作为 loader.bin 来启动。 下面是之前中断相关文章的 demo 演示:
10. 附录 — 完整代码
10.1. Makefile
代码语言:javascript复制BOOT:=boot.asm
LDR:=loader.asm
IDTLDR:=protect.asm
BOOT_BIN:=$(subst .asm,.bin,$(BOOT))
LDR_BIN:=$(subst .asm,.bin,$(LDR))
IMG:=boot.img
FLOPPY:=/mnt/floppy/
.PHONY : everything
everything : $(BOOT_BIN) $(LDR_BIN)
# bximg
dd if=$(BOOT_BIN) of=$(IMG) bs=512 count=1 conv=notrunc
sudo mount -o loop $(IMG) $(FLOPPY)
sudo cp $(LDR_BIN) $(FLOPPY) -v
sudo umount $(FLOPPY)
clean :
rm -f $(BOOT_BIN) $(LDR_BIN)
$(BOOT_BIN) : $(BOOT)
nasm $< -o $@
$(LDR_BIN) : $(LDR)
nasm $< -o $@
idt:
nasm boot.asm -o boot.bin
nasm protect.asm -o loader.bin
dd if=$(BOOT_BIN) of=$(IMG) bs=512 count=1 conv=notrunc
sudo mount -o loop $(IMG) $(FLOPPY)
sudo cp $(LDR_BIN) $(FLOPPY) -v
sudo umount $(FLOPPY)
10.2. loader.asm
代码语言:javascript复制org 0100h
mov ax, cs
mov ds, ax
mov di, (80*3 2) * 2 ; 屏幕第 3 行, 第 39 列
mov ax, 0B800h
mov gs, ax
mov ah, 0Fh ; 0000: 黑底 1111: 白字
mov si, BootMessage
xor cx, cx
mov cl, byte [MessageLength]
print_loop:
lodsb
mov [gs:di], ax
add bl, 1
add di, 2
loop print_loop
jmp $
BootMessage: db "techlog loader"
MessageLength: db $-BootMessage
10.3. boot.asm
代码语言:javascript复制org 07c00h
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 ; 用于计算文件的开始扇区号 BPB_RsvdSecCnt (BPB_NumFATs * FATSz) - 2
jmp short LABEL_START ; Start to boot.
nop ; jmp 语句 3 字节,nop 补足 4 字节
; 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 '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
mov dh, 0
call DispStr
; 复位软驱
xor ah, ah
xor dl, dl
int 13h
; 在根目录区寻找 LOADER.BIN
; wSectorNo 为根目录区扇区号,初始为 19
mov word [wSectorNo], SectorNoOfRootDirectory
LABEL_SEARCH_IN_ROOT_DIR_BEGIN:
; 根目录区已读完,则说明未找到
cmp word [wRootDirSizeForLoop], 0
jz LABEL_NO_LOADERBIN
dec word [wRootDirSizeForLoop]
; 读取扇区
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
mov ax, [wSectorNo]
mov cl, 1
call ReadSector
mov si, LoaderFileName ; ds:si = "LOADER BIN"
mov di, OffsetOfLoader ; es:di = BaseOfLoader:0100
cld ; df = 0
; 循环读取目录条目
mov dx, 10h ; 当前扇区所有目录条目循环次数
LABEL_SEARCH_FOR_LOADERBIN:
; 循环结束,已完成当前扇区目录条目读取
cmp dx, 0
jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR
dec dx
; 比较目录条目中 DIR_Name 是否与 LOADER.BIN 相同
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 ; 字符不同,说明当前非目录条目
LABEL_GO_ON:
inc di
jmp LABEL_CMP_FILENAME
; 非当前条目,跳至下一条目
LABEL_DIFFERENT:
and di, 0FFE0h ; 让 es:di 指向当前条目起始位置
add di, 20h ; 跳至下一条目
mov si, LoaderFileName
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 对应的数据区簇号,保存在栈中
and di, 0FFE0h ; di = 当前条目起始位置
add di, 01Ah ; es:di 指向 DIR_FstClus,对应数据区簇号
mov cx, word [es:di]
push cx
; 获取文件所在扇区号,保存在 cx 中
mov ax, RootDirSectors ; 根目录扇区数
add cx, ax ; 因为 BPB_SecPerClus 为 1,每簇 1 扇区
add cx, DeltaSectorNo ; 所以,文件所在扇区号 = 根目录起始扇区号 根目录扇区数 文件数据区簇号 - 2
; es:bx = loader.bin 将要被加载到的内存物理地址
mov ax, BaseOfLoader
mov es, ax
mov bx, OffsetOfLoader
; 循环读取 loader.bin
mov ax, cx ; ax <- Sector 号
LABEL_GOON_LOADING_FILE:
; 打点,表示准备读取一个扇区,展示 Booting....
push ax
push bx
mov ah, 0Eh
mov al, '.'
mov bl, 0Fh
int 10h
pop bx
pop ax
mov cl, 1
call ReadSector
pop ax ; 取出此 Sector 在 FAT 中的序号
call GetFATEntry ; 读取 FAT 项值
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 ; 跳转到已加载到内
; -------------------------- 变量 --------------------------------
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
; ---- 显示一个字符串, 函数开始时 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
; ----- 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 -----
ReadSector:
push bp
mov bp, sp
sub esp, 2 ; 开辟两个字节的堆栈区域保存要读的扇区数: byte [bp-2]
mov byte [bp-2], cl
push bx
mov bl, [BPB_SecPerTrk] ; bl: 每磁道扇区数
div bl ; 商保存在 al 中,余数保存在 ah 中
inc ah ; 获取其实扇区号
mov cl, ah ; cl <- 起始扇区号
mov dh, al
shr al, 1 ; 获取柱面号
mov ch, al ; ch <- 柱面号
and dh, 1 ; 获取磁头号
pop 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
; ---- 读取序号为 ax 的 Sector 在 FAT 中的条目, 放在 ax 中 ----
GetFATEntry:
push es
push bx
push ax
; 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT
mov ax, BaseOfLoader
sub ax, 0100h
mov es, ax
; 判断 ax 奇偶性,赋值 bOdd 变量
pop ax
mov byte [bOdd], 0 ; bOdd 变量用于存放当前是奇数次读取还是偶数次读取
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:
; 计算 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 = FAT1 起始扇区号 指定读取扇区号 = FATEntry 所在的扇区号
mov cl, 2
call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个
; 赋值结果给 ax 并矫正结果
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 ; 结束标志
10.4. bochsrc.bxrc
代码语言:javascript复制###############################################################
# Configuration file for Bochs
###############################################################
# how much memory the emulated machine will have
megs: 32
# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
vga: extension=vbe, update_freq=15
magic_break: enabled=1
# what disk images will be used
floppya: 1_44=boot.img, status=inserted
# choose the boot disk.
boot: a
# log: stdout.log
# enable key mapping, using US layout as default.
keyboard: keymap=$BXSHARE/keymaps/x11-pc-us.map
mouse: enabled=1
mouse: type=imps2, enabled=1
mouse: type=serial, enabled=1
mouse: enabled=0, toggle=ctrl f10
magic_break: enabled=1
11. 附录 — 系列目录
实现一个操作系统