《操作系统真象还原》第二章

《操作系统真象还原》第二章

编写MBR主引导记录

载入内存

过程:

(1)程序被加载器(软件或硬件)加载到内存某个区域。
(2)CPU的cs:ip寄存器被指向这个程序的起始地址。

从按下主机上的power键后,第一个运行的软件是BIOS

BIOS

BIOS全称叫Base Input &Output System,即基本输入输出系统。

实模式下的 1MB内存布局

在这里插入图片描述

​ 先从低地址看,地址0~0x9FFFF处是DRAM(Dynamic Random Access Memory),即动态随机访问内存,我们所装的物理内存就是DRAM,如DDR、DDR2 等。

​ 内存地址0~0x9FFFF的空间范围是640KB,这片地址对应到了DRAM,也就是插在主板上的内存条。

在CPU眼里,我们插在主板上的物理内存不是它眼里“全部的内存”。

地址总线宽度决定了可以访问的内存空间大小,如16位机的地址总线为20位,其地址范围是1MB,32位地址总线宽度是32位,其地址范围是4GB。但以上的地址范围是指地址总线可以触及到的边界,是指计算机在寻址上可以到达的疆域。

在计算机中,并不是只有咱们插在主板上的内存条需要通过地址总线访问,还有一些外设同样是需要通过地址总线来访问的,这类设备还很多呢。若把全部的地址总线都指向物理内存,那其他设备该如何访问呢?由于这个原因,只好在地址总线上提前预留出来一些地址空间给这些外设用,这片连续的地址给显存,这片连续的地址给硬盘控制器等。留够了以后,地址总线上其余的可用地址
再指向DRAM,也就是指插在主板上的内存条、我们眼中的物理内存。

物理内存多大都没用,主要是看地线总线的宽度。还要看地址总线的设计,是不是全部用于访问DRAM。所以说,地址总线是决定我们访问哪里、访问什么,以及访问范围的关键。我们平时用的机器一般是32位,上面的内存条并不是全部都用到了,按理说内存条大小超过 4GB 就没意义了,超过了地址总线的势力就是浪费。不过通过前面的介绍,即使内存条大小没有超过地址总线的范围,也不会全都能被访问到,毕竟要预留一些地址用来访问其他外设,所以最终还得看地址总线把地址指向哪块内存了。这就是安装了 4GB 内存,电脑中只显示3.8GB左右的原因。

​ 看顶部的 0xF0000~0xFFFFF,这64KB的内存是ROM。这里面存的就是BIOS的代码。BIOS 的主要工作是检测、初始化硬件,怎么初始化的?硬件自己提供了一些初始化的功能调用,BIOS 直接调用就好了。BIOS 还做了一件伟大的事情,建立了中断向量表,这样就可以通过“int中断号”来实现相关的硬件调用,当然BIOS建立的这些功能就是对硬件的IO操作,也就是输入输出,但由于就64KB大小的空间,不可能把所有硬件的IO操作实现得面面俱到,而且也没必要实现那么多,毕竟是在实模式之下,对硬件支持得再丰富也白搭,精彩的世界是在进入保护模式以后才开始,所以挑一些重要的、保证计算机能运行的那些硬件的基本IO操作,就行了。这就是BIOS称为基本输入输出系统的原因。

BIOS是如何苏醒的

​ BIOS是计算机上第一个运行的软件,所以它不可能自己加载自己,由此可以知道,它是由硬件加载的。那这个硬件是谁呢?其实前面已经提到过了,相当于是只读存储器ROM,因为它一直就睡在那里不动。

​ BIOS代码所做的工作也是一成不变的,而且在正常情况下,其本身是不需要修改的,平时听说的那些主板坏了要刷BIOS的情况属于例外。于是BIOS顺理成章地便被写进此ROM。ROM也是块内存,内存就需要被访问。此ROM被映射在低端1MB内存的顶部,即地址0xF0000~0xFFFFF处,可以参考表2-1顶部的BIOS部分。只要访问此处的地址便是访问了BIOS,这个映射是由硬件完成的。

​ BIOS本身是个程序,程序要执行,就要有个入口地址才行,此入口地址便是0xFFFF0。最重要的一点来了,知道了BIOS在哪里后,CPU如何去执行它,即CPU中的cs:ip值是如何组合成0xFFFF0的。既然作为第一个运行的程序都没开始执行,自然就没办法用软件搞定这件事了,还是得靠硬件支持才行。

