[自制操作系统] 第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 ; 指明程序的装载地址 ; 以 ...
随机推荐
- json在前后台传递的形式
json对象和json字符串的区别纠结了好久一阵子,经过查阅大量博客资料总结得,字符串形式它就是带单引号或者双引号的,对象就是没有被引号包括,可以直接进行调用属性.前后端间数据传递json形式应该是字 ...
- Python 一网打尽<排序算法>之堆排序算法中的树
本文从树数据结构说到二叉堆数据结构,再使用二叉堆的有序性对无序数列排序. 1. 树 树是最基本的数据结构,可以用树映射现实世界中一对多的群体关系.如公司的组织结构.网页中标签之间的关系.操作系统中文件 ...
- python 处理网络帧时,CRC算法中整数按位取反运算(~)得到负数的规避方法
计算机中的符号数有三种表示方法,即原码.反码和补码.三种表示方法均有符号位和数值位两部分,符号位都是用0表示"正",用1表示"负". 正数的原码,反码,补码都是 ...
- Python 函数进阶-递归函数
递归函数 什么是递归函数 如果一个函数,可以自己调用自己,那么这个函数就是一个递归函数. 递归,递就是去,归就是回,递归就是一去一回的过程. 递归函数的条件 一般来说,递归需要边界条件,整个递归的结构 ...
- [python][flask] Flask 入门(以一个博客后台为例)
目录 1.安装 1.1 创建虚拟环境 1.2 进入虚拟环境 1.3 安装 flask 2.上手 2.1 最小 Demo 2.2 基本知识 3.解构官网指导 Demo 3.1 克隆与代码架构分析 3.2 ...
- Linux系统创建可执行文件软链接
技术背景 由于创建软链接这个事情,在算法开发的日常中使用到的并不是很多,因此本文也是做一个简单的回顾.这里我们使用的案例是通过TMalign这个蛋白质打分文件,在编译好可执行文件之后,可以使用建立软链 ...
- 《你不知道的JS》上
- SQL注入之information_schema
在学习SQL注入时, 经常拿出来的例子就是PHP+MySQL这一套经典组合. 其中又经常提到的>=5.0版本的MySQL的内置库: information_schema 简单看一下informa ...
- 【mq】从零开始实现 mq-08-配置优化 fluent
前景回顾 [mq]从零开始实现 mq-01-生产者.消费者启动 [mq]从零开始实现 mq-02-如何实现生产者调用消费者? [mq]从零开始实现 mq-03-引入 broker 中间人 [mq]从零 ...
- Springcloud及Git线上配置详解
SpringCloud 这个阶段该如何学? 三层架构 + MVC 框架: Spring IOC AOP SpringBoot,新一代的JavaEE开发标准,自动装配 模块化~ all in one,代 ...