从零开始开发一个64位操作系统

HelloOS

虽然说我是电子信息工程专业的,平时学的基本是硬件和 MCU,但我比较偏向软件,思来想去,写一个操作系统算是比较贴近这个专业,而且应该也挺有意思的。

写这个操作系统并不是拿来用的,我想没人会使用一个本科生写出来的操作系统吧。写这个操作系统的主要目的是学习计算机底层原理,把计算机知识融会贯通,还可以拿来做毕设。

不是在 Linux 内核上删删改改,而是真正的从零开始写一个“麻雀虽小,五脏俱全”的 64 位操作系统出来。

写应用程序可以借助丰富的库函数支持,而写操作系统则一切从零开始。为此,我进一步学习了计算机组成原理、操作系统、x86 Assembly、ANSI C 和 GNU C。

因为遇到不会的地方还得现学,平时还要上课、做项目等等,所以开发速度会很慢很慢很慢……

对了,我的操作系统得有个名字吧?就叫HelloOS吧!灵感来源于 HelloWorld。

开发环境

写操作系统需要 nasm 汇编器和 bochs 虚拟机,因为微软的 masm 汇编器在 64 位 Win10 下已经不能用了,并且 bochs 能调试虚拟机内的操作系统,那就重新安装一个 CentOS 虚拟机专门拿来写操作系统吧。登陆官网一看,居然 CentOS 8 都出来了!安装!安装完之后发现屏幕分辨率总是 800x600,太小了,更改了设置之后重启还是 800x600,更改设置然后重启了几次都是这个分辨率,上网查发现是 xrandr 配置问题,毕竟这是 CentOS 8 的第一个版本,可能还有 bug,那就换成之前的 CentOS 7 吧。CentOS 7 我没安装 GUI,而 bochs 虚拟机最好需要一个 GUI,于是乎我开始安装 GUI,Gnome 感觉太臃肿了,最后决定用 Xfce,然而似乎安装不了,源出了一点问题,又换源,换成阿里的折腾半天感觉太麻烦,索性不用 CentOS 了!我还有个 Ubuntu,开启 Ubuntu 安装 nasm 和 bochs 很顺利,然而因为 bochs 的配置是基于命令行的,我又是自制操作系统,虚拟硬件环境配置问题很多,于是乎,卡在了第一步……

想了想,主要是虚拟机软件和汇编器这两个软件,虚拟机软件有现成的 VirtualBox,汇编器就比较麻烦,微软官方的 masm 已经不能运行在 64 位的 Win10 下了,emu8086 又不支持一些我需要用到的伪指令,最后抱着试一试的心态上网一查,欸嘿!nasm 居然有 64 位 Windows 版本的!太好了!终于可以开始写操作系统了!

BootLoader

写操作系统不是一件容易的事儿,因此我需要找一本适合开发操作系统的参考书。一开始找的是川合秀实写的《30 天自制操作系统》这本书,写得是很不错,最后完成了一个多任务带窗口的 32 位操作系统。但问题是这本书年代有点久远了,工具链早就更新换代了,而电脑也早已进入 64 位的时代了。我想最后能写一个 64 位的操作系统,于是又找找找,找到了田宇写的《一个 64 位操作系统的设计与实现》,感觉还不错,于是就用这本书当参考书了。

当然,还是得从 16 位的 8086 实模式到 32 位的保护模式再到 64 位的 IA-32e,不能一步登天。

同理,一开始我用虚拟软盘而不是虚拟硬盘,因为软盘的逻辑比较简单。这里选用 3.5 英寸的标准软盘格式,2 个磁头,每个面 80 个磁道,每个磁道 18 个扇区,每个扇区 512 字节,所以初始容量是2 x 80 x 18 x 512 B = 1474560 B = 1440 KB

简单说一下计算机启动过程:首先,计算机上电后主板 BIOS 进行硬件自检以及初始化,然后查找并调用显卡 BIOS 程序,显卡 BIOS 进行初始化,这时候屏幕就有画面了。接着主板 BIOS 会查找其它设备的 BIOS 程序,找到之后同样要调用这些 BIOS 程序来初始化这些设备。一系列初始化完成之后 CPU 的 CS:IP 被设置指向逻辑地址0000:7c00处的指令,开始执行0000:7c00处的程序(为什么是0000:7c00,这要问当年 Intel 的 BIOS 工程师们)。通常这里的程序被称为 BootLoader,还不是 kernel。BootLoader 和 kernel 的关系就像运载卫星的火箭与被火箭运载的卫星之间的关系一样。

第一个 BootLoader 程序如下:

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
    org 0x7c00

start:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7c00

; clear screen
mov ax, 0x0600
int 0x10

; set focus
mov ax, 0x0200
mov bx, 0x0000
mov dx, 0x0000

; display
mov ax, 0x1301
mov bx, 0x000f
mov cx, 10
mov dx, ds
mov es, dx
mov dx, 0x0000
mov bp, StartBootMessage
int 0x10

; reset floppy
xor ah, ah
xor dl, dl
int 0x13

jmp $

StartBootMessage:
db "start boot"

times 510 - ($ - $$) db 0x00
db 0x55
db 0xaa

这个程序只实现了 Boot 没实现 Loader,先看看能不能运行。编译出来只有 512B,一开始我总想着要填满 1440KB 因为软盘容量就是 1440KB,然而当我抱着试一试的心态把这个 512 字节的程序加载进虚拟机开机运行的时候,奇迹出现了!

成功运行~

从开始写这篇文章(9 月 10 日)到今天(10 月 13 日),一个多月过去了,期间上课写作业做项目然后断断续续地学习操作系统相关知识,到今天总算能在虚拟机里面看见我写的程序成功运行了。感觉万里长征终于走完了第一步……


上面那个程序是参考的书上的,但那个jmp $让我感觉很不好,因为会占用 CPU,应该换成hlt更好。实验了一下,果然如此:

这是jmp $版本的结果。可以看到,CPU 1(从 0 开始计数),就是被 VBOX 选中执行 HelloOS 的那个,跑到了 100%,电脑上的风扇不一会儿就转得更快了。

这是hlt版本的结果。可以看到,几乎不耗费任何 CPU 资源。


待续