这里写个往期推荐,这样可以来回跳跃(狗头
0x00-环境准备
0x01-BIOS以及MBR
0x02-MBR支持显卡
0x03-MBR操作硬盘以及Loader
0x04-进入保护模式
0x00 开机
大家都知道,开机时咱们得首先得将操作系统载入内存RAM区才可以继续我们的工作,但是咱们操作系统都没运行,那我们是如何实现加载这一过程呢,难道说是妥妥的灵异事件?
其实并非如此,咱们是不是忘了还有个磁盘呢,而且不要认为没操作系统就无法运行程序,实际上操作系统只是给咱们提供了一个方便运行程序的环境,如果没有操作系统程序也是可以执行的,因为程序执行只需要简单的两个元素,指令序列以及CPU按序列执行即可,只不过没操作系统你可能实现函数十分繁琐,也就比如说虚拟地址与物理地址的映射,但是这种情况在早期没有保护模式这一概念时,程序员们也确实是直接运用物理地址进行编程。何况还有更早的程序员打纸孔带呢(
现在来回答刚刚的问题,那就是开机如何加载呢,首先给出一个答案,开机后咱们地一个需要运行的程序是位于磁盘的BIOS程序:
0x01 软件接力第一棒,BIOS
BIOS全称为 Base Input & Output System,也即基本输入输出系统,他的主要工作那就是基本输入输出了(手动狗头
1. 实模式
实模式是什么呢,简单粗暴来讲那就是哥们只用物理地址的模式,因为最开始开机时还不存在页表,更不存在页映射一说,所以咱们最开始的地址只能通过物理地址来进行编程,并且此时编程也只能用汇编。
而在Intel 8086时期只有20条地址线,也就是说若按字节寻址的话咱们的发挥空间就只有1MB,用16进制表示就是从0x00000到0xFFFFF,以下我先给出实模式下的地址分布:
开始地址 |
结束地址 |
大小 |
用途 |
FFFF0 |
FFFFF |
16B |
BIOS入口,这么小的一个位置实际上仅仅是一个跳转指令 |
F0000 |
FFFEF |
64KB-16B |
系统BIOS |
C8000 |
EFFFF |
160KB |
映射硬件适配器的ROM或内存映射式I/O |
C0000 |
C7FFF |
32KB |
显示适配器BIOS |
B8000 |
BFFFF |
32KB |
文本显示适配器 |
B0000 |
B7FFF |
32KB |
黑白显示适配器 |
A0000 |
AFFFF |
64KB |
彩色显示适配器 |
9FC00 |
9FFFF |
1KB |
EBDA |
7E00 |
9FBFF |
约608KB |
可用 |
7C00 |
7DFF |
512B |
MBR加载地址 |
500 |
7BFF |
约30KB |
可用 |
400 |
4ff |
256B |
BIOS数据区域 |
000 |
3FF |
1KB |
中断向量表 |
2. BIOS
从上表也可以直观的看出来,在0xF0000~0xFFFFF这儿的64KB就保存的BIOS代码,而BIOS的功能就是检测初始化硬件。但是具体是如何初始化呢,硬件自身会实现一些初始化的功能调用,这里BIOS直接调用即可,就跟咱们高级程序调库类似,但是BIOS面向的是硬件,而咱们面向的是操作系统或者说是程序员自己实现的库。
除了上述功能BIOS还做了一个伟大的事,那就是建立中断向量表,这样咱们就可以通过"int 中断号"来进行硬件调用(每次看到这个int我都想到int 0x80哈哈哈,来自pwner奇奇怪怪的直觉)
但是我们这里还得清楚一件事情,那就是BIOS是放哪儿的,这里直接说答案,他是存放在内存ROM区中,学过机组的伙伴可能知道主存一般分为RAM和ROM,其中RAM大多由DRAM这种存储器构成,但是他是断电就消失,并且他要保持数据必须一定时间内还要不断刷新行,所以咱们的BIOS不能放到rAM中,但是ROM断电是不会消失的,就像刻光盘一样刻上面了,所以咱们的BIOS是放在ROM区中的。
而BIOS其实也是指令流,也是个程序,所以肯定也得有入口地址,这个入口地址便是0xFFFF0,这时候就得考虑如何找到这个地址了,这里有个既定规则,那就是实模式下寄存器宽度为16位,而程序一般都是通过分段机制来进行寻址,分段机制需要用到两个寄存器,那就是cs,ip,相信会汇编的同学知道,所以咱们寻址都是通过cs:ip来进行寻址,但是如何通过两个16位宽度的寄存器来表示20位的地址呢,这里已经有前人作答,咱们站在巨人肩膀上了已经,那就是通过将cs的值左移四位,然后再加上ip地址值,这样就刚好是20位了,我话个图:

这里图中所表示的也就是真实情况下cs:ip的值,但是你可能会疑惑明明有其他的方案为什么只有这个是真实的呢,你说的确实对,但是这是人家规定了的,没有理由可言,就相当与为什么负数小于正书,这是人为规定的。还有个至于为什么cs会设定为0xf000,这也是加点他自动变的,没有为什么。
在这里也给大家说清楚,这里BIOS的入口地址为啥是0xFFFF0,此时留给BIOS的大小只有16字节了,16字节能干啥呢,没错啥也干不了,这里的16字节就只是一个jmp指令,也就是说你先执行BIOS时,你得先到0xFFF0,然后通过这里的跳转指令再跳到别处(过于滑稽,而这里的指令具体就是jmp far f000:e05b
,也就是远眺指令到0xfe05b的物理地址,而这里才是真正BIOS代码开始的地方
而接下来BIOS所做的事情就是各种检测内存显卡等外设信息,然后在0x000到0x3ff处建立中断向量表。
在完成上述工作之后,BIOS的工作告一段落,之后就是大名鼎鼎的MBR了。
0x02 主引导记录MBR
BIOS最后的工作就是检验0盘0道1扇区的内容,这里历史遗留问题所以说这里扇区是从1开始,大家不用刻意管这个,只需要记住BIOS最后检验的是地一个磁盘扇区即可,在检验过程中,若BIOS检验出该磁盘末尾两个字节是0x55和0xaa则认定其为活动区,便加载到物理地址0x7c00,然后跳转过去,即可开始执行MBR。
这里的0x55,0xaa是一对魔数,就跟java字节码文件开头的0xcafebaby一样,没啥实际意义,而0x7c00,跟上面一样,规定为MBR起始地址。(这里我看了大象书有详细的解释,但是我觉得意义不大,就没写出来
MBR为咱们地一个在编写操作系统中自行构造的程序,理论上现在咱们无所不能(中二起来惹 , 这里还有几个规定,我在这里一一说出:
- MBR大小为512字节
- 地511以及512字节必须是0xaa,0x55,这是由于咱们模拟的是x86平台,所以采用小端序
- 凑行(笑死
1. 汇编编程基础
咱们本次的汇编采用NASM编译器,所以采用他的编译规则,其中比较常用的符号这里得讲讲:
- $:表示当前汇编代码行地址
- $$:表示本section地址
- section: 汇编代码中的节,这是程序员自行设定的,声明自己这个是个干什么的节
2. NASM简单用法
咱们不用掌握全,知道目前需要用到的即可
nasm -f <format><filename> [-o <output>]
其中-f就是指定输出文件格式。若要知道有多少格式,咱们可用nasm -hf来查看
,具体我借个图如下:

由于代码较繁琐,就不贴这儿了,不过我还是会讲解。
3.编写思路
虽然这里不会贴代码,但是我会给出思路,大家可以自己尝试,然后去github上clone我的源码查看即可,里面注释也比较详细
-
首先清屏,这里利用了BIOS所建立的中断向量表,用0x06号功能,即int 0x10,而我们实现系统调用的操作只需我们将功能号送入ah(注意是ah,也就是说向ax中传入0x600)寄存器,然后执行int 0x10即可。
-
而在我们编写的section后面加上vstart=0x7c00表示告诉编译器把我这个起始地址编译为0x7c00
-
然后我们再通过中断3号功能来获取光标位置
-
然后我们来实现打印字符串,此刻由于我们未使用IO知识,所以我们还是用中断向量来实现(现在知道BIOS的伟大之处了),运用13号子功能
-
打印结束记得填充,nasm中有自带的填充语句,即为
times 指令
-
最后两字节填充0xaa55
4.代码实现
这里由于代码不多所以直接贴这儿,具体代码可以去我的github上面拷下来看,由于之后操作系统的编写代码就不止这么少,所以我之后很少会贴,毕竟十分看起来水贴
SECTION MBR vstart=0x7c00
mov ax,cs
mov dx,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0x600
mov bx,0x700
mov cx,0
mov dx,0x184f
int 0x10
mov ah,3
mov bh,0
int 0x10
mov ax, message
mov bp, ax
mov cx,0xa
mov ax,0x1301
mov bx,0x2
int 0x10
jmp $
message db "peiwithhao"
times 510-($-$$) db 0
db 0x55,0xaa
5.汇编
运用我们之前的知识
nasm -o mbr.bin mbr.S
之后咱们用ls查看下是否512字节,利用ls即可验证
-rw-rw-r-- 1 dawn dawn 512 Dec 26 07:41 mbr.bin
咱们会发现确实无误,这样咱们就可以做接下里的步骤,那就是把咱们的MBR程序给拷到磁盘上,而Linux本身提供了一个dd命令,他被成为穿甲弹,他可以深入磁盘任何一个扇区,这里给出几个选项的示意:
- of=FILE 指定要读的文件
- bs=BYTES 指定块大小
- count=BLOCKS 指定块数
- seek=BLOCKS 制定当我们把块输出到文件是要跳过多少块
- conv=CONVS 指定如何转换文件
介绍结束,之后我们使用这个命令将mbr.bin打入相应磁盘扇区,也就是第一快512扇区,还记得之前那个0,0,1吗,就是BIOS结束的工作所找寻的快
dd if=/你的路径/mbr.bin of=/你的路径/bochs/hd60M.img bs=512 count=1 conv=notrunc
执行完出现以下提示即表示成功打入

然后咱们就可以开始测试了
0x03 测试代码
激动的心,颤抖的手,这是咱们地一个自己实现的代码,还是跟之前一样
bin/bochs -f bochsrc.disk
我们在最开始执行的指令也可以发现这个就是jmp,跟我们最开始说BIOS执行的第一条指令的论述是完全一致的!

一切如咱所愿,大成功!!

0x04 总结
本结不难,大伙可能会遇到些什么功能号,上卷窗口类似的感觉很杂,但是不需担心,这并不是我们写OS的重点,所以稍微了解即可
这次得源码依旧在github上保持更新,分支名为BIOS,欢迎大家指正
传送门