自制操作系统——CherryOS (实现启动区)

Day3!
我们之前实现的操作系统全部都是一次性写好,生成img文件,最后使用qemu进行运行。后面我们的操作系统会越来越大,如果每一次我们全部用汇编去完成,用nasm去编译,显然这是不现实的。
今天我们将修改之前的代码,只做一个512字节的启动区,并且用启动区启动我们的操作系统。今天我们的操作系统的界面非常简单,仅仅是一个黑屏,只要黑屏出现,意味着我们的启动区引导了我们的操作系统。

今天我们引导区的文件名为ipl.asm,操作系统文件名为cherryos.asm

Makefile

我们之前每一次运行我们的操作系统,都需要经历这样几个步骤,nasm编译生成img文件,使用qemu加载img文件。
可以说我们每一次运行调试都要这两个步骤。那么我们是不是可以一次性运行这两个操作?
当然可以!
这里,我们使用Makefile。

什么是Makefile

在软件开发中,make通常被视为一种软件构建工具。该工具主要经由读取一种名为“makefile”或“Makefile”的文件来实现软件的自动化建构。它会通过一种被称之为“target”概念来检查相关文件之间的依赖关系,这种依赖关系的检查系统非常简单,主要通过对比文件的修改时间来实现。在大多数情况下,我们主要用它来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。

Makefile语法

1
2
target...: prerequisites ...(预备知识,先决条件)
command(指令)

注意,command前面必须是tab!

自己写一个Makefile

1
2
3
run:ipl.asm
nasm ipl.asm -o cherryOS.img
qemu-system-i386 cherryOS.img

这个Makefile文件实现了我们之前的所有操作。
现在我只需要在Terminal中输入make run,就可以实现编译与运行了。

最简单的启动区

其实最最简单的启动区就是将我们昨天的代码稍作修改,将最后生成img由1.4m改为512字节。这个代码很简单,这里就不再赘述了。

最简单的启动区在此基础上实现了自动装载下一个扇区的功能。
上代码!

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
; cherry-os ipl
ORG 0x7c00 ;指定程序装载的位置
;下面用于描述FAT12个格式的软盘
JMP entry
DB 0x90
DB "CHRRYIPL" ;启动区的名称可以是任意的字符串,但长度必须是8字节
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 18;1个磁道(track) 有几个扇区 必须是18
DW 2; 磁头个数 必须是2
DD 0; 不使用分区,必须是0
DD 2880; 重写一次磁盘大小
DB 0,0,0x29 ;扩展引导标记 固定0x29
DD 0xffffffff ;卷列序号
DB "CHERRY-OS " ;磁盘的名称(11个字节)
DB "FAT12 " ;磁盘的格式名称(8字节)
TIMES 18 DB 0; 先空出18字节 这里与原文写法不同
;程序核心
entry:
MOV AX,0 ;初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
;读盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ;柱面0
MOV DH,0 ;磁头0
MOV CL,2 ;扇区2
MOV AH,0x02; AH=0x02; 读盘
MOV AL,1 ;一个扇区
MOV BX,0
MOV DL,0x00 ;A驱动器
INT 0x13 ;BIOS INT13 AH=0x02 表示读盘
JC error
putloop:
MOV AL,[SI]
ADD SI,1
CMP AL,0
JE fin
MOV AH,0x0e ;显示一个文字
MOV BX,15 ;指定字符的颜色
INT 0x10 ;调用显卡BIOS
JMP putloop
fin:
HLT ;CPU停止,等待指令
JMP fin ;无限循环
error:
MOV SI,msg
msg:
DB 0x0a , 0x0a ;换行两次
DB "load error"
DB 0x0a
DB 0
TIMES 0x1fe-($-$$) DB 0 ;填写0x00,直到0x001fe
DB 0x55, 0xaa

重点修改的是读盘部分的代码!
在INT0x13调用下,AH=0x02表示读盘,CL表是扇区号,CH表示柱面号,DH表示磁头号,DL表示驱动器号。我们现在只有一个软盘,所以我们驱动器号为0就好。

我们运行一下。

如果出现这样的画面证明我们成功了。
(原书中显示的是booting from floppy,我不知道是我的代码错误,还是qemu的问题,如果大家知道,可以留言评论)

真正的启动区

我们真正的启动区肯定不能仅仅是读取下个扇区这么简单。
我们在之前启动区的基础上需要添加试错,读取18个扇区,读入10个柱面这样的功能。

