30天自制操作系统(一)

开个新坑,写一写学习阅读《30天自制操作系统》一些笔记、重点内容、平台处理和个人感想。

前言

由于原书是完全在Windows下面描述的,本人日常使用Linux作为首选系统,在阅读到实践这一过程费了不少劲,但是鉴于Linux上各种齐全的开发工具,实际体验上是比Windows容易的。

二进制编辑器我选择了Bless,写汇编的编辑器当然是强大的VSCode了。

汇编器用了nasm,运行系统的虚拟机用了qemu

从HelloWorld开始

书的配套资料中给出了系统软盘映像的二进制文件具体内容,使用二进制编辑器可以编辑,当然由于比较多,试着写了几行我还是选择复制粘贴了。

1
2
3
4
5
6
7
8
9
10
11
12
EB 4E 90 48 45 4C 4C 4F 49 50 4C 00 02 01 01 00 
02 E0 00 40 0B F0 09 00 12 00 02 00 00 00 00 00
40 0B 00 00 00 00 29 FF FF FF FF 48 45 4C 4C 4F
2D 4F 53 20 20 20 46 41 54 31 32 20 20 20 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
B8 00 00 8E D0 BC 00 7C 8E D8 8E C0 BE 74 7C 8A
04 83 C6 01 3C 00 74 09 B4 0E BB 0F 00 CD 10 EB
EE F4 EB FD 0A 0A 68 65 6C 6C 6F 2C 20 77 6F 72
6C 64 0A 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
......

把以上一堆东西用二进制编辑器写入helloos.img,打开终端执行

1
qemu-system-i386 helloos.img

如果你对文件写的没问题,那么你对系统应该是可以正常启动并输出hello, world的。

使用汇编

汇编的指令大小写不敏感,指令大小写均可。

一些指令的意义:

  • DB:“define byte”的缩写,就是定义一个字节类型变量的指令,占1字节。
  • RESB:“reserve byte”的缩写,定义一段全部为0的字节类型变量,如RESB 5相当于DB 0x00, 0x00, 0x00, 0x00, 0x00
  • DW:“define word”的缩写,定义字类型变量,占2字节。
  • DD:“define double-word”的缩写,定义双字类型变量,占4字节。

我们可以使用汇编来辅助我们生成一个和上面的helloos.img完全一样的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
DB 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
DB 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
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, 0x0a, 0x0a, 0x68, 0x65
DB 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
DB 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
......

我们用RESB指令来处理其中过多的0x00,代码可以稍微简化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DB 0xeb, 0x4e, 0x90, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x49, 0x50, 0x4c, 0x00, 0x02, 0x01, 0x01, 0x00
DB 0x02, 0xe0, 0x00, 0x40, 0x0b, 0xf0, 0x09, 0x00
DB 0x12, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
DB 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x29, 0xff
DB 0xff, 0xff, 0xff, 0x48, 0x45, 0x4c, 0x4c, 0x4f
DB 0x2d, 0x4f, 0x53, 0x20, 0x20, 0x20, 0x46, 0x41
DB 0x54, 0x31, 0x32, 0x20, 0x20, 0x20, 0x00, 0x00
RESB 16
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, 0x0a, 0x0a, 0x68, 0x65
DB 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x77, 0x6f, 0x72
DB 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 368
DB 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0xaa
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432

把汇编代码保存到helloos.asm中,然后使用nasm对代码进行编译,得到二进制文件helloos.img

1
nasm helloos.asm -o helloos.img

使用qemu运行也可以得到相同的效果。

优化写法

上面的汇编代码,仅凭人眼是很难看出是做什么的,我们对代码进行改进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
db 0xeb, 0x4e, 0x90
db "HELLOIPL" ; 启动区名称,必须是8字节
dw 512 ; 每个扇区大小,必须为512字节
db 1 ; 簇的大小,必须为1个扇区
dw 1 ; FAT的起始位置,第一个扇区开始
db 2 ; FAT个数,必须为2
dw 224 ; 根目录大小
dw 2880 ; 该磁盘大小,必须是2880
db 0xf0 ; 磁盘种类,必须是0xf0
dw 9 ; FAT长度,必须是9个扇区
dw 18 ; 1个磁道的扇区数,必须为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 ; 两个换行符
db "hello, world"
db 0x0a ; 一个换行符
db 0

resb 0x1fe-($-$$) ; 用0x00填充直到0x001fe的位置

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

这里遇到点小问题,书中第32行的写法是这样的:resb 0x1fe-$

其中给出的定义是符号$表示当前字节数量。

而根据nasm的文档,$$$分别表示“包含表达式的行的开始处的位置”和“当前字节的开始位置”,我们让二者做差,就可以得出书中想要的字节数量。

所以,32行应该写为resb 0x1fe-($-$$)

为什么要用做差的方式计算字节呢?是为了方便改变前面输出的字符串,这样我们在改变前面“helloworld”字符串长度的时候,就不用再专门计算占用字节数而改动后面的代码了。

还是,nasm编译,qemu运行,结果也是同样的,改变字符串内容重新编译运行,也有不同的效果。


以上,明天尽量继续更新!坚持不咕咕咕!