​ 在开机的一瞬间,也就是接电的一瞬间,CPU的cs:ip寄存器被强制初始化为0xF000:0xFFF0。由于开机的时候处于实模式,再重复一遍加深印象,在实模式下的段基址要乘以16,也就是左移4位,于是0xF000:0xFFF0的等效地址将是0xFFFF0。上面说过了,此地址便是BIOS的入口地址。

​ BIOS是在实模式下运行的,而实模式只能访问1MB空间(20位地址线,2的20 次方是1MB)。而地址0xFFFF0距1MB只有16个字节了(见表2-1),这么小的空间够干吗?BIOS 又要检测硬件,做各种初始化工作,还要建立中断向量表……16 字节的机器指令肯定干不了这么多事。既然此处只有16字节的空间了,这只能说明BIOS真正的代码不在这,那此处的代码只能是个跳转指令才能解释得通了。

​ 在内存物理地址0xFFFF0处的内容是一条跳转指令jmp far f000:e05b,这是条跳转指令。那CPU的执行流是跳到哪里了呢?段基址0xf000左移4位+0xe05b,即跳向了0xfe05b处,这是BIOS代码真正开始的地方。接下来BIOS 便马不停蹄地检测内存、显卡等外设信息,当检测通过,并初始化好硬件后,开始在内存中0x000~0x3FF处建立数据结构,中断向量表IVT并填写中断例程。

为什么是 0x7c00

​ BIOS最后一项工作校验启动盘中位于0盘0道1扇区的内容。

在此插播一段小告示:在计算机中是习惯以0作为起始索引的,因为人们已经习惯了偏移量的概念,无论是机器眼里和程序员眼里用“相对”的概念,即偏移量来表示位置显得很直观,所以很多指令中的操作数都是用偏移量表示的。0盘0道1扇区本质上就相当于0盘0道0扇区。为什么称为1呢,因为硬盘扇区的表示法有两种,我们描述0盘0道1扇区用的便是其中的一种:CHS方法,即柱面Cylinder磁头Header扇区Sector(另外一种是LBA 方式,暂不关心),“0盘”说的是0磁头,因为一张盘是有上下两个盘面的,一个盘面上对应一个磁头,所以用磁头Header 来表示盘面。“0道”是指0柱面,柱面Cylinder指的是所有盘面上、编号相同的磁道的集合,形象一点描述就是把很多环叠摞在一起的样子,组合在一起之后是一个立体的管状。“1扇区”才是我们要解释的部分,将磁道等距划分成一段段的小区间,由于磁道是圆形的,确切地说是圆环,这些被划分出来的小区间便是扇形,所以称为扇区。好了,背景交待完了,重点来了,在CHS 方式中扇区的编号是从1开始的,不是0,不是0,所以0盘0道1扇区其实就相当于0盘0道0扇区,它就是磁盘上最开始的那个扇区。而LBA方式中,扇区编号是从0开始的。先不要着急,只要知道MBR所在的位置是磁盘上最开始的那个扇区就行了。

​ 如果此扇区末尾的两个字节分别是魔数0x55和0xaa,BIOS 便认为此扇区中确实存在可执行的程序(在此先剧透一下,此程序便是久闻大名的主引导记录MBR),便加载到物理地址0x7c00,随后跳转到此地址,继续执行。

不过,这就又抛出两个问题:

(1)为什么是0盘0道1扇区的内容?

在计算机中处处充满了协议、约定,所以,将0盘0道1扇区作为mbr的栖身之地,我完全可以理解
为规定。我们反证一下,如果不存在这个“规定”,会发生什么。当然,此扇区最初是给BIOS 使用的,
咱们设想一下 BIOS 的工作将变成怎样。

主引导记mbr 是段程序,无论位于软盘、硬盘或者其他介质,总该有个地方保存它。Ok,现在不告
诉BIOS它存储在哪个位置了。BIOS只好将所有检测到的存储设备上的每一个存储单位都翻一遍,挨个
对比,如果发现该存储单位最后的两个字节是0x55和0xaa,就认为它是mbr。这就好比查字典一样,不
用偏旁部首和拼音检索的方法,只能一页一页翻了。

由于0盘0道1扇区是磁盘的第一个扇区,mbr选择了离BIOS最近的位置

