第三章 MBR -- loader

news/2025/2/28 20:10:19/文章来源:https://www.cnblogs.com/fdxsec/p/18651761

第三章 MBR --> loader

本文是对《操作系统真象还原》第三章学习的笔记,欢迎大家一起交流。

a

知识介绍

在上一章的代码部分,我们通过 BIOS 中断进行字符输出,但是离开实模式之后,BIOS 中断就没法用了,因为 BIOS 中断向量表只在实模式下存在,因此我们肯定还会有别的方法来输出字符串,这就是通过显存输出。

能这样的原理是因为显卡的内存已经编排到了 cpu 能够寻址的范围之内,当 cpu 操作这部分“内存”时,实际上是直接在和显卡打交道。显卡拿到了数据处理之后,显示器最终会按照要求显示这些数据。内存中的显存映射的地址范围如下:

image

从起始地址 0xB8000 到 0xBFFFF,这片 32KB 大小的内存区域是用于文本显示。我们往 0xB8000 处输出的字符直接会落到显存中,显存中有了数据,自然显卡就将其搬到显示器屏幕上了,这后续的事情咱们是不需要处理的,咱们只要保证写进显存的数据是正确的就可以。

显示器上每个字符占两字节,低字节是字符 ASCII 码,高字节是用来控制颜色的。高字节低 4 位是字符前景色,也就是字符的颜色(RGB 是红蓝绿三种颜色的调和,I 位表示是否高亮),高字节高 4 位是字符的背景色(RGB 是红蓝绿三种颜色的调和,K 位控制是否闪烁)。所以我们向显卡的对应内存操作时,也应按照如下格式:

image

高八位的颜色组合如下:

image

在写代码之前再补充一个高端内存的小知识,内存只有 0--0xffff,但是可以表示到 0x10ffef,这是因为 0xFFFF*16+0xFFFF=0xFFFF0+0xFFFF

image

代码部分

;主引导程序 
;
;LOADER_BASE_ADDR equ 0xA000 
;LOADER_START_SECTOR equ 0x2
;------------------------------------------------------------
SECTION MBR vstart=0x7c00     mov ax,cs  mov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10   功能号:0x06	   功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:mov     ax, 0600hmov     bx, 0700hmov     cx, 0               ; 左上角: (0, 0)mov     dx, 184fh	       ; 右下角: (80,25),; 因为VGA文本模式中,一行只能容纳80个字符,共25行。; 下标从0开始,所以0x18=24,0x4f=79int     10h                 ; int 10h; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"mov byte [gs:0x00],'1'mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色mov byte [gs:0x02],' 'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'M'mov byte [gs:0x05],0xA4   mov byte [gs:0x06],'B'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'R'mov byte [gs:0x09],0xA4jmp $		       ; 通过死循环使程序悬停在此times 510-($-$$) db 0db 0x55,0xaa

初始化以及清屏的工作和第二章一样的,这里不再赘述,我们直接看操作显存部分。

13-14 行将 gs 寄存器初始化为 0xb800,方便后面直接向显存写字符

17-50 行就是向显存写字符的过程,以两个字节为一个单位,第八位是字符,高八位是控制颜色和背景色,我们这里统一写入 0xA4​,即 1010,0100​,结合上面颜色组合的表格(注意小端格式)可知,前景色为红色,绿色背景闪烁。

效果如下,我这里把字符串改了一下:

image

b

知识介绍

好了,接下来我们要用 MBR 做点实事了,MBR 只有 510B,能做的事情非常少,所以不能指望它做完所有事情。所以,我们用它把操作系统的 loader 加载到指定位置,然后跳转到 loader 执行,loader 由于大小可以比 MBR 大得多,所以能做的就很多了。

所以,MBR 要加载 loader,就必须要和磁盘打交道。打交道的方式很简单,就是通过 in 与 out 指令与磁盘暴露在外的寄存器交互。下图是 in 与 out 指令的用法:

image

磁盘端口寄存器对应的用途(详细请见书 p127):

image

其中 Status 与 Device 寄存器比较复杂,它们的结构的含义如下:

image

