动手学操作系统(四、MBR读取硬盘加载Loader)

2024-05-30 04:04

本文主要是介绍动手学操作系统(四、MBR读取硬盘加载Loader),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

动手学操作系统(四、MBR读取硬盘加载Loader)

在上一节中,我们学习了使用MBR来直接控制显卡进行显示,在这一节中我们学习如何让MBR来操作硬盘,加载Loader来完成操作系统的后续启动过程。

文章目录

  • 动手学操作系统(四、MBR读取硬盘加载Loader)
    • 1. 为什么需要Lodaer?
    • 2. 硬盘介绍
    • 3. MBR读取硬盘加载Loader
    • Reference

1. 为什么需要Lodaer?

我们的MBR受限于512字节的大小,在如此小的空间中,我们无法为操作系统的内核准备好完整的运行环境,更没办法将内核成功加载到内存并运行,所以我们需要另一个程序来完成初始化环境及加载内核的工作,这个程序就被称为Loader,简而言之就是,MBR负责从硬盘上把Loader加载到内存中,然后将控制权交给Loader,在Loader中要定义一些数据结构,这些数据结构在将来的内核中还是需要使用的,所以将Loader加载到内存中时不能进行覆盖。我们尽量将Loader放置在低地址处,多留一些空间给内核。

2. 硬盘介绍

硬盘属于存储介质,在硬盘的发展历史中,随机存取具有划时代的意义,程序中的算法不用再考虑存储时间,访问任意数据所用的时间几乎是相等的,这一改之前的存储设备其存取时间呈线性的历史。

为了让硬盘工作,我们需要通过读写硬盘控制器的端口,端口就是位于IO控制器上的寄存器,这里就算指的是硬盘控制器上的寄存器。硬盘的控制遵循标准ATA(Advanced Technology Attachment),ATA的标准有些冗长,这里只简单介绍我们需要使用到的部分,如下图所示

Image

3. MBR读取硬盘加载Loader

由于MBR占据了硬盘的第0扇区,则第1扇区是空闲的,为了安全性,间隔1个扇区,将Loader防止子地2扇区,参考实模式下的内存布局,

Image

0x500~0x7BFF0x7E00~0x9FBFF这两段内存区域都是可以使用的,为了给内核留足够的空间,这里将Loader加载到内存地址的0x900处(当然,你也可以放置在别处)。

接下来就编写程序来进行加载,在编写程序之前,我们先优化一下我们的工程路径结构,将工程路径优化为如下,在src路径下新增了一个boot目录用于管理我们boot过程中的代码文件,然后在boot目录中增加一个lib目录用于管理我的一些配置文件比如boot.inc,然后新建一个loader.S用于编写Loader的代码,bin目录用于存放编译的结果,整个工程的目录如下所示,当然你也可以按照你喜欢的方式来进行组织

Image

分别写入以下的内容

mbr.S

; ~/d2los/src/boot/mbr.S
; 主引导程序 
; LOADER_BASE_ADDR equ 0xA000 
; LOADER_START_SECTOR equ 0x2
;------------------------------------------------------------
%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; 输出字符串: Hello Worldmov byte [gs:0x00],'H'mov byte [gs:0x01],0xA4     ; A表示绿色背景,4表示前景色为红色mov byte [gs:0x02],'e'mov byte [gs:0x03],0xA4mov byte [gs:0x04],'l'mov byte [gs:0x05],0xA4mov byte [gs:0x06],'l'mov byte [gs:0x07],0xA4mov byte [gs:0x08],'o'mov byte [gs:0x09],0xA4mov byte [gs:0x0A],' 'mov byte [gs:0x0B],0xA4mov byte [gs:0x0C],'W'mov byte [gs:0x0D],0xA4mov byte [gs:0x0E],'o'mov byte [gs:0x0F],0xA4mov byte [gs:0x10],'r'mov byte [gs:0x11],0xA4mov byte [gs:0x12],'l'mov byte [gs:0x13],0xA4mov byte [gs:0x14],'d'mov byte [gs:0x15],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   ; 设置74位为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 0   ; 剩余部分填充0db 0x55,0xaa

boot.inc

; ~/d2los/src/boot/lib/boot.inc
;-------------	 loader和kernel   ----------
LOADER_BASE_ADDR equ 0x900 
LOADER_START_SECTOR equ 0x2

loader.S

; ~/d2los/src/boot/loader.S
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR; 清屏
; 利用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],'H'
mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色
mov byte [gs:0x02],'i'
mov byte [gs:0x03],0xA4
mov byte [gs:0x04],' '
mov byte [gs:0x05],0xA4   
mov byte [gs:0x06],'L'
mov byte [gs:0x07],0xA4
mov byte [gs:0x08],'o'
mov byte [gs:0x09],0xA4
mov byte [gs:0x0a],'a'
mov byte [gs:0x0b],0xA4
mov byte [gs:0x0c],'d'
mov byte [gs:0x0d],0xA4
mov byte [gs:0x0e],'e'
mov byte [gs:0x0f],0xA4
mov byte [gs:0x10],'r'
mov byte [gs:0x11],0xA4jmp $		       ; 通过死循环使程序悬停在此

