[自制操作系统] 第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 ; 指明程序的装载地址 ; 以 ...
随机推荐
- 设计模式学习笔记(十四)责任链模式实现以及在Filter中的应用
责任链模式(Chain Of Responsibility Design Pattern),也叫做职责链,是将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求.当有请求发生时,可将请求沿着这条 ...
- [ Perl ] 对文本文件进行行列翻转
https://www.cnblogs.com/yeungchie/ code #!/usr/bin/env perl #----------------------------- # Program ...
- Java学习day3
今天跟着b站up的视频学习了方法定义调用与重载,以及类的封装. Java中的方法与c++当中的函数类似,只是定义格式为: public static void 方法名(){ 方法体 } 上完课做实验的 ...
- addEventListener() 和 removeEventListener() 简介
DOM方法 addEventListener() 和 removeEventListener() 是用来分配和删除事件的函数 这两个方法都需要三个参数 分别为: 事件名称(String) ...
- IDEA小技巧:Markdown里的命令行可以直接运行了
作为一名开发者,相信大部分人都喜欢用Markdown来写文章和写文档. 如果你经常用开源项目或者自己维护开源项目,肯定对于项目下的README文件也相当熟悉了吧,通常我们会在这里介绍项目的功能.如何使 ...
- 共读《redis设计与实现》-数据结构篇
准备将之前攒下的书先看一遍,主要是有个大概的了解,以后用的时候也知道在哪里找.所以准备开几篇共读的帖子,激励自己多看一些书. Redis 基于 简单动态字符串(SDS).双端链表.字典.压缩列表.整数 ...
- 支持中文!秒建 wiki 知识库的开源项目,构建私人知识网络
不知道有没有人和我一样,觉得自建的东西是互联网上的"自留地".私人空间,有一种自己的一亩三分地随心所欲的痛快. 比如自建的博客想写什么随笔就写什么,不用取悦读者可以自娱自乐:再比如 ...
- SpringCloud入门简述
1.微服务简述 微服务,是一个小型的服务,也是一种设计理念,将一个大型繁杂的系统拆分为多个小型的服务,进行独立部署,这些服务在独立进程中运行,通过特定的协议进行通信 优点: 轻量化:一个服务不 ...
- 03. 树莓派初始配置——安装vim编辑器
安装vim编辑器 树莓派系统默认是不带vim编辑器的,默认安装的vi编辑器在编辑文档的时候用得特别不舒服,对人体的血压和树莓派的寿命影响较大. 解决办法:一行命令 1. 安装vim编辑器 sudo a ...
- 神经网络 CNN 名词解释
隐藏层 不是输入或输出层的所有层都称为隐藏层. 激活和池化都没有权重 使层与操作区分开的原因在于层具有权重.由于池操作和激活功能没有权重,因此我们将它们称为操作,并将其视为已添加到层操作集合中. 例如 ...