上代码!

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
; cherry-os ipl
CYLS EQU 10;定义柱面个数为10个
ORG 0x7c00 ;指定程序装载的位置
;下面用于描述FAT12个格式的软盘
JMP entry
DB 0x90
DB "CHRRYIPL" ;启动区的名称可以是任意的字符串,但长度必须是8字节
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 18;1个磁道(track) 有几个扇区 必须是18
DW 2; 磁头个数 必须是2
DD 0; 不使用分区,必须是0
DD 2880; 重写一次磁盘大小
DB 0,0,0x29 ;扩展引导标记 固定0x29
DD 0xffffffff ;卷列序号
DB "CHERRY-OS " ;磁盘的名称(11个字节)
DB "FAT12 " ;磁盘的格式名称(8字节)
TIMES 18 DB 0; 先空出18字节 这里与原文写法不同
;程序核心
entry:
MOV AX,0 ;初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
;读盘
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ;柱面0
MOV DH,0 ;磁头0
MOV CL,2 ;扇区2
readloop:
MOV SI,0 ; 记录失败次数的寄存器
retry:
MOV AH,0x02 ;AH=0x02 表示读磁盘
MOV AL,1 ;一个扇区
MOV BX,0 ;
MOV DL,0x00 ;A驱动器
INT 0x13; 调用磁盘BIOS
JNC next ;没出错的时候跳转到next
ADD SI,1 ;SI+1
CMP SI,5 ;比较SI与5
JAE error; 当SI>=5时 跳转到error
MOV AH,0x00
MOV DL,0x00 ;A驱动器
INT 0x13 ;重置驱动器
JMP retry
next:
MOV AX,ES ;把内存地址后移0x200
ADD AX,0x0020
MOV ES,AX ;因为没有ADD ES,0x0020 所以只能这样
ADD CL,1 ;往CL加1
CMP CL,18 ; 比较CL与18
JBE readloop ; 如果CL<=18 跳转到readloop
MOV CL,1
ADD DH,1
CMP DH,2
JB readloop ;如果DH<2 则跳转到readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS
JB readloop ;如果CH<CHLS 则跳转到readloop
MOV [0x0ff0],CH
JMP 0xc200
error:
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1
CMP AL,0
JE fin
MOV AH,0x0e ;显示一个文字
MOV BX,15 ;指定字符的颜色
INT 0x10 ;调用显卡BIOS
JMP putloop
fin:
HLT ;CPU停止,等待指令
JMP fin ;无限循环
msg:
DB 0x0a , 0x0a ;换行两次
DB "load error"
DB 0x0a
DB 0
TIMES 0x1fe-($-$$) DB 0 ;填写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

CYLS表示我们要读取10个柱面

retry完成了试错,首先正常读取,如果没出错就跳转到next读取下一个扇区,如果出错,跳转到error,error可以打印错误信息,之后将AH改为0x00,在INT0x13调用下,其表示系统复位,重新读取。

next完成了18个扇区与10个柱面的读取。读取下一个扇区,只需要给CL+1,给ES+0x20,0x20刚好是512/16。读取柱面的话,给CH+1。

最后这句JMP 0xc200 以及之前的 MOV [0x0ff0],CH 我们等下再说。

我们将这个文件命名为ipl.asm

编写操作系统

知识储备:
一般向一个空的软盘保存文件时
1.文件名会写在0x002600以后的地方
2.文件的内容会写在0x004200以后的地方

所以我们要执行的程序位于磁盘的0x004200号地址。我们从启动区开始把内容装载在0x80000号地址,所以我们的要运行的程序所在内存地址就是:0x8000+0x4200=0xc200地址!

有了这个前提,我们来编写我们的操作系统,这次我们的操作系统不完成任何功能,仅仅是调用显卡,显示一个黑屏界面。

1
2
3
4
5
6
7
8
9
10
;cherry-os
ORG 0xc200
MOV AL,0x13
MOV AH,0x00
INT 0x10
fin:
HLT
JMP fin

第一句ORG 0xc200 指明了这个程序将要被装载的地方。
同样我们要在ipl.asm中加上JMP 0xc200
另外,我们可以把硬盘装载内容的结束地址告诉操作系统,所以可以加上 MOV [0x0ff0],CH

我们将这个文件命名为cherryOS.asm

Makefile让系统跑起来

启动区(ipl.asm)和操作系统(cherryOS.asm)我们现在都有了。
我们只需要让ipl正常引导操作系统即可。
这里我们用Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
default:
make img
make run
img:cherryos.bin ipl.bin
dd if=cherryos.bin of=ipl.img bs=512 seek=33 count=1 conv=notrunc
ipl.bin:ipl.asm
nasm -f bin ipl.asm -o ipl.img -l ipl.lst
cherryos.bin:cherryos.asm
nasm -f bin cherryos.asm -o cherryos.bin
run:img
qemu-system-i386 -fda ipl.img -boot a
clean:
rm ipl.lst

我们使用nasm将ipl生成为ipl.bin,将cherryOS生成为cherryOS.bin,将两个bin文件合并为ipl.img,最后我们运行ipl.img。

原书中使用了edimg这样一个软件,作者自己写的,鉴于使用的太少了,我们还是用通用的工具。

在mac上,使用dd来把cherryOS.bin写入ipl.bin中我们希望的位置。
cherryOS.bin在ipl.bin中的位置从0x4200开始,因为dd以512字节为一块,所以用seek把输出文件定位到0x4200/512=0x21=33,notrunc保证ipl.img不被截断。

那么,我们现在只需要make一下,就可以看到完整的效果。

最后的效果就是……….一个黑屏!

OK,今天先到这里。

小结

今天我们主要完成了启动区的制作,以及启动区对于操作系统的引导。
同时,Makefile这种方式应该好好掌握!

明天,我们导入C语言!!!
可以开始做一个简单的界面了。

另附我的简书:https://www.jianshu.com/u/55a1bc4688c6
欢迎关注!!!!!!!!