然后编译

cd ~/d2los
nasm -I src/boot/lib -o bin/mbr.bin src/boot/mbr.S
nasm -I src/boot/lib -o bin/loader.bin src/boot/loader.S

然后装载到硬盘

cd ~/d2los
dd if=./bin/mbr.bin of=~/bochs/bin/hardisk60MB.img bs=512 count=1 seek=0 conv=notrunc
dd if=./bin/loader.bin of=/home/sjh/bochs/bin/hardisk60MB.img bs=512 count=1 seek=2 conv=notrunc

这里的关键字解释如下:

  • bs=512: 设置块大小(block size)为512字节。这意味着 dd 命令在处理数据时会以512字节为单位进行读写。

  • count=1: 指定要读取和写入的块的数量为1。这意味着 dd 命令会读取1个512字节的块并将其写入输出文件。

  • seek=2: 在输出文件中跳过2个块(每个块大小为 bs 设置的512字节),然后开始写入。就是将loader装载自硬盘的第二个扇区,如果seek=0就是将文件装载到第0个扇区

然后运行

~/bochs/bin/bochs -f ~/bochs/bin/bochsrc.disk 

如果写入有误,可以使用命令

dd if=/dev/zero of=~/bochs/bin/hardisk60MB.img bs=1M count=60

来清空虚拟硬盘。

效果如下

Image

然后我们详细解释一下代码,在boot.inc文件中,我们定义了两个宏分别是LOADER_BASE_ADDRLOADER_START_SECTORequ是用于定义宏的关键字类似于C语言中的#define

Reference

[1]《一个64位操作系统的设计与实现》
[2]《操作系统真象还原》

这篇关于动手学操作系统(四、MBR读取硬盘加载Loader)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1015528

相关文章

最好用的WPF加载动画功能

《最好用的WPF加载动画功能》当开发应用程序时,提供良好的用户体验(UX)是至关重要的,加载动画作为一种有效的沟通工具,它不仅能告知用户系统正在工作,还能够通过视觉上的吸引力来增强整体用户体验,本文给... 目录前言需求分析高级用法综合案例总结最后前言当开发应用程序时,提供良好的用户体验(UX)是至关重要

SpringBoot使用Apache POI库读取Excel文件的操作详解

《SpringBoot使用ApachePOI库读取Excel文件的操作详解》在日常开发中,我们经常需要处理Excel文件中的数据,无论是从数据库导入数据、处理数据报表,还是批量生成数据,都可能会遇到... 目录项目背景依赖导入读取Excel模板的实现代码实现代码解析ExcelDemoInfoDTO 数据传输

Python读取TIF文件的两种方法实现

《Python读取TIF文件的两种方法实现》本文主要介绍了Python读取TIF文件的两种方法实现,包括使用tifffile库和Pillow库逐帧读取TIFF文件,具有一定的参考价值,感兴趣的可以了解... 目录方法 1:使用 tifffile 逐帧读取安装 tifffile:逐帧读取代码:方法 2:使用

MyBatis延迟加载的处理方案

《MyBatis延迟加载的处理方案》MyBatis支持延迟加载(LazyLoading),允许在需要数据时才从数据库加载,而不是在查询结果第一次返回时就立即加载所有数据,延迟加载的核心思想是,将关联对... 目录MyBATis如何处理延迟加载?延迟加载的原理1. 开启延迟加载2. 延迟加载的配置2.1 使用

Android WebView的加载超时处理方案

《AndroidWebView的加载超时处理方案》在Android开发中,WebView是一个常用的组件,用于在应用中嵌入网页,然而,当网络状况不佳或页面加载过慢时,用户可能会遇到加载超时的问题,本... 目录引言一、WebView加载超时的原因二、加载超时处理方案1. 使用Handler和Timer进行超

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

Linux操作系统 初识

在认识操作系统之前,我们首先来了解一下计算机的发展: 计算机的发展 世界上第一台计算机名叫埃尼阿克,诞生在1945年2月14日,用于军事用途。 后来因为计算机的优势和潜力巨大,计算机开始飞速发展,并产生了一个当时一直有效的定律:摩尔定律--当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍。 那么相应的,计算机就会变得越来越快,越来越小型化。

matlab读取NC文件(含group)

matlab读取NC文件(含group): NC文件数据结构: 代码: % 打开 NetCDF 文件filename = 'your_file.nc'; % 替换为你的文件名% 使用 netcdf.open 函数打开文件ncid = netcdf.open(filename, 'NC_NOWRITE');% 查看文件中的组% 假设我们想读取名为 "group1" 的组groupName

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA

argodb自定义函数读取hdfs文件的注意点,避免FileSystem已关闭异常

一、问题描述 一位同学反馈,他写的argo存过中调用了一个自定义函数,函数会加载hdfs上的一个文件,但有些节点会报FileSystem closed异常,同时有时任务会成功,有时会失败。 二、问题分析 argodb的计算引擎是基于spark的定制化引擎,对于自定义函数的调用跟hive on spark的是一致的。udf要通过反射生成实例,然后迭代调用evaluate。通过代码分析,udf在