(2)为什么是物理地址0x7c00,而不是个好记或好看的其他地址?

1981年8月,IBM公司生产了世界上第一台个人计算机PC 5150,它是个人兼容机的祖先,个人计算机肯定要运行操作系统,在这台计算机上,运行的操作系统是DOS 1.0,不清楚此系统要求的最小内存是16KB,还是32KB,反正 PC 5150 BIOS研发工程师就假定其是32KB的,所以此版本BIOS是按最小内存32KB研发的。

MBR不是随便放在哪里都行的,首先不能覆盖已有的数据,其次,不能过早地被其他数据覆盖。不覆盖已有数据,这个好理解。说一下后面这个“其次”。通常,MBR的任务是加载某个程序(这个程序一般是内核加载器,很少有直接加载内核的)到指定位置,并将控制权交给它。所谓的交控制权就是jmp过去而已。之后MBR 就没用了,被覆盖也没关系。我说的过早被覆盖,是指不能让mbr破坏自己,比如被加载的程序,如内核加载器,其放置的内存位置若是MBR自己所在的范围,这不就是破坏自己了吗,这就是
我所说的“过早”了,怎么也得等MBR执行完才行。

8086CPU要求物理地址0x0~0x3FF存放中断向量表,所以此处不能动了,再选新的地方看看。按DOS 1.0要求的最小内存32KB来说,MBR希望给人家尽可能多的预留空间,这样也是保全自己的作法,免得过早被覆盖。所以MBR只能放在32KB的末尾。

MBR本身也是程序,是程序就要用到栈,栈也是在内存中的,MBR虽然本身只有512字节,但还要为其所用的栈分配点空间,所以其实际所用的内存空间要大于512字节,估计1KB内存够用了。

结合以上三点,选择32KB中的最后1KB最为合适,那此地址是多少呢?32KB换算为十六进制为0x8000,
减去1KB(0x400)的话,等于0x7c00。这就是倍受质疑的0x7c00的由来,这下清楚了。

让MBR先飞一会儿

$ 和 $$

和 和 $是编译器NASM预留的关键字,用来表示当前行和本section的地址,起到了标号的作用,它是NASM提供的,并不是CPU原生支持的,相当于伪指令一样,对CPU来说是假的。

属于“隐式地”藏在本行代码前的标号,也就是编译器给当前行安排的地址,看不到却又无处不在, 属于“隐式地”藏在本行代码前的标号,也就是编译器给当前行安排的地址,看不到却又无处不在, 属于隐式地藏在本行代码前的标号,也就是编译器给当前行安排的地址,看不到却又无处不在,在每行都有。或者这种说法并不是很正确,只有“显示地”用了$的地方,nasm 编译器才会将此行的地址公布出来。

​ $$指代本section的起始地址,此地址同样是编译器给安排的。

​ 对于 和 和 的意义,是编译器给安排的地址,默认情况下,它们的值是相对于本文件开头的偏移量。至于实际安排的是多少,还要看程序员同学是否在 s e c t i o n 中添加了 v s t a r t 。这个关键字可以影响编译器安排地址的行为,如果该 s e c t i o n 用了 v s t a r t = x x x x 修饰, 的意义,是编译器给安排的地址,默认情况下,它们的值是相对于本文件开头的偏移量。至于实际安排的是多少,还要看程序员同学是否在section中添加了vstart。这个关键字可以影响编译器安排地址的行为,如果该section用了vstart=xxxx修饰, 的意义,是编译器给安排的地址,默认情况下,它们的值是相对于本文件开头的偏移量。至于实际安排的是多少,还要看程序员同学是否在section中添加了vstart。这个关键字可以影响编译器安排地址的行为,如果该section用了vstart=xxxx修饰, 的值则是此 s e c t i o n 的虚拟起始地址 x x x x 。 的值则是此section的虚拟起始地址xxxx。 的值则是此section的虚拟起始地址xxxx的值是以xxxx为起始地址的顺延。如果用了vstart关键字,想获得本section在文件中的真实偏移
量(真实地址)该怎么做?nasm编译器提供了这个方法。
section.节名.start。如果没有定义section,nasm默认全部代码同为一个section,起始地址为0。

编写MBR引导内容

这里我们需要提前安装 nasm编译器 方便后面的使用
只需要在终端输入指令
sudo apt-get install nasm

即刻完成安装下载

小知识:
bin纯二进制文件 cpu可直接食用
elf给操作系统程序加载器食用的