在写硬盘时 Status 就变成了 command 寄存器,这和 408 学的东西也一致,这里主要用到三个命令:

(1)identify:0xEC,即硬盘识别。

(2)read sector:0x20,即读扇区。

(3)write sector:0x30,即写扇区。

接下来我们来实现代码,实现 mbr 从硬盘读取 loader,然后跳转执行 loader。

首先约定一下读取硬盘的过程,好让我们做到心中有数:

(1)先选择通道,往该通道的 sector count 寄存器中写入待操作的扇区数。

(2)往该通道上的三个 LBA 寄存器写入扇区起始地址的低 24 位。

(3)往 device 寄存器中写入 LBA 地址的 24~27 位,并置第 6 位为 1,使其为 LBA 模式,设置第 4 位,选择操作的硬盘(master 硬盘或 slave 硬盘)。

(4)往该通道上的 command 寄存器写入操作命令。

(5)读取该通道上的 status 寄存器,判断硬盘工作是否完成。

(6)如果以上步骤是读硬盘,进入下一个步骤。否则,完工。

(7)将硬盘数据读出。

读出之后写入内存,然后跳转执行即可,但是在此之前我们还需要解决两个问题,loader 写到硬盘的哪里,loader 写入内存的哪里。

loader 写到硬盘的哪里

由于 MBR 是占据了硬盘的第 0 扇区(以逻辑 LBA 方式,扇区从 0 开始编号,若是以物理 CHS 方式,扇区则从 1 开始编号),第 1 扇区是空闲的,可以用,但离得太近总感觉不如隔开一点心里踏实,所以把 loader 放到第 2 扇区。MBR 从第 2 扇区中把它读出来。

loader 写入内存的哪里

读出来放到哪里呢?原则上是找个空闲地方就行了,在实模式下的内存布局表格中查看下,只要在“用途”列中注明“可用区域”的地方都可以用。0x500~0x7BFF 和 0x7E00~9FBFF 这两段内存区域都可以。

image

首先,loader 中要定义一些数据结构(如 GDT 全局描述符表,不懂没关系,以后会说),这些数据结构将来的内核还是要用的,所以 loader 加载到内存后不能被覆盖。

其次,随着咱们不断添加功能,内核必然越来越大,其所在的内存地址也会向越来越高的地方发展,难免会超过可用区域的上限,咱们尽量把 loader 放在低处,多留出一些空间给内核。

所以,作者将 loader 的加载地址选为 0x900。为什么不是 0x500,这个多省空间?还是预留出一定空间吧,彼此隔开远一点心里才踏实,不差这点空间了,哈哈,完全是作者的偏好,大家随意啦。

代码部分

ok,下面上代码

MBR

