动手学操作系统(四、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   ; 设置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 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

相关文章

使用Java读取本地文件并转换为MultipartFile对象的方法

《使用Java读取本地文件并转换为MultipartFile对象的方法》在许多JavaWeb应用中,我们经常会遇到将本地文件上传至服务器或其他系统的需求,在这种场景下,MultipartFile对象非... 目录1. 基本需求2. 自定义 MultipartFile 类3. 实现代码4. 代码解析5. 自定

MySQL 数据库表操作完全指南:创建、读取、更新与删除实战

《MySQL数据库表操作完全指南:创建、读取、更新与删除实战》本文系统讲解MySQL表的增删查改(CURD)操作,涵盖创建、更新、查询、删除及插入查询结果,也是贯穿各类项目开发全流程的基础数据交互原... 目录mysql系列前言一、Create(创建)并插入数据1.1 单行数据 + 全列插入1.2 多行数据

SpringBoot加载profile全面解析

《SpringBoot加载profile全面解析》SpringBoot的Profile机制通过多配置文件和注解实现环境隔离,支持开发、测试、生产等不同环境的灵活配置切换,无需修改代码,关键点包括配置文... 目录题目详细答案什么是 Profile配置 Profile使用application-{profil

SpringBoot多环境配置数据读取方式

《SpringBoot多环境配置数据读取方式》SpringBoot通过环境隔离机制,支持properties/yaml/yml多格式配置,结合@Value、Environment和@Configura... 目录一、多环境配置的核心思路二、3种配置文件格式详解2.1 properties格式(传统格式)1.

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

Android Paging 分页加载库使用实践

《AndroidPaging分页加载库使用实践》AndroidPaging库是Jetpack组件的一部分,它提供了一套完整的解决方案来处理大型数据集的分页加载,本文将深入探讨Paging库... 目录前言一、Paging 库概述二、Paging 3 核心组件1. PagingSource2. Pager3.

从入门到精通详解LangChain加载HTML内容的全攻略

《从入门到精通详解LangChain加载HTML内容的全攻略》这篇文章主要为大家详细介绍了如何用LangChain优雅地处理HTML内容,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录引言:当大语言模型遇见html一、HTML加载器为什么需要专门的HTML加载器核心加载器对比表二

Python使用openpyxl读取Excel的操作详解

《Python使用openpyxl读取Excel的操作详解》本文介绍了使用Python的openpyxl库进行Excel文件的创建、读写、数据操作、工作簿与工作表管理,包括创建工作簿、加载工作簿、操作... 目录1 概述1.1 图示1.2 安装第三方库2 工作簿 workbook2.1 创建:Workboo

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直