下面就是编写内容了
其实只完成了清空屏幕和输出字符串的功能

; 主引导程序
;-------------------------------------------------------

section MBR vstart=0x7c00

    ; 初始化段寄存器
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov fs, ax
    mov sp, 0x7c00 ;这个时候 ds = es = ss = 0 栈指针指向MBR开始位置
    ; 初始化段寄存器结束


    ; 清屏 利用0x06号功能,上卷全部行,则可清屏。
    ; -----------------------------------------------------------
    ;INT 0x10   功能号:0x06	   功能描述:上卷窗口
    ;------------------------------------------------------
    ;输入:
    ;AH 功能号= 0x06
    ;AL = 上卷的行数(如果为0,表示全部)
    ;BH = 上卷行属性
    ;(CL,CH) = 窗口左上角的(X,Y)位置
    ;(DL,DH) = 窗口右下角的(X,Y)位置
    ;无返回值:
    mov ax, 0x600 ; 设置AH为06, 即功能号= 0x06, 上卷全部行数
    mov bx, 0x700
    mov cx, 0     ; 窗口左上角的(0,0)位置
    mov dx, 0x184f ;窗口右下角的(24,79)位置, 因为默认为80x25的VGA文本模式, 0x18=24, 0x4f=79
    int 0x10
    ; 清屏结束


    ;;;;;;;;;    下面这三行代码是获取光标位置    ;;;;;;;;;
    ;.get_cursor获取当前光标位置,在光标位置处打印字符.
    mov ah, 3   ; 输入: 3号子功能是获取光标位置,需要存入ah寄存器
    mov bh, 0   ; bh寄存器存储的是待获取光标的页号
    ; 输出: ch=光标开始行,cl=光标结束行
    ; dh=光标所在行号,dl=光标所在列号
    int 0x10
    ;;;;;;;;;    获取光标位置结束    ;;;;;;;;;;;;;;;;


    ;;;;;;;;;    往光标处打印字符串    ;;;;;;;;;;;
    ;还是用10h中断,不过这次是调用13号子功能打印字符串
    mov ax, message ;往ax写入字符串首地址
    mov bp, ax  ; es:bp 为串首地址, es此时同cs一致,

    ; 光标位置要用到dx寄存器中内容,cx中的光标位置可忽略
    ; .get_cursor输出: ch=光标开始行,cl=光标结束行
    ; dh=光标所在行号,dl=光标所在列号
    mov cx, 5		; 显示字符串长度
    mov ax, 0x1301  ; 子功能号13是显示字符及属性,要存入ah寄存器,al设置写字符方式 al=01: 显示字符串,光标跟随移动
    mov bx, 0x2 ; bh存储要显示的页号,此处是第0页,bl中是字符属性, 属性黑底绿字(bl = 02h)
    int 0x10    ; 执行BIOS 0x10 号中断
    ;;;;;;;;;      打字字符串结束	 ;;;;;;;;;;;;;;;


    jmp $   ; 使程序悬停在此

    message db "1 MBR"
    times 510-($-$$) db 0 	;times伪指令,重复执行db 0 指令 510-($-$$)次
    db 0x55,0xaa 	;在最后写入魔数 0x55 0xaa

在这里插入图片描述

编译并检验mbr.bin

译时输入指令
nasm -o mbr.bin mbr.S
如果没有报错的话 可以看到你编译的文件夹中多了一个
mbr.bin的文件
并且我们的mbr引导区域要求是512字节
我想为什么最少是512字节呢
我感觉应该是 一个扇区规格一般是512字节
那么mbr刚好占用了一个扇区则刚好为那么大
之后我们再用指令ls -l 看看当前目录下文件 和文件的属性
发现也恰好是我们要求的512字节 那应该是没有其他的问题了
如果有问题回来再检查一下指令

在这里插入图片描述

mbr.bin文件布局:

在这里插入图片描述

dd命令

linux命令总结dd命令详解
这里是我存储位置的代码 供参考

dd if=/home/steven/bochs/mbr.bin of=/home/steven/bochs/hd60M.img bs=512 count=1 conv=notrunc

在这里插入图片描述

这里就发现 已经把我们的代码通过dd指令这把利刃插进了 我们创建的虚拟磁盘中的0号扇区

打开bochs测试

在这里插入图片描述

测试成功😀

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值