;主引导程序 
;------------------------------------------------------------
%include "boot.inc"
SECTION MBR vstart=0x7c00     mov ax,cs  mov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00mov ax,0xb800mov gs,ax; 清屏
;利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10   功能号:0x06	   功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:mov     ax, 0600hmov     bx, 0700hmov     cx, 0                   ; 左上角: (0, 0)mov     dx, 184fh		   ; 右下角: (80,25),; 因为VGA文本模式中,一行只能容纳80个字符,共25行。; 下标从0开始,所以0x18=24,0x4f=79int     10h                     ; int 10h; 输出字符串:MBRmov byte [gs:0x00],'1'mov byte [gs:0x01],0xA4mov byte [gs:0x02],' 'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'M'mov byte [gs:0x05],0xA4	   ;A表示绿色背景闪烁,4表示前景色为红色mov byte [gs:0x06],'B'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'R'mov byte [gs:0x09],0xA4mov eax,LOADER_START_SECTOR	 ; 起始扇区lba地址mov bx,LOADER_BASE_ADDR       ; 写入的地址mov cx,1			 ; 待读入的扇区数call rd_disk_m_16		 ; 以下读取程序的起始部分(一个扇区)jmp LOADER_BASE_ADDR;-------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:	   
;-------------------------------------------------------------------------------; eax=LBA扇区号; ebx=将数据写入的内存地址; ecx=读入的扇区数mov esi,eax	  ;备份eaxmov di,cx		  ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数mov dx,0x1f2mov al,clout dx,al            ;读取的扇区数mov eax,esi	   ;恢复ax;第2步:将LBA地址存入0x1f3 ~ 0x1f6;LBA地址7~0位写入端口0x1f3mov dx,0x1f3                   out dx,al                      ;LBA地址15~8位写入端口0x1f4mov cl,8shr eax,clmov dx,0x1f4out dx,al;LBA地址23~16位写入端口0x1f5shr eax,clmov dx,0x1f5out dx,alshr eax,cland al,0x0f	   ;lba第24~27位or al,0xe0	   ; 设置7~4位为1110,表示lba模式mov dx,0x1f6out dx,al;第3步:向0x1f7端口写入读命令,0x20 mov dx,0x1f7mov al,0x20                    out dx,al;第4步:检测硬盘状态.not_ready:;同一端口,写时表示写入命令字,读时表示读入硬盘状态nopin al,dxand al,0x88	   ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙cmp al,0x08jnz .not_ready	   ;若未准备好,继续等。;第5步:从0x1f0端口读数据mov ax, dimov dx, 256mul dxmov cx, ax	   ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,; 共需di*512/2次,所以di*256mov dx, 0x1f0.go_on_read:in ax,dxmov [bx],axadd bx,2		  loop .go_on_readrettimes 510-($-$$) db 0db 0x55,0xaa

第一行 %include "boot.inc"​,我们在 include/boot.inc ​定义了起始扇区 lba 地址以及写入内存的地址

;-------------	 loader和kernel   ----------
LOADER_BASE_ADDR equ 0x900 ;写入内存的地址
LOADER_START_SECTOR equ 0x2 ;起始扇区lba地址

4--52 行和之前实验一样的部分我们先跳过不看,重点看读硬盘的过程,在 call 读取硬盘之前,先准备参数

image

然后再函数的开始,再次对这些重要的参数备份

image

然后就是按照我们上面的约定,一步一步从磁盘中读出数据,首先是先选择通道,往该通道的 sector count 寄存器中写入待操作的扇区数。

image

第二步,写 LBA 地址以及 device 寄存器,这里按照上面说的写即可,其中我们获取 LBA 各个部分的地址是通过右移 eax 获得的

image

第三步,往该通道上的 command 寄存器写入操作命令。

image

;第 4 步:检测硬盘状态,直到磁盘准备好了再继续,这里的 nop 相当于一个时间很短的 sleep

image

第五步:读取该通道上的 status 寄存器,判断硬盘工作是否完成。每次只能读两个字节,所以一个扇区要读 256 次,256*读取的扇区数即读取的总次数,读取之后写到内存中,不断循环即可,读完直接返回。

image

调用完函数之后直接 jmp 到 loader 即可

image

Loader

我们这里先写一个简单的 loader 验证跳转即可,在屏幕上显示 2 Loader​,代码很简单,没什么要说的

%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x00],'2'
mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4mov byte [gs:0x04],'L'
mov byte [gs:0x05],0xA4   mov byte [gs:0x06],'O'
mov byte [gs:0x07],0xA4mov byte [gs:0x08],'A'
mov byte [gs:0x09],0xA4mov byte [gs:0x0a],'D'
mov byte [gs:0x0b],0xA4mov byte [gs:0x0c],'E'
mov byte [gs:0x0d],0xA4mov byte [gs:0x0e],'R'
mov byte [gs:0x0f],0xA4jmp $		       ; 通过死循环使程序悬停在此

但是我们这里的编译命令有变化,要加一个 -I ​参数用于指定头文件位置,在用 dd 命令写入硬盘时也要加上 seek 命令用来写到 2 号扇区(0开始计算的情况下)

nasm -I include/ -o mbr.bin mbr.s
dd if=./mbr.bin of=../../../hd60M.img bs=512 count=1 conv=notruncnasm -I include/ -o loader.bin loader.s
dd if=./loader.bin of=../../../hd60M.img bs=512 count=1 seek=2 conv=notrunc

最后效果如下:

