[自制操作系统] 第04回 完善MBR
一、前景回顾
在之前我们说到,MBR的作用便是加载操作系统内核到指定位置。而MBR需要通过读取硬盘来获得操作系统内核。在上一回我们已经讲解了硬盘的工作原理和读取方法,本回便开始初步完善我们的MBR,使之具有读取硬盘的功能。
二、改写MBR
我们的MBR受限于512字节大小,在这么小的空间没法为内核准备好环境,更没法将内核加载到内存中运行。所以我们需要在另一个程序中完成初始化环境及加载内核的任务,这个程序我们称之为loader,即加载器。loader会在后面的内容实现,问题是,现在我们要考虑loader放在硬盘哪个位置?loader读取出来后加载到内存哪个位置呢?最后MBR又如何跳过去执行?这就是我们MBR最终的使命,也是我们目前需要解决的三个问题。
第一个问题,由于MBR占据了硬盘的第0个扇区(以LBA方式的逻辑来看,扇区从第0开始编号,若是以物理CHS方式的逻辑来看,扇区便是从第1开始编号),第1个扇区是空闲的,但是我们的空间是比较宽裕的,没必要这么省,所以这里就将loader放在第2扇区,后面MBR将它从第2扇区读取出。
第二个问题,loader被加载到内存哪个位置呢?原则上,在内存布局中找一个空闲位置都可以用来存放loader。查看实模式下的内存布局,我们可以看到0x500~0x7BFF和0x7E00~0x9FBFF这两段内存区域都可以。这里提前剧透一下,因为loader中需要定义一些数据结构(比如GDT全局描述符表,这个后面会涉及到),这些数据结构将来的内核依旧会使用到,因此loader被加载到内存后不能被覆盖。其次,随着内核的不断完善,我们的内核会不断变大,所以内核所在的内存地址会向着越来越高的地方增长,难免会超过可用区域的上限,因此我们考虑把loader放在位置低处,至少要放在将来内核加载处的下面,这样也可以尽量多留一些空间给内核。因此最后我们将loader的加载地址放在0x900处,完全是个人的意愿。
第三个问题呢,其实就很简单了,只要我们确定了loader的加载位置,最后只需要在MBR中使用jmp 0x900就可以顺利跳转到loader中去了。
还是首先来看看改写后的MBR代码吧:
1 %include "boot.inc"
2 section MBR vstart=0x7c00
3 mov ax, cs
4 mov ds, ax
5 mov es, ax
6 mov ss, ax
7 mov fs, ax
8 mov sp, 0x7c00
9 mov ax, 0xb800
10 mov gs, ax
11
12 ;利用int 0x10 的0x06号功能实现清屏
13 mov ax, 0x600
14 mov bx, 0x700
15 mov cx, 0
16 mov dx, 0x184f
17
18 int 0x10
19
20 mov ah, 3
21 mov bh, 0
22
23 int 0x10
24 ;输出字符串“HELLO MBR” A表示绿色背景闪烁,4表示前景色为红色
25 mov byte [gs:0x00],'H'
26 mov byte [gs:0x01],0xA4
27
28 mov byte [gs:0x02],'E'
29 mov byte [gs:0x03],0xA4
30
31 mov byte [gs:0x04],'L'
32 mov byte [gs:0x05],0xA4
33
34 mov byte [gs:0x06],'L'
35 mov byte [gs:0x07],0xA4
36
37 mov byte [gs:0x08],'O'
38 mov byte [gs:0x09],0xA4
39
40 mov byte [gs:0x0A],' '
41 mov byte [gs:0x0B],0xA4
42
43 mov byte [gs:0x0C],'M'
44 mov byte [gs:0x0D],0xA4
45
46 mov byte [gs:0x0E],'B'
47 mov byte [gs:0x0F],0xA4
48
49 mov byte [gs:0x10],'R'
50 mov byte [gs:0x11],0xA4
51
52 mov eax, LOADER_START_SECTOR ;起始扇区lba的地址
53 mov bx, LOADER_BASE_ADDR ;loader将要被写入的内存地址
54 mov cx, 4 ;待读入的扇区数
55 call rd_disk_m_16 ;调用函数,将loader写入到内存中
56
57 jmp LOADER_BASE_ADDR
58
59 ;---------------------------------------
60 ;功能:读取硬盘n个扇区
61 rd_disk_m_16:
62 mov esi, eax ;备份eax,eax中存放了扇区号,这里为0x2
63 mov di, cx ;备份cx,cx中存放待读入的扇区数
64
65 ;读写硬盘:
66 ;第一步:设置要读取的扇区数
67 mov dx, 0x1f2
68 mov al, cl
69 out dx, al
70
71 mov eax, esi
72
73 ;第二步:将lba地址存入到0x1f3 ~ 0x1f6
74 ;lba地址7-0位写入端口0x1f3
75 mov dx, 0x1f3
76 out dx, al
77
78 ;lba地址15-8位写入端口0x1f4
79 mov cl, 8
80 shr eax, cl
81 mov dx, 0x1f4
82 out dx, al
83
84 ;lba地址23-16位写入端口0x1f5
85 shr eax, cl
86 mov dx, 0x1f5
87 out dx, al
88
89 shr eax, cl
90 and al, 0x0f
91 or al, 0xe0
92 mov dx, 0x1f6
93 out dx, al
94
95 ;第三步:向0x1f7端口写入读命令,0x20
96 mov dx, 0x1f7
97 mov al, 0x20
98 out dx, al
99
100 ;第四步:检测硬盘状态
101 .not_ready:
102 nop
103 in al, dx
104 and al, 0x88
105 cmp al, 0x08
106 jnz .not_ready
107
108 ;第五步:从0x1f0端口读数据
109 mov ax, di
110 mov dx, 256
111 mul dx
112 mov cx, ax
113 ;di为要读取的扇区数,一个扇区共有512字节,每次读入一个字,总共需要
114 ;di*512/2次,所以di*256
115 mov dx, 0x1f0
116 .go_on_read:
117 in ax, dx
118 mov [bx], ax
119 add bx,2
120 loop .go_on_read
121 ret
122 ;---------------------------------------
123
124 times 510-($-$$) db 0
125 db 0x55, 0xaa
mbr.S
第1行中的%include"boot.inc",这个%include是nasm编译器中的预处理指令,意思就是编译之前将boot.inc文件也包含进来,boot.inc文件存放于当前目录下的include文件夹中。这个文件中目前就只定义了如下两行代码:
1 LOADER_BASE_ADDR equ 0x900
2 LOADER_START_SECTOR equ 0x2
其实就跟我们在C语言中的头文件作用类似,LOADER_BASE_ADDR便是将来loader被加载到内存中的指定位置,LOADER_START_SECTOR是loader存放在硬盘的扇区起始地址。
第2~50行和之前MBR的程序相似,就不再赘述。
下面的rd_disk_m_16便是本次MBR程序改造的重点,也就是硬盘的读取函数。仔细看代码中的步骤,其实就是对照着上一回内容中,硬盘的读取步骤来编写的。最后再将读取到的数据,也就是后面我们会编写的loader加载到内存地址0x900处。
同样使用nasm来编译我们的mbr.S文件,只是由于加入了头文件,因此在编译时需要指定头文件的路径。输入如下代码编译:
nasm -I include/ mbr.S -o mbr.bin
输入如下代码将mbr.bin写入到我们的硬盘第一个扇区,也就是最开始的那个扇区。
dd if=./mbr.bin of=./hd60M.img bs=512 count=1 conv=notrunc
好了,我们已经完成了MBR的修改,接下来需要去创建加载器loader。
三、实现loader
其实loader中的内容会比较多,但是目前我们不需要去考虑这些东西,我们这里先实现一个非常非常简单的loader,它的作用就是在屏幕上打印“HELLO LOADER”字符串。我们只是想测试一下MBR中读取硬盘函数和加载功能是否能成功。
同样是在当前目录下新建一个loader.S文件,键入如下代码:
1 %include "boot.inc"
2 section loader vstart=LOADER_BASE_ADDR
3 ;输出背景色为绿色,前景色为红色,并且跳动的字符串“HELLO LOADER”
4 mov byte [gs:0x00],'H'
5 mov byte [gs:0x01],0xA4
6
7 mov byte [gs:0x02],'E'
8 mov byte [gs:0x03],0xA4
9
10 mov byte [gs:0x04],'L'
11 mov byte [gs:0x05],0xA4
12
13 mov byte [gs:0x06],'L'
14 mov byte [gs:0x07],0xA4
15
16 mov byte [gs:0x08],'O'
17 mov byte [gs:0x09],0xA4
18
19 mov byte [gs:0x0A],' '
20 mov byte [gs:0x0B],0xA4
21
22 mov byte [gs:0x0C],'L'
23 mov byte [gs:0x0D],0xA4
24
25 mov byte [gs:0x0E],'O'
26 mov byte [gs:0x0F],0xA4
27
28 mov byte [gs:0x10],'A'
29 mov byte [gs:0x11],0xA4
30
31 mov byte [gs:0x12],'D'
32 mov byte [gs:0x13],0xA4
33
34 mov byte [gs:0x14],'E'
35 mov byte [gs:0x15],0xA4
36
37 mov byte [gs:0x16],'R'
38 mov byte [gs:0x17],0xA4
39 jmp $
loader.S
同样也是利用nasm和dd命令将汇编文件生成bin文件,随后再写入到硬盘中去。
nasm -I include/ loader.S -o loader.bin
dd if=./loader.bin of=./hd60M.img bs=512 count=4 seek=2 conv=notrunc
这里的seek=2的意思就是跳过两个扇区,也就是我们前面说的,将loader存放在磁盘的第0x2个扇区(以LBA法来表示)。
最后让我们开机看看实际效果如何:
这里其实一开始屏幕上方应该是HELLO MBR,随后才是HELLO LOADER。只是一闪而过了,有兴趣的朋友可以去截图看看。总之随着HELLO LOADER的出现,说明我们mbr中的硬盘读写函数是没有问题的,总的框架也没有问题。接下来要做的事就是不断地往loader中填充代码就可以了。本回到此结束,欲知后事如何,请看下回分解。
[自制操作系统] 第04回 完善MBR的更多相关文章
- [自制操作系统] 第02回 初识MBR
目录 一.前景回顾 二.写一个粗略的MBR 三.运行测试 一.前景回顾 上回说到,开机的启动过程就是当时Intel和BIOS等硬件厂商所制定的规则,现在我们来回顾一下有如下三点: 1.按下开机键后,C ...
- [自制操作系统] 第05回 CPU的三种模式
目录 一.前景回顾 二.实模式和保护模式 一.前景回顾 在之前我们说到,loader的作用才是读取加载操作系统内核,那么我们的重心就应该是loader.S文件,其实我们接下来也的确是会往loader. ...
- 【自制操作系统06】终于开始用 C 语言了,第一行内核代码!
一.整理下到目前为止的流程图 写到这,终于才把一些苦力活都干完了,也终于到了我们的内核代码部分,也终于开始第一次用 c 语言写代码了!为了这个阶段性的胜利,以及更好地进入内核部分,下图贴一张到目前为止 ...
- 《30天自制操作系统》笔记(03)——使用Vmware
<30天自制操作系统>笔记(03)——使用Vmware 进度回顾 在上一篇,实现了用IPL加载OS程序到内存,然后JMP到OS程序这一功能:并且总结出下一步的OS开发结构.但是遇到了真机测 ...
- 自制操作系统小样例——参考部分linux0.11内核源码
详细代码戳这里. 一.启动引导 采用软件grub2进行引导,基于规范multiboot2进行启动引导加载.multiboot2的文档资料戳这里. 二.具体内容 开发环境 系统环境:Ubuntu 14. ...
- 30天自制操作系统-day1
30天自制操作系统(linux环境)--第一天 我是在CentOS的环境上面实现的,使用ubuntu的环境也是类似的 第一步:因为要对二进制文件进行编辑,所以安装二进制编辑器hexedit(当然其他的 ...
- 《30天自制操作系统》笔记(06)——CPU的32位模式
<30天自制操作系统>笔记(06)——CPU的32位模式 进度回顾 上一篇中实现了启用鼠标.键盘的功能.屏幕上会显示出用户按键.点击鼠标的情况.这是通过设置硬件的中断函数实现的,可以说硬件 ...
- 《30天自制操作系统》笔记(02)——导入C语言
<30天自制操作系统>笔记(02)——导入C语言 进度回顾 在上一篇,记录了计算机开机时加载IPL程序(initial program loader,一个nas汇编程序)的情况,包括IPL ...
- 《30天自制操作系统》笔记(01)——hello bitzhuwei’s OS!
<30天自制操作系统>笔记(01)——hello bitzhuwei's OS! 最初的OS代码 ; hello-os ; TAB=4 ORG 0x7c00 ; 指明程序的装载地址 ; 以 ...
随机推荐
- crm单元测试使用
Action使用 使用paramBag传递入参,填写入参名,入参值,后使用 serviceProvider传入插件. Assert.AreEqual(this.output["state&q ...
- Elasticsearch8.1-ElasticsearchClient-Java客户端简单增删查改-随笔
环境准备 Springboot 基本环境 自行前往https://start.spring.io/ 构建一个即可 Elasticsearch服务端 简单说下windows版本的安装 https:// ...
- 序列化之Serializer类与ModelSerializer类的使用
序列化之Serializer类的使用(5星) 作用: 序列化,序列化器会把模型对象转换成字典,经过response以后变成json字符串 反序列化,把客户端发送过来的数据,经过request以后变成字 ...
- linux find命令 -mtime参数 根据修改时间查找文件
命令:find 搜索路径 -mtime n 主要说明n的含义: 例: n=5 "5"指的是前 5~6 天那一天修改的文件 n=-5 "-5"指的是 5 天内修改 ...
- 学习day43
开始学习html知识
- 如何突破Jenkins瓶颈,实现集中管理、灵活高效的CI/CD
在过去的几年间,随着DevOps的兴起,持续集成(Continuous Integration)与持续交付(Continuous Delivery)的热度也水涨船高.在本文中,我们将首先带您了解热门的 ...
- BigInterger && BigDecimal
BigInterger BigDecimal
- 测试必会 Docker 实战(一):掌握高频命令,夯实内功基础
在 Dokcer 横空出世之前,应用打包一直是大部分研发团队的痛点.在工作中,面对多种服务,多个服务器,以及多种环境,如果还继续用传统的方式打包部署,会浪费大量时间精力. 在 Docker 出现后,它 ...
- 如何改变函数内部 this 的指向
一.函数内 this 的指向 1. this 的指向是当调用函数时确定的,调用的方式不同,this 的指向也就不同. 1.1 this 一般是指向调用者. 函数类型 this 的指向 普通函数 Win ...
- 【Python数据分析案例】python数据分析老番茄B站数据(pandas常用基础数据分析代码)
一.爬取老番茄B站数据 前几天开发了一个python爬虫脚本,成功爬取了B站李子柒的视频数据,共142个视频,17个字段,含: 视频标题,视频地址,视频上传时间,视频时长,是否合作视频,视频分区,弹幕 ...