要想写一个启动区代码,就须要了解开机的启动过程,由于开机过程当中一些硬件的规定决定了这段代码应该怎么写,不明白不要紧,且听我慢慢道来。html
具体过程在我上一篇文章 【自制操做系统01】硬核讲解计算机的启动过程 讲述得一清二楚,这里咱们简单回顾一下。了解开机过程,并非一个简单的问题,我总结成你须要有三个前置知识,并记住四次跳跃。ios
首先说下三个前置知识,这些必须假设你是知道的,不然我不可能从质子中字原子开始讲起。这三个前置知识就是:git
有这三个前置知识后,接下来就是要记住计算机开机后的四次关键跳跃,由于这些都是当时 Intel 和 BIOS 等制做厂商的大叔们定下来的,没什么道理可言,记住就好:程序员
知道了上面这些后,咱们就能够写启动区代码了。别急,咱们先想一下咱们须要作什么。先不说须要什么软件,须要什么代码,这些都不是核心问题,咱们先想一下,假如咱们有无所不能的上帝之手,咱们应该怎么作才能让开机过程顺利走下去呢?架构
首先,BIOS 里面有一段写死的代码,会帮咱们把启动区的第一扇区的 512 字节的内容,原封不动复制到内存 0x7c00 这个位置,并跳转到此处,这个是不用咱们管的。编辑器
因此咱们要作的就是,把咱们的指令,写到硬盘(固然也能够是光盘、软盘、U 盘,这里咱们就只拿硬盘举例)的第一扇区的 512 字节,并把这部分标记为“启动区”(就是把最后两个字节写死为 0x55, 0xaa)。工具
好了,那咱们能够试想下,若是咱们有上帝之手,应该是这样一个操做流程。学习
就是这么简单,这样电脑就会乖乖从开机后的某一时刻,开始执行你写的 010100111001010 的代码了。网站
其实并不须要上帝之手,有不少工具能够直接往磁盘中写数据,或者你把硬盘拿出来用电路板操做也是能够的,而后在真机上跑你的操做系统。但这样作太麻烦了,不过就是有人会特别纠结这一点,非要在真机上跑出来才肯罢休,这种精神是值得称赞的。但学习计算机,该有的抽象仍是要有,能用低成本的方式实现等价的事情,不去纠结这一点是很必要的。ui
因此咱们仍是用低成本的方式来作今天这个事,刚刚上面说的真机,咱们用虚拟机来实现。上面说的硬盘,咱们用虚拟磁盘映像文件来实现。刚刚说的写硬盘这个过程,咱们也用软件命令往这个虚拟磁盘映像里写。这其实和上面的效果彻底等价,只不过都虚拟化了而已。总结起来就是:
好了,有了上面的知识储备,终于能够实践了。但是一上来就三个工具,听起来仍是很恐怖,别怕,这部分咱们只用到上述的 QEMU 工具便可。
由于虚拟磁盘映像,其实就只是一个二进制文件而已,在 Windows 电脑上直接右键,新建一个文本文件就能够了。而写数据这个过程,咱们能够直接用二进制编辑器打开这个文件,直接往里面写就行了,暂时也不须要什么工具帮助。
所以总结起来,咱们这部分的开发环境,就是下面的两个(具体安装步骤见章节四)
此时问题已经简化到极致了,咱们只须要编辑一个虚拟磁盘映像文件,再用 QEMU 启动一下就行了。那么我再简化一下,虚拟磁盘映像有不少种格式,但其实映射到真正的硬盘中,是同样的,只不过多种多样的格式,适合咱们一些工具进行方便读取和展现。
这里咱们使用和真正硬盘中数据一一对应的无格式的格式(raw),哈哈这个对刷过 B 站的人应该很容易理解,就是生肉,不经任何加工的原汁原味的格式。这个格式是什么样的呢?该格式中记录磁盘第一扇区的 512 字节的位置,就是该文件从第一个字节开始日后的 512 字节,就是这么简单粗暴。
ok,接下来,咱们终于能够开始真正动笔啦!
--------------- 正片开始预警 ------------------
不废话,直接上三部曲。
第一步:新建文件
右键,新建,文本文档。随便取一个名字和扩展名。我这里取的是 mbr.raw。mbr 的意思是主引导记录,raw 的意思是无格式的虚拟磁盘映像格式。但你没必要和我同样。
第二步:写入数据并保存
用 Notepad++ 文本编辑工具打开它,切换到 16 进制模式,开始一个字节一个字节写以下内容,写好后记得保存。
第三步:QEMU 启动
shift + 右键,在此处打开命令行窗口,输入以下命令并按下回车
qemu-system-i386 mbr.raw
等一秒钟,QEMU 启动,并展现出以下画面。
第四步:没了
哈哈就是这么简单,若是你看《30 天自制操做系统》,也有一部分会让你编辑这个二进制文本文件,但你几乎要花上半小时时间敲出来,再花半小时时间检查下到底哪里错了,最后放弃了,再花十分钟时间找到这本书的源码直接 copy 出来,我以为彻底没有必要。
若是你还以为多,那你能够试试下面这版:
但再用 QEMU 启动时会是这个效果(注意第一个字母 h 是笔者经过指令打上去的哦):
嘿嘿,这时你是否是发现了什么,这版比上一版少了哪部分呢?
若是你还不服,那我再来个赖皮版:
别找了,除了最后两个字节以外,其他的都是 0,用 QEMU 启动后是这个样子,这些纯是 QEMU 虚拟机中 BIOS 的输出了,彻底没有咱们的一行指令,但它却正确地开机了。
哈哈,这回我能够自豪地说,我这个是世界上最简单的启动区了么?没有骗你吧。相信你已经有不少疑问了,那咱们接下来就是揭秘这个二进制文件的时刻。
不废话,先直接上开发环境。
文本编辑器:Notepad++
不要下载过高版本,彷佛运行插件会有问题,个人是 Notepad++ 7.5.4 release,能够参考。
虚拟机:QEMU
找到 Windows 版的下载按钮,一样也是无脑下载,下载好后记得把目录加入到环境变量里。
汇编工具:NASM
官网下载地址:https://www.nasm.us/
一样也是找到对应的 Windows 系统,选择一个版本下载,这里我推荐:
直接点这个链接选择 nasm-2.14.02-installer-x64.exe 下载便可。
下载好后一样也记得把 bin 目录加入到环境变量。
虚拟磁盘映像工具:dd
下载完以后其实就是一个 dd.exe 文件,把这个文件所在的目录,也加到环境变量里。
OK,整个环境搭建的工做,就结束了,直接所有官方网站一键下载,配置下环境变量,就大功告成了,是否是很简单。这里又想吐槽下《30 天自制操做系统》,做者用了本身写的汇编工具,本身写的磁盘拷贝工具,本身写的虚拟映像生成工具,还有本身为 QEMU 写的启动脚本。虽然直接下载其代码就能直接运行启动,但我就是感受很不友好。
好了,如今咱们开始真正实现这个启动区代码了。咦,没错,刚刚其实已经实现过了,但没有人会用这种方式写操做系统。不过固然也能够,你能够只用一个文本编辑器,从第一个字节开始敲,敲到最后一个字节,完成一个操做系统的制做。话说在没有汇编语言的时候,可不就是这样作的么,那时候虚拟机更是没有,须要用纸带在真机上一遍一遍试。这样想一想看如今幸福多了。
经过上面的环境介绍不难猜出,咱们将使用 NASM 汇编语言来实现这个启动区,最后编译出来的二进制文件,其实和咱们上面手写的二进制文件是同样的,反过来讲就是,上面给你们展现的二进制文件,可不是我一个个手打的哦,是我实现写好了汇编代码,而后编译出来的(偷笑)。
咱们就拿刚刚的初版二进制文件来看,咱们能够把这里面的字节一一取出来,去查 Intel x86 指令集架构下,这些机器指令对应的 NASM 汇编代码是什么?若是你足够耐心,是能够一个个查出来的,但咱们 NASM 这个软件自己就有现成的工具来实现这个“反编译”的功能。
ndisasm -o0x7c00 mbr.raw
咱们看到:
也就是说,咱们按照第三列的汇编指令把代码写出来,再编译,就能够了,对吧?没错,不过还须要进行一些小调整,咱们直接上代码。
;----BIOS把启动区加载到内存的该位置,因此需设置地址偏移量 section mbr vstart=0x7c00 ;----卷屏中断,目的是清屏 mov ax,0x0600 mov bx,0x0700 mov cx,0 mov dx,0x184f int 0x10 ;----直接往显存中写数据 mov ax,0xb800 mov gs,ax mov byte [gs:0x00],'h' mov byte [gs:0x02],'e' mov byte [gs:0x04],'l' mov byte [gs:0x06],'l' mov byte [gs:0x08],'o' ;----512字节的最后两字节是启动区标识 times 510-($-$$) db 0 db 0x55,0xaa
咱们看到,和上面反编译出来的汇编语句,几乎是同样的,但仍是有几处不一样,你找找看。下面我来一一解释。
首先第一行
section mbr vstart=0x7c00
其实也能够写成:
org 0x7c00
这个的意思用通俗的话讲就是把如下代码加载到内存 0x7c00 这个位置,但这个说法不正确,其本质是 指定一个地址,后面的程序或数据从这个地址值开始分配。好比有个跳转指令,跳转到某处的代码,编译器能够经过该处代码偏移了第一行代码多少来计算出一个相对的偏移地址,但却没法知道其绝对地址是多少,因此有必要让程序员来告诉编译器这个信息。
第二部分
;----卷屏中断,目的是清屏 mov ax,0x0600 mov bx,0x0700 mov cx,0 mov dx,0x184f int 0x10
前面几行是设置一些寄存器的值,有点像高级语言中方法的入参。最后一行 int 0x10 是调用 BIOS 的 10 号中断程序,有点像高级语言中调用一个方法。这个不是咱们的重点,总之它实现了一个效果就是清屏。但你不写这一段也能够,无非就是让屏幕多了 QEMU 自己的输出,难看一点罢了。
第三部分
;----直接往显存中写数据 mov ax,0xb800 mov gs,ax mov byte [gs:0x00],'h' mov byte [gs:0x02],'e' mov byte [gs:0x04],'l' mov byte [gs:0x06],'l' mov byte [gs:0x08],'o'
这部分的目的就是让咱们的操做系统运行可以看出一点效果,有不少种方式,咱们选择直接往显存中写数据的方式,这种方式最简单,也最直观。
上一节课咱们讲了实模式下的内存分布,知道 0xB8000 - 0xB8FFFF 这段内存空间是文本模式下显存的内存映射区域,往这个区域里写数据,就至关于在屏幕上输出文本了。
因此前两行就是往 gs 段寄存器中写入该内存区域的起始地址 0xB800(注意这里不是少写了一个 0,由于实模式下段寄存器还须要乘以 16,因此此处先除以 16,详见上一节课的内容)。
后面的五条 mov 语句,就是往这片内存区域的开始的几个字节处,分别写入 'h'、'e'、'l'、'l'、'o'。你可能注意到上面反汇编出来的语句中后面不太同样,其实也能够写成:
mov byte [gs:0x0],0x68 mov byte [gs:0x2],0x65 mov byte [gs:0x4],0x6c mov byte [gs:0x6],0x6c mov byte [gs:0x8],0x6f
这是彻底等价的,0x68 是 ‘h’ 的 ASCII 码,你写成 ‘h’,汇编工具会自动帮你转换成 0x68。
第四部分
;----512字节的最后两字节是启动区标识 times 510-($-$$) db 0 db 0x55,0xaa
因此这两行代码的意思就是,后面一直补充 0,一直到 510 个字节,而后最后两个字节写死为 0x55, 0xaa,一共恰好是 512 字节,完美。
咱们上一步获得了 mbr.asm,如今执行下面的命令来编译它,生成一个 mbr.bin:
nasm -o mbr.bin mbr.asm
执行完后能够看到文件夹下多了一个叫 mbr.bin 的文件,用 Notepad++ 打开它,看看是否是和上面咱们看到的二进制文件同样呢?
实际上,咱们上面获得的 mbr.bin 文件,直接用 QEMU 启动也是能够的,由于它与一个 raw 格式的虚拟磁盘映像是如出一辙的。不过咱们仍是装模做样地建立一个虚拟磁盘映像,而后把刚刚的 bin 文件填充到磁盘的第一扇区,这样才像回事嘛。
执行下面的命令建立一个虚拟磁盘映像:
qemu-img create -f raw mbr.raw 1440K
咱们看到后面的数字为 1440K,这就更像回事了,刚刚咱们本身手写的二进制文件,只有 512 字节,没有那个硬盘是这么小的。这回咱们用工具直接建立一个 1440K 的映像,就更接近真实的硬盘啦。
将 mbr.bin 文件的内容,装载到 mbr.raw 这个空磁盘映像文件的第一扇区:
dd if=mbr.bin of=mbr.raw bs=512 count=1
该命令也是可以见名知意,就是从 if=mbr.bin 这个文件,往 of=mbr.raw 这个文件拷贝数据,以 bs=512 字节大小做为单位,拷贝 count=1 这么多单位的数据。
hooo!到了这步,和咱们一开始的情形就是同样的了,咱们有了一个 raw 格式的虚拟磁盘映像文件,咱们也有了 QEMU 这个虚拟机能够进行模拟,天然接下来就是最激动人心的时刻(其实早就激动过了)。
qemu-system-i386 mbr.raw
运行效果也是见了不少次的:
好了,启动区代码就是这么简单,其实重点在于对整个过程的理解,并不在于最终运行的那一刻,因此你能够看到大部分时间是在帮你捋顺这个过程,实际的代码量,不多不多。若是本 chat 中有不了解的地方,欢迎在留言区留言,或者先去看一下我上一节课的内容,好多问题可能都会找到答案:
若是你对自制一个操做系统感兴趣,不妨跟随这个系列课程看下去,甚至加入咱们,一块儿来开发。
当你看到该文章时,代码可能已经比文章中的又多写了一些部分了。你能够经过提交记录历史来查看历史的代码,我会慢慢梳理提交历史以及项目说明文档,争取给每一课都准备一个可执行的代码。固然文章中的代码也是全的,采用复制粘贴的方式也是彻底能够的。
若是你有兴趣加入这个自制操做系统的大军,也能够在留言区留下您的联系方式,或者在 gitee 私信我您的联系方式。
本课程打算出系列课程,我写到哪以为能够写成一篇文章了就写出来分享给你们,最终会完成一个功能全面的操做系统,我以为这是最好的学习操做系统的方式了。因此中间遇到的各类坎也会写进去,若是你能持续跟进,跟着我一块写,必然会有很好的收货。即便没有,交个朋友也是好的哈哈。