image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/863809.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正常运行 打包后提示某些属性或者类找不到

lombok 注解未正确加载,注意 idea属性修改

itextpdf 找出PDF中 文字的坐标

目录添加引用添加工具类调用 找到位置,签名的话见:https://www.cnblogs.com/vipsoft/p/18644127 新项目可以尝试一下 iText 7 , 我这边是老项目所以还是继续使用 iText 5,主打够用 iText 5 没有直接提供获取文本精确位置的功能。它只能提取文本内容,而文本位置通常需要通过…

Jmeter 进行websocket接口测试

什么是websocket协议? Websocket是基于tcp的一种全双通信协议,客户端与服务器之间通过websocket建立连接后,客户端和服务器之间会长时间保持连接状态(即长连接)。客户端可以向服务器发送数据,服务器也可以主动向客户端推送数据。与http协议不同的是http是tcp的单向通信协…

vscode下载vetur和vue-helper插件之后删除键(backspace)失效

最近我在学习前端的过程中,使用vscode下载的vue的插件:vetur和vue-helper这两个 但随后在写代码的时候发现删除键(backspace)不能使用,其他键都能正常使用,也可以用鼠标选中右键剪切/删除 最后发现是上面的插件会占用backspace按键作为插件的功能键 解决方法 点击左上角—…

使用 C# WPF 根据 SEGY 数据绘制二维地震图

一、引言 在地震勘探领域,SEGY(Society of Exploration Geophysicists Y-data)文件格式是常见的地震数据存储格式。对于地震数据的可视化,通常会将 SEGY 文件中的振幅数据通过图像进行展示,以便进行分析。本文将介绍如何使用 C# WPF 应用程序绘制基于 SEGY 数据的二维地震…

为什么浏览器打开的网页默认是英文显示?

最近在学习的时候遇到的问题,打开一些软件的官网默认都是英文(即使这些网站是国内做的)。 其中一个原因是浏览器的默认设置语言是英文 修改浏览器默认语言 以Google Chrome浏览器为例: 右上角三个点—>设置 左边找到“语言” 将中文置顶

LeetCode 762[二进制表示中质数个计算置位]

LeetCode 762[二进制表示中质数个计算置位]题目 链接 LeetCode 762[二进制表示中质数个计算置位] 详情实例提示题解 思路 两个条件: 1、二进制位为1 2、满足条件1的个数为质数首先 for 循环遍历区间for (int i = left; i < right + 1; i++){int iCount = 0;//二进制位为1的…

[Web Fronted] 前端框架: React

序部分开源项目是基于 Web 前端框架 React 构建的,有必要了解一二。避免一脸懵逼,不知道怎么修改相关代码和配置概述: React React 的简介React 起源于 Facebook 的内部项目因为该公司对市场上所有 JavaScript MVC 框架 都不满意,就决定自己写一套,用来架设 Instagram 的网…

15C++循环结构-while循环(2)——教学

1、while语句的应用; 2、双精度实数double及科学计数法; 3、分数化为小数一、while语句的应用 (第44课 角谷猜想)参考视频1 问题:对于每一个正整数,如果它是奇数,则对它乘3再加1,如果它是偶数,则对它除以2,如此循环,最终都能够得到1,这就是由日本数学家角谷静夫发现的…

Xshell 8 Build 0065中文免安装绿色版

前言 Xshell8是一个非常受欢迎的远程连接管理软件,它的界面简单易懂,用起来特别方便。能支持好多种连接方式,比如SSH1、SSH2、SFTP、TELNET等等,还有串行协议和其他一些高级功能,基本上你想连什么都能满足。而且,它还支持好多种不同的终端类型,比如VT100、VT220、XTERM、…

大语言模型提示技巧(四)-文本概括

文本概括是大语言模型的常用功能之一,我们总结一段文字、一篇文章的主要内容,一篇论文的摘要,甚至一本书的简介都属于文本概括的范畴。文本概括是大语言模型的常用功能之一,我们总结一段文字、一篇文章的主要内容,一篇论文的摘要,甚至一本书的简介都属于文本概括的范畴。…