第一步、编写汇编程序(day-01.nas)
; hello-os ; TAB=4 ; 以下这段是标准 FAT12 格式软盘专用的代码 DB 0xeb, 0x4e, 0x90 DB "HELLOIPL" ; 启动区名称可以是任意字符串 DW 512 ; 每个扇区的大小,必须是 512 字节 DB 1 ; 簇(Cluster)的大小,必须为 1 个扇区 DW 1 ; FAT 的起始位置(一般从第一个扇区开始) DB 2 ; FAT 的个数(必须为 2) DW 224 ; 根目录的大小(一般设成 244 项) DW 2880 ; 该磁盘的大小(必须是 2880 扇区) DB 0xf0 ; 磁盘的种类(必须是 0xf0) DW 9 ; FAT的长度(必须是 9 扇区) DW 18 ; 1 个磁道(Track)有几个扇区(必须是 18) DW 2 ; 磁头数(必须是 2) DD 0 ; 不使用分区(必须是 0) DD 2880 ; 重写一次磁盘大小 DB 0,0,0x29 ; 意义不明,固定 DD 0xffffffff ; 可能是卷标号码 DB "HELLO-OS " ; 磁盘的名称(11 字节) DB "FAT12 " ; 磁盘格式名称(8 字节) RESB 18 ; 先空出 18 字节 ; 程序主体 DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09 DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb DB 0xee, 0xf4, 0xeb, 0xfd ; 信息显示部分 DB 0x0a, 0x0a ; 2 个换行 DB "hello, world" DB 0x0a ; 1 个换行 DB 0 RESB 0x1fe - ($ - $$) ; 使用 0x00 填充,直到 0x01fe 结束 DB 0x55, 0xaa ; 以下是启动区以外的部分的输出 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 4600 DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 1469432
第二步、编译并运行程序
nasm day-01.nas qemu-system-i386 day-01
一、先动手操作
第一步、选择二进制编辑器
我们使用 Emacs,但是不太行。我们最后使用 hexedit 编辑器。
第二步、编写系统镜像文件
以下是二进制文件的简写,其中 *** 代表该部分全为 00 字节):
00000000 EB 4E 90 48 45 4C 4C 4F 49 50 4C 00 02 01 01 00 .N.HELLOIPL..... 00000010 02 E0 00 40 0B F0 09 00 12 00 02 00 00 00 00 00 ...@............ 00000020 40 0B 00 00 00 00 29 FF FF FF FF 48 45 4C 4C 4F @.....)....HELLO 00000030 2D 4F 53 20 20 20 46 41 54 31 32 20 20 20 00 00 -OS FAT12 .. *** 00000050 B8 00 00 8E D0 BC 00 7C 8E D8 8E C0 BE 74 7C 8A .......|.....t|. 00000060 04 83 C6 01 3C 00 74 09 B4 0E BB 0F 00 CD 10 EB ....<.t......... 00000070 EE F4 EB FD 0A 0A 68 65 6C 6C 6F 2C 20 77 6F 72 ......hello, wor 00000080 6C 64 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 ld.............. *** 000001F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA ..............U. 00000200 F0 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *** 00001400 F0 FF FF 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *** 00168000
检查错误:(1)使用 xxd 以十六禁止打印,(2)然后使用 diff 与作者提供的文件进行对比。
第三步、镜像文件写入软盘
1)保存镜像文件:将镜像文件保存退出,上面的镜像文件是可引导的,正好是 1474560 字节。
2)写入到软盘中:软盘倒是有,怎么写入、如何运行才是问题。我们可以使用 qemu-system-i386 模拟器代替。
二、究竟做了些什么
我们写的这个二进制文件什么意思呢?在这之前需要先了解一下电脑结构:
我们编写的二进制文件,交由 CPU 执行,它将二进制文件的 1 与 0 视为电信号开与关进行处理。
三、初次体验汇编程序
编写二进制文件繁琐且进度慢,现在开始使用汇编语言。
第一步、使用汇编语言编写
(编写)这里与原文结构不同,因此 RESB 指令保留的字节数也不同:
DB 0xEB, 0x4E, 0x90, 0x48, 0x45, 0x4C, 0x4C, 0x4F, 0x49, 0x50, 0x4C, 0x00, 0x02, 0x01, 0x01, 0x00 DB 0x02, 0xE0, 0x00, 0x40, 0x0B, 0xF0, 0x09, 0x00, 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00 DB 0x40, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x29, 0xFF, 0xFF, 0xFF, 0xFF, 0x48, 0x45, 0x4C, 0x4C, 0x4F DB 0x2D, 0x4F, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41, 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00 RESB 16 DB 0xB8, 0x00, 0x00, 0x8E, 0xD0, 0xBC, 0x00, 0x7C, 0x8E, 0xD8, 0x8E, 0xC0, 0xBE, 0x74, 0x7C, 0x8A DB 0x04, 0x83, 0xC6, 0x01, 0x3C, 0x00, 0x74, 0x09, 0xB4, 0x0E, 0xBB, 0x0F, 0x00, 0xCD, 0x10, 0xEB DB 0xEE, 0xF4, 0xEB, 0xFD, 0x0A, 0x0A, 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x20, 0x77, 0x6F, 0x72 DB 0x6C, 0x64, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 352 DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xAA DB 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 4592 DB 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 1469424
第二步、汇编代码编译镜像
# nasm -o boot.img boot.asm
汇编指令含义说明
DB – DEFINE BYTE,定义字节,前缀 0x 表示十六禁止(否则被汇编器视为十进制数);
RESB – RESERVE BYTE,保留字节,将指定长度的字节使用 0x00 表示;
四、加工润色
使用汇编重新编写(更具可读性)
作者使用的汇编器 nask 是自己开发的,以下代码使用其他汇编器可能无法通过:
; hello-os ; TAB=4 ; 以下是标准 FAT12 格式软盘专用的代码 DB 0xeb, 0x4e, 0x90 DB "HELLOIPL" ; 启动区的名称可以是任意字符串,必须为八字节 DW 512 ; 每个扇区的大小,必须是 512 字节 DB 1 ; 簇的大小,必须为 1 个扇区 DW 1 ; FAT 的起始位置,一般从第一个扇区开始 DB 2 ; FAT 的个数,必须为 2 DW 224 ; 根目录的大小,一般设置成 224 项 DW 2880 ; 该磁盘的大小,必须是 2880 扇区 DB 0xf0 ; 磁盘种类,必须是 0xf0 DW 9 ; FAT 的长度,必须是 9 扇区 DW 19 ; 一个磁道有几个扇区,必须是 18 DW 2 ; 磁头数,必须是 2 DD 0 ; 不使用分区,必须是 0 DD 2880 ; 重写一次磁盘大小 DB 0, 0, 0x29 ; 意义不明,固定写法 DD 0xffffffff ; (可能是)卷标号码 DB "HELLO-OS " ; 磁盘名称,11 字节 DB "FAT12 " ; 磁盘格式名称,8字节 RESB 18 ; 先空出 18 字节 ; 程序主体 DB 0xB8, 0x00, 0x00, 0x8E, 0xD0, 0xBC, 0x00, 0x7C, 0x8E, 0xD8, 0x8E, 0xC0, 0xBE, 0x74, 0x7C, 0x8A DB 0x04, 0x83, 0xC6, 0x01, 0x3C, 0x00, 0x74, 0x09, 0xB4, 0x0E, 0xBB, 0x0F, 0x00, 0xCD, 0x10, 0xEB DB 0xEE, 0xF4, 0xEB, 0xFD ; 信息显示部分 DB 0x0a, 0x0a ; 2 个换行 DB "hello, world" DB 0x0a ; 换行 DB 0 RESB 0x1fe-$ ; 填写 0x00,直到 0x001fe DB 0x55, 0xaa ; 以下是启动区以外部分的输出 DB 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 4592 DB 0xF0, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 RESB 1469424
汇编指令含义说明
; – 注释
DB – DEFINE BYTE,定义字节,前缀 0x 表示十六禁止(否则被汇编器视为十进制数);还可以直接定义字符串,汇编器将自动转化为十六进制编码
DW – DEFINE WORD,定义字(指十六位)
DD – DEFINE DOUBLE WORD,定义双字(指三十二位)
RESB – RESERVE BYTE,保留字节,将指定长度的字节使用 0x00 表示;0x1fe-$ 表示从当前位置延续到 0x1fe 位置,用于动态定义 0x00 字节,防止由于前面动态变化而需要调节 RESB 指令参数的场景。
专用术语解释说明
TAB=4 – 指定缩进,方便源码阅读
FAT12 – 文件系统格式,参考 Wikipedia/File Allocation Table 百科。用 Windows 或 MS-DOS 格式化出来的软盘就是这种格式。我们的操作系统就使用这种格式,兼容性好,剩余磁盘空间用于保存文件。
Boot Sector – 启动区,软盘的首个扇区被称为启动区。什么是扇区?当计算机读写软盘时以 512 字节为单位进行读取,而不是单个字节读取,因此 512 字节被成为一个扇区。软盘 1474560 字节,共计 2880 扇区。什么是启动扇区?我们在磁盘中保存程序,为了执行程序,需要确定程序起始部分在磁盘中的位置。这就是启动扇区的作用,我们约定将程序的开始写入软盘的首个扇区,并以 0x55AA 结尾。软盘的首个扇区最先被读取,当该扇区以 0x55AA 结尾时,则视为该扇区启动程序的开始,并开始执行这个程序。
IPL – Initial Program Loader,启动程序加载器。由于启动区只有 512 字节,但是操作系统通常大于启动区。因此操作系统并没有保存在启动区,而是通过启动区的程序加载操作系统。在启动区中的程序被称为启动程序加载器,就是今天的 Bootloader 程序。它的名字只能是八字节。
Boot – 启动,Bootstrap,至于启动为什么使用 bootstrap 可以参考 Wikipedia/Bootstrapping/Etymology 解释。简单的说,引导程序加载器器是操作系统的组成部分,这有点像“让操作系统启动自己”的意思(我们也没有深入研究这个词源)。
参考文献
How do I compare binary files in Linux?
Any x86-64 Linux assembler?