从0到-1写一个操作系统-0x01-BIOS以及MBR
本帖最后由 peiwithhao 于 2023-3-2 14:00 编辑> 这里写个往期推荐,这样可以来回跳跃(狗头
> (https://www.52pojie.cn/thread-1730367-1-1.html)
> (https://www.52pojie.cn/thread-1730510-1-1.html)
> (https://www.52pojie.cn/thread-1731150-1-1.html)
> (https://www.52pojie.cn/thread-1731546-1-1.html)
> (https://www.52pojie.cn/thread-1732769-1-1.html)
## 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位了,我话个图:
![](http://imgsrc.baidu.com/forum/pic/item/d4628535e5dde7115b340f32e2efce1b9c166114.jpg)
这里图中所表示的也就是真实情况下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为咱们地一个在编写操作系统中自行构造的程序,理论上现在咱们无所不能(中二起来惹 , 这里还有几个规定,我在这里一一说出:
1. MBR大小为512字节
2. 地511以及512字节必须是0xaa,0x55,这是由于咱们模拟的是x86平台,所以采用小端序
3. 凑行(笑死
### 1. 汇编编程基础
咱们本次的汇编采用NASM编译器,所以采用他的编译规则,其中比较常用的符号这里得讲讲:
+ $:表示当前汇编代码行地址
+ $$:表示本section地址
+ section: 汇编代码中的节,这是程序员自行设定的,声明自己这个是个干什么的节
### 2. NASM简单用法
咱们不用掌握全,知道目前需要用到的即可
> nasm -f <format><filename> [-o <output>]
其中-f就是指定输出文件格式。若要知道有多少格式,咱们可用`nasm -hf来查看`,具体我借个图如下:
![](http://imgsrc.baidu.com/forum/pic/item/9358d109b3de9c8239efb64b2981800a18d84368.jpg)
由于代码较繁琐,就不贴这儿了,不过我还是会讲解。
### 3.编写思路
虽然这里不会贴代码,但是我会给出思路,大家可以自己尝试,然后去github上clone我的源码查看即可,里面注释也比较详细
1. 首先清屏,这里利用了BIOS所建立的中断向量表,用0x06号功能,即int 0x10,而我们实现系统调用的操作只需我们将功能号送入ah(注意是ah,也就是说向ax中传入0x600)寄存器,然后执行int 0x10即可。
2. 而在我们编写的section后面加上vstart=0x7c00表示告诉编译器把我这个起始地址编译为0x7c00
3. 然后我们再通过中断3号功能来获取光标位置
4. 然后我们来实现打印字符串,此刻由于我们未使用IO知识,所以我们还是用中断向量来实现(现在知道BIOS的伟大之处了),运用13号子功能
5. 打印结束记得填充,nasm中有自带的填充语句,即为
> times 指令
6. 最后两字节填充0xaa55
### 4.代码实现
这里由于代码不多所以直接贴这儿,具体代码可以去我的github上面拷下来看,由于之后操作系统的编写代码就不止这么少,所以我之后很少会贴,毕竟十分看起来水贴
```
;主引导程序
;------------------------------------------
SECTION MBR vstart=0x7c00 ; 向编译器表示咱们这儿起始地址应为0x7c00
mov ax,cs
mov dx,ax ; 由于BIOS通过 0:0x7c00跳转MBR,所以此时cs为0,因此借他来初始化寄存器
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
; 清屏利用0x06号子功能,上卷全部行即可清屏
;-------------------------------------------
;INT 0x10 功能号:0x06 功能:上卷窗口
;-------------------------------------------
;输入:
;AH 功能号:0x06
;AL = 上卷行数(若为0则表示全部行,太适合我们辣)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值
mov ax,0x600
mov bx,0x700
mov cx,0 ; 左上角(0,0)
mov dx,0x184f ; 右下角(0x4f,0x18),在VGA文本模式中一行只能容纳80字符
int 0x10
;;;;;;;; 下面三行获取光标位置 ;;;;;;;;;;;;;;;
; ,get_cursor获取光标位置,并在光标位置打印字符
mov ah,3 ;输入: 3号子功能即为获取当前光标位置
mov bh,0 ;bh 寄存器存储的是待获取光标的页号
int 0x10 ;输出 ch=光标开始行,cl =光标结束行
;dh=光标所在行号,dl=光标所在页号
;;;;;;;; 获取光标结束 ;;;;;;;;;;;;;;
;;;;;;;;;;; 打印字符串 ;;;;;;;;;;;;;;;;
;依旧用中断的0x13号子功能
mov ax, message ;文件末会声明此字符串
mov bp, ax ;es:bp为串首地址,es此时由于最开始的初始化,同cs一致
;光标位置需要用到dx寄存器内容,cx中的光标位置可忽略
mov cx,0xa ;cx 为串长度,不包括结束副\x00
mov ax,0x1301 ;ah=13为显示字符功能号
;al = 01 表示该功能的模式,具体模式有以下几种
;(1)0,显示字符串,光标返回起始位置
;(2)1,显示字符串,光标跟随到新位置
;(3)2,显示字符串以及属性,光标返回起始位置
;(4)3,显示字符串以及属性,光标跟随到新位置
mov bx,0x2 ;bh存储要显示的页号,此时为0页,bl中是字符属性,bl=02h表示黑底绿字
int 0x10 ; 执行BIOS 0x10中断
;;;;;;;;;; 打印字符串结束 ;;;;;;;;;;;;;;;
jmp $ ;悬停指针
messagedb "peiwithhao"
times 510-($-$$) db 0 ;$-$$表示该指令行距离section起始地址的偏移,这里也可表示为目前指令大小
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
执行完出现以下提示即表示成功打入
![](http://imgsrc.baidu.com/forum/pic/item/4034970a304e251f6220aa3ce286c9177e3e531b.jpg)
然后咱们就可以开始测试了
## 0x03 测试代码
激动的心,颤抖的手,这是咱们地一个自己实现的代码,还是跟之前一样
> bin/bochs -f bochsrc.disk
我们在最开始执行的指令也可以发现这个就是jmp,跟我们最开始说BIOS执行的第一条指令的论述是完全一致的!
![](http://imgsrc.baidu.com/forum/pic/item/8435e5dde71190efbf90db1b8b1b9d16fcfa608f.jpg)
一切如咱所愿,大成功!!
![](http://imgsrc.baidu.com/forum/pic/item/dbb44aed2e738bd4e2e1fcf5e48b87d6267ff936.jpg)
## 0x04 总结
本结不难,大伙可能会遇到些什么功能号,上卷窗口类似的感觉很杂,但是不需担心,这并不是我们写OS的重点,所以稍微了解即可
这次得源码依旧在github上保持更新,分支名为BIOS,欢迎大家指正
> [传送门](https://github.com/PeiandHao/Simple_OS_For_Studing)
十多年前使用过一个大神写的操作系统,现在还在开发后续版本哦
http://www.os-z.com/ 先收藏,补充点基础知识再学习学习。谢谢分享! 感谢分享。。。 封装试试看 红客联盟红哥 发表于 2022-12-26 21:53
封装试试看
这还远未达到操作系统的要求,仅仅是个开头的引导程序,到全部写完了封装试试{:301_988:} peiwithhao 发表于 2022-12-26 21:54
这还远未达到操作系统的要求,仅仅是个开头的引导程序,到全部写完了封装试试
好用的话可以开公司,我有tg大学联盟可介绍他们体验 哈点研究一下 请继续教我 很专业额 很专业!!