和学习编程语言一样,编写操作系统也从Hello World开始,不过我们的Hello World是脱离操作系统直接在硬件上面实现的,旨在了解一些概念,比较有意思。
预备知识
在编写之前要准备一些知识,我本身也不是计算机专业,只学习过408相关的内容,基础并不深厚,所以在写的过程中也要随着遇到的问题不断进行学习。
计算机启动过程
计算机启动时首先会执行ROM中的固定程序来进行自检和初始化各个寄存器的值,就是我们通常说的BIOS。然后计算机会将启动盘的第一个扇区加载到内存0x07C00H的位置开始执行,操作系统也是利用这一个扇区来完成引导操作,也就是我们所说的BootLoader。现在最新的UEFI引导方式有所不同,但我们用不到,不再去纠结了。
识别启动扇区
计算机执行完预置程序后会加载启动扇区,那么怎么识别启动扇区呢?
很简单,计算机会检查第一个扇区的最后两个字节是否为0xAA55,如果是则说明是启动扇区,如果不是则忽略。
显卡的文本模式
显卡是用来控制屏幕显示内容的,图形的显示十分复杂,我们也不做考虑。显卡一般都支持文本模式,且会在内存中划出一部分区域作为缓冲区来进行文本的显示,我们只要将文本输出到这段内存区域就行了,屏幕上就会直接显示出来。由于历史原因,缓冲区被固定为0xB8000-0xB8FFF。其中没两个字节表示一个字符,第一个字节为ASCII码,第二个字节为样式。
汇编语言
不同的指令集的CPU用到的汇编语言也不一样,我们是在X86平台编写,但为了简单和方便去,还是使用NASM汇编器,不直接使用英特尔的汇编语言。
开始编写
有了上面的预备知识后我们想要在屏幕上打出HelloWorld的思路就很清晰了,首先使用汇编语言编写程序,将字符输出到0xB8000的内存缓冲区中,然后将该程序写入到磁盘的第一个扇区,并且将该扇区的后两个字节设置为0xAA55。这样计算机启动时就会自动启动我们的程序并且输出文字。
编写汇编程序
我使用的是NASM的汇编器,编写后可以直接输出对应的二进制文件。所以接下来会涉及一些很简单的汇编代码,此时需要对常用的寄存器有所了解,推荐看一下《X86汇编语言:从实模式到保护模式》这本书。
创建文件loader.asm,文本打开,输入以下代码
org 07c00h ;程序注册到07c00h的地址
mov ax, 0xB800 ;将立即数0xB800转移到ax寄存器
mov es, ax ;将ax的值转移到es寄存器,下面是按照上面说的将字符输出到缓冲区,每两个字节对应一个字符。
mov byte [es:0x00],'H' ;地址格式为[段地址:偏移量] move byte写入字节
mov byte [es:0x01],0x07
mov byte [es:0x02],'E'
mov byte [es:0x03],0x07
mov byte [es:0x04],'L'
mov byte [es:0x05],0x07
mov byte [es:0x06],'L'
mov byte [es:0x07],0x07
mov byte [es:0x08],'O'
mov byte [es:0x09],0x07
mov byte [es:0x0A],0x20
mov byte [es:0x0B],0x07
mov byte [es:0x0C],'W'
mov byte [es:0x0D],0x07
mov byte [es:0x0E],'O'
mov byte [es:0x0F],0x07
mov byte [es:0x10],'R'
mov byte [es:0x11],0x07
mov byte [es:0x12],'L'
mov byte [es:0x13],0x07
mov byte [es:0x14],'D'
mov byte [es:0x15],0x07
times 510-($-$$) db 0 ;重复命令,$为编译后的当前行地址,$$为段起始地址,除去后两字节为55 AA,将其它的字节填充为0
dw 0xaa55 ;填充最后两个字节
我用的编译环境是WSL2,ubuntu 20.04,直接sudo apt install nasm即可,然后执行编译命令,
nasm loader.asm -o loader.img
直接输出img文件即可,大小刚好为512B。
运行系统
img文件制作好后便可以开始运行了,为了方便推荐使用虚拟机,我用的VirtualBox,创建一个虚拟机,添加一个软盘,将img文件注册进去,运行就可以看到熟悉的Hello World了:
到此就算完成了,Hello World的编写,这只是第一步,主要在了解基本概念,能做到这一步还是很有成就感的。
接下来的任务就更艰巨了:
- 再进一步的学习x86汇编
- C语言和汇编互调
- 制作BootLoader(或者使用其它已有的,看难度而定)
- 正式编写操作系统的主要模块:内存管理,进程管理,文件管理等