你是否也和我一样,想要知道当我们轻轻按下电源键,电脑哔的一声响,几行字闪过,然后操作系统的启动画面出现,电脑启动可以被使用,这一系列过程中,电脑到底做了什么呢?
如果你深入的思考过计算机应该如何被启动,你就会发现这其中存在着一个悖论 — 如果要启动计算机,那么就要先执行启动程序,可是如果计算机没有启动,那么就没有办法去执行启动程序西方有个谚语:
pull oneself up by one’s bootstrap 拽着自己的鞋带把自己拉起来
这就是计算机的启动过程被称为 boot 的原因,他就来源于上述谚语中的单词“bootstrap”,那么,计算机设计中是如何解决这个悖论的呢? 早期计算机通过先为内存供电,将启动所需的程序预先写入内存的临时方法来解决这个悖论,但后来,BIOS 的诞生终于圆满的解决了这个问题。
上世纪七十年代初,只读存储器 ROM 诞生了,他不再会因为掉电而造成数据的丢失,数据一旦烧录,就无法更改。 于是,只要在计算机出厂时,将固定的程序写入 ROM,并且设置电脑开机时率先读取 ROM 的固定位置并执行,就可以解决上述的悖论了。 这个在计算机只读存储区 ROM 中存储的就是 BIOS 程序(Basic Input Output System) BIOS 程序主要做了下面的两件事:
硬件自检也称为 POST(Power On Self Test) 主要功能就是检测硬件工作是否正常,是否满足最基本的启动需要,并且对这些硬件进行必要的初始化工作。 早期的计算机会在这一过程中显示下面的界面:
但随着计算机工业的发展,硬件问题发生的概率越来越低,与此同时,计算机的启动速度越来越快,整个硬件自检过程也在1秒内就可以完成,这个界面也就不再显示了,除非出现了什么问题,屏幕上才会显示出对应的错误描述信息。
对硬件进行一系列检测与必要的初始化工作后,BIOS 会去遍历用户配置的引导设备列表,也就是我们定义的设备的先后启动顺序。 BIOS 从这些设备中一个一个地去尝试启动,直到找到符合要求的设备。 那么,这里说到的“符合要求”是什么呢?怎么确定一个设备是否是启动设备呢? IBM 制定了一个规范,那就是如果一个存储设备的第 511 字节和第 512 字节分别存储的是 0xAAh 与 0x55h,那么,BIOS 就认为这个设备是启动设备,从而不再去寻找下一个设备,因为磁盘每个扇区正好是 512 字节,这第一个 512 字节也就因此被称为引导扇区。 此时,BIOS 将这第一个扇区载入到内存地址 0x7C00h 的位置,就开始执行这段引导代码了,这也就是操作系统设计时的第一段代码,通过这段代码会加载并跳转到磁盘的另一段代码中,从而开始整个操作系统的引导、初始化与启动工作。
既然我们已经知道了计算机启动的上述过程,我们能不能编写自己的启动程序呢?答案当然是肯定的。 那就让我们来编写操作系统的第一步,从我们自己的启动盘启动,并在屏幕上显示“Hello World my OS!”。
既然我们要在屏幕上显示“Hello World my OS!”,那么首先要解决的问题是怎么让 BIOS 能够将内存中的信息显示在屏幕上。 BIOS 通过硬件中断的方式内置了很多 IO 功能,具体可以参考:https://en.wikipedia.org/wiki/BIOS\_interrupt\_call,也可以参考本文的附录。 其中 10H 号中断就是显示服务,我们通过 INT 10H 指令就可以触发 10H 号中断。 在中断触发后,BIOS 会去读取寄存器 AH 中的值,并根据这个字节的内容,来进行不同的操作,例如,如果 AH 中存储的是 13H,BIOS 就会在屏幕上显示一行字符串。
上面已经提到,在 INT 10H 触发时,如果 AH 中存储的是 13H,那么 BIOS 就会在屏幕上显示一行字符串。
寄存器 AL 中的最低两位,决定了具体的显示方式。
如上所述,当 AL 的 1 位为 0 时,BL 表示显示属性:
下列寄存器中存储了显示所需的其他信息:
通过上面这么多的讲解,我们知道,只需要在第一个扇区的 511 字节和 512 字节设置结束标志:0xAA55h,我们就可以将这个磁盘设置为启动盘,而剩下的 510 字节足够保存我们要在屏幕上显示的字符串了。
所以我们需要编写一段汇编代码,主流的汇编器主要有四个:微软家的 MASM、Borland 公司的 TASM、开源的 NASM 以及 GNU 汇编器。 MASM 与 TASM 的语法是最为接近的,NASM 语法与他们有一些差别,但只要熟悉三者中一个的语法,通过查阅手册就可以清楚另外两者的代码如何编写了。 推荐是在 windows 平台使用微软家的 MASM,在 linux 平台使用 NASM,网上资料非常多,选择跨平台的 TASM 也可以,至于 GNU 汇编器,他的语法与其他三者的差距最大,除非是非常熟悉 GNU 汇编语法,否则不是太推荐使用。 本文我们选用开源的 NASM 在 linux 环境下进行编写。
; author: techlog
org 07c00h ; 将下列程序加载到内存地址 7c00h 处
; 初始化段寄存器
mov ax, cs
mov ds, ax
mov es, ax
call DisplayString
jmp $ ; 跳转到当前位置,无限循环
DisplayString:
mov ax, BootMessage
mov bp, ax ; ES:BP = 串地址
mov cx, [MessageLength] ; CX = 串长度
mov ax, 01301h ; AH = 13h 显示字符串, AL = 01h,显示属性存储在 BL 中
mov bx, 008ch ; BH = 0 从第 0 行开始显示,BL = 8Ch 黑底红字高亮闪烁
mov dl, 0 ; 从第 0 列开始显示
int 10h ; 10h 号中断
ret
BootMessage: db "Hello World my OS!"
MessageLength: db $-BootMessage
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ; 引导扇区标志
上面代码中的注释已经非常清晰,这里做一些额外的说明。
在 DisplayString 函数中,我们看到一个赋值语句:
mov ax, BootMessage
在 MASM 中,我们需要这样写:
mov ax, word ptr BootMessage
MASM 中,如果要取变量的首地址,需要使用 OFFSET 或 PTR 指令,但在 NASM 中并没有这两个指令,取而代之的是,只要是变量,默认都是返回地址,所以直接使用命令 mov ax, BootMessage 就是讲变量 BootMessage 的首地址放入 ax。 而如果你想要将 MessageLength 变量的值放入 cx 中,那么你需要执行:
mov cx, [MessageLength]
方括号表示取该变量的值。
标识符也同样代表当前代码的起始地址。除此之外,NASM 增加了
-
times 是 NASM 中十分实用的一个伪指令,他有两个操作数:
times n m
表示把 m 重复 n 次。 例如 times 3 db 0 指令相当于:
db 0
db 0
db 0
这有些类似 MASM 中的 DUP 指令(需要先添加 start label 到程序第一行):
db 510-($-start) dup(0)
无论你用哪种汇编器完成代码的编写,都要用相应的汇编器执行编译链接,例如,基于 NASM 编写的上述代码可以在 linux 下执行:
nasm boot.asm -o boot.bin
生成二进制文件 boot.bin,如果提示 nasm 命令不存在,使用对应平台下的包管理机制或到官网下载源码编译安装即可。
既然我们已经拥有了用于启动的二进制文件,只要将他写入磁盘的第一个扇区并将该磁盘设置为启动盘,开机启动就可以进入这个扇区了。 那么,第一步,我们要写入磁盘。 在 linux 下,可以通过 dd 命令写入:
dd if=boot.bin of=boot.img bs=512 count=1 conv=notrunc
参数 conv=notrunc 意味着a.img不能被截断。
如果你是在 windows 环境下,你也可以使用 rawrite 或者 UltraISO 软件。 如果你安装了 rawrite2 软件,只需要在 cmd 或者是运行窗口中执行:
rawrite2.exe -f boot.bin -d boot.img
同时你也可以安装更为强大的 UltraISO 软件,如下图点击启动菜单,加载引导文件选项:
选择刚刚编译生成的二进制文件,点击工具栏上的保存按钮,就可以生成启动盘 ISO 文件了。
你可以将刚刚生成 ISO 或者 IMG 文件刻录到 U 盘、光盘或是软盘上,然后放入计算机,重启,在 BIOS 中设置从该设备启动,就可以看到屏幕上显示出了闪闪的“Hello World my OS!”。 当然,这显得有些繁琐,我们完全可以换一种方式 — 使用虚拟机。 如果你在 linux 环境下,可以使用轻量化又功能强大的 bochs 虚拟机,或者如果你有图形界面或在 windows、mac 环境下,也可以使用 virtualbox、VMware 等等。 这里就不赘述上述某个虚拟机的使用方式了,总之创建一个虚拟环境,然后将我们生成的 img 或是 ISO 文件载入虚拟机的虚拟软驱或光驱,然后点击启动按钮,你就会看到:
上图就是博主在自己的 windows 环境下使用 virtualbox 虚拟机启动的界面。 是不是非常激动人心?是不是从未想过制作一个自己的操作系统是如此简单?
很遗憾,这还完全不能称得上是一个操作系统,但我们已经顺利让 BIOS 从我们的初始扇区启动了,并且显示出了激动人心的 Hello World,接下来的事情还有什么难的呢? 真正的操作系统被引导后,究竟又做了哪些事情呢?敬请期待,博主的下一篇文章。
https://en.wikipedia.org/wiki/Booting。 https://en.wikipedia.org/wiki/BIOS\_interrupt\_call。 http://www.360doc.com/content/18/0824/12/44974165\_780828242.shtml。 https://www.cnblogs.com/jiu0821/p/4422464.html。 https://techlog.cn/article/list/10183457。 《Orange’s 一个操作系统的实现》。