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

news2024/11/17 7:35:51

动手学操作系统(四、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,ax
   mov es,ax
   mov ss,ax
   mov fs,ax
   mov sp,0x7c00
   mov ax,0xb800
   mov gs,ax

; 清屏
; 利用0x06号功能,上卷全部行,可以清屏。
; -----------------------------------------------------------
;INT 0x10   功能号:0x06    功能描述:上卷窗口
; ------------------------------------------------------
; 输入:
; AH 功能号= 0x06
; AL = 上卷的行数(如果为0,表示全部)
; BH = 上卷行属性
; (CL,CH) = 窗口左上角的(X,Y)位置
; (DL,DH) = 窗口右下角的(X,Y)位置
; 无返回值:
   mov     ax, 0600h
   mov     bx, 0700h
   mov     cx, 0                   ; 左上角: (0, 0)
   mov     dx, 184fh               ; 右下角: (80, 25),
                                   ; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
                                   ; 下标从0开始,所以0x18=24,0x4f=79
   int     10h                     ; int 10h

   ; 输出字符串: Hello World
   mov byte [gs:0x00],'H'
   mov byte [gs:0x01],0xA4     ; A表示绿色背景,4表示前景色为红色
   mov byte [gs:0x02],'e'
   mov byte [gs:0x03],0xA4
   mov byte [gs:0x04],'l'
   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],' '
   mov byte [gs:0x0B],0xA4

   mov byte [gs:0x0C],'W'
   mov byte [gs:0x0D],0xA4
   mov byte [gs:0x0E],'o'
   mov byte [gs:0x0F],0xA4
   mov byte [gs:0x10],'r'
   mov byte [gs:0x11],0xA4
   mov byte [gs:0x12],'l'
   mov byte [gs:0x13],0xA4
   mov byte [gs:0x14],'d'
   mov byte [gs:0x15],0xA4
     
   mov 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    ;保存eax
      mov di,cx      ;保存cx
;读入硬盘:
;1:设置要读取的扇区数
      mov dx,0x1f2
      mov al,cl
      out dx,al            ;读取的扇区数

      mov eax,esi   ;恢复ax

;2:将LBA地址存入0x1f3 ~ 0x1f6端口

      ;LBA地址7~0位写入端口0x1f3
      mov dx,0x1f3                       
      out dx,al                          

      ;LBA地址15~8位写入端口0x1f4
      mov cl,8
      shr eax,cl
      mov dx,0x1f4
      out dx,al

      ;LBA地址23~16位写入端口0x1f5
      shr eax,cl
      mov dx,0x1f5
      out dx,al

      shr eax,cl
      and al,0x0f   ;lba第24~27位
      or al,0xe0   ; 设置74位为1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;3:0x1f7端口写入读取命令0x20 
      mov dx,0x1f7
      mov al,0x20                        
      out dx,al

;4:检查硬盘状态
  .not_ready:
      ;同一个端口,写时表示写入命令字,读时表示读取硬盘状态
      nop
      in al,dx
      and al,0x88   ;4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
      cmp al,0x08
      jnz .not_ready   ;如果未准备好,继续等待

;5:0x1f0端口读取数据
      mov ax, di
      mov dx, 256
      mul dx
      mov cx, ax   ; di为要读取的扇区数,一个扇区有512字节,每次读取一个字,
                   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax
      add bx,2     
      loop .go_on_read
      ret
   
   times 510-($-$$) db 0   ; 剩余部分填充0
   db 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, 0600h
   mov     bx, 0700h
   mov     cx, 0                   ; 左上角: (0, 0)
   mov     dx, 184fh               ; 右下角: (80, 25),
                                   ; 因为VGA文本模式中,一行只能容纳80个字符,共25行。
                                   ; 下标从0开始,所以0x18=24,0x4f=79
   int     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],0xA4

jmp $		       ; 通过死循环使程序悬停在此

然后编译

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]《操作系统真象还原》

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OrangePi_Kunpeng_Pro开发板测验——性能巨人

文章目录 📑前言一、开箱初体验的愉悦二、产品规格概述三、前置工作四、性能测试4.1 CPU 性能测试4.1.1 单线程 CPU 测试4.1.2 多线程 CPU 测试 4.2 内存性能测试4.2.1 内存读写性能测试4.2.2 高负载内存测试 4.3 I/O 性能测试4.4 网络性能测试4.5 测试小结4.5.1 CP…

基于广义极大极小凹惩罚的心电信号降噪方法(MATLAB R2021B)

凸优化是数学最优化的一个子领域,研究定义于凸集中的凸函数最小化问题。由于心电信号降噪的过程可以理解为求信号的稀疏近似解,因此基于凸优化和稀疏性表达的去噪方法可用于心电信号处理。在凸优化的数学模型中,惩罚项的选取对最终结果会产生…

那些不起眼但很好玩的API合辑

那些不起眼但很好玩的API,为我们带来了许多出人意料的乐趣和惊喜。这些API可能看起来并不起眼,但它们却蕴含着无限的创意和趣味性。它们可以是一些小游戏API,让我们可以在闲暇时刻尽情娱乐;也可以是一些奇特的音乐API,…

gradio image 类型

3种类型,默认是 numpy.array numpy.array PIL.Image str file path. 互相转换 # 从路径到 numpy.ndarray import cv2 image_mask cv2.imread(imagePath) print(type(image_mask))# 从路径到 PIL.IMAGE from PIL import Image image_maskImage.open(imagePath) pri…

zynq PS端 GPIO

记录一下PS端 GPIO 1. GPIO 简介 ZYNQ PS 中包含一组丰富的外设,用于和外部设备进行通信。ZYNQ 的 IO 包括对外连接的 GPIO 和内部 PS 与 PL 通信的 AXIO。其中对外的 GPIO 又分为两种:MIO 和 EMIO。 MIO 和 EMIO 只是 GPIO 信号的两种接口&#xff0c…

wangEditor富文本实现自定义插入数据(手把手)

插件版本如下: vue2版本 “@wangeditor/editor”: “^5.1.23”, “@wangeditor/editor-for-vue”: “^1.0.2”, 文件截图如下: 一、plugins/index.js 入口文件,整合模块导出外部使用 import withMention from ./plugin import renderElemConf from ./render-elem import …

ES升级--04--SpringBoot整合Elasticsearch

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 SpringBoot整合Elasticsearch1.建立项目2.Maven 依赖[ES 官方网站:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/6.8/index.html](…

C++ vector 模拟实现

vector的底层也是一个动态数组,他与 string 的区别就是,string 是专门用来存储字符类数据的,为了兼容C语言,使用C语言的接口,在string的动态数组内都会都开一块空间用来存 \0 ,而vector则不会。 首先我们要…

水电收费远程抄表

1.前言:从传统到现代的改变 水电收费远程抄表,是科学技术在公共服务领域的一次重要运用,它改变了过去人力上门服务抄表的传统模式,提高了高效率,降低了偏差,为群众与企业带来了极大的便利。这种系统运用智…

富途面试题:用面向对象的思想设计人关门的场景

我工作两年后,有一次不成功的富途证券的面试,印象非常深刻,面试官提出了一个看似简单实则充满深意的问题:如何用面向对象的思想设计一个人关门的场景? 我当时是这样设计的,创建两个类:Person和D…

Linux命令那么多,先来一篇文件和目录管理命令!

💡本文建议大家收藏! 文件和目录管理命令 1. ls - 列出目录内容 ls命令是Linux中最常用的命令之一,用于列出目录中的文件和子目录。 ls显示当前目录下的所有文件和目录。 ls -l以长格式列出目录内容,显示文件权限、所有者、大…

云途探索——移动云云日志接入实践

目录 1 前言2 新手入门2.1 前置条件2.2. 配置采集对象及规则2.3 安装Agent2.4 日志管理 3 使用场景3.1 优势3.2 应用场景 4 总结 1 前言 随着人工智能、大数据、物联网以及云计算时代的到来,在日志数据量持续爆增、日志数据日益多样化的今天,传统日志服…

Java(六)——抽象类与接口

文章目录 抽象类和接口抽象类抽象类的概念抽象类的语法抽象类的特性抽象类的意义 接口接口的概念接口的语法接口的特性接口的使用实现多个接口接口与多态接口间的继承抽象类和接口的区别 抽象类和接口 抽象类 抽象类的概念 Java使用类实例化对象来描述现实生活中的实体&…

[数据集][图像分类]家庭场景下的家具分类数据集1010张101类别

数据集类型:图像分类用,不可用于目标检测无标注文件 数据集格式:仅仅包含jpg图片,每个类别文件夹下面存放着对应图片 图片数量(jpg文件个数):1010 分类类别数:101 类别名称:[“bath_bath”,“bath_heated_t…

java版CRM客户关系管理系统源码-CRM客户关系管理系统的技术架构与功能实现

CRM客户关系管理系统的技术架构与功能实现 一、引言 随着市场竞争的日益激烈,客户关系管理(CRM)已成为企业赢得市场、提升客户满意度、促 进业务增长的关键手段。本文旨在介绍一款先进的CRM客户关系管理系统的技术架构与功能实现&#xff0…

C++:类的内存分布

类的成员变量和方法是分开存储的,内存给类实例化出的对象开辟空间时只开辟成员变量所占用的空间。类中的所有方法(成员函数)都会放在代码区,所以类的大小一般只计算类中成员变量的对齐之后大小的综合(如果没有虚函数的…

Java设计模式 _行为型模式_观察者模式

一、观察者模式 1、观察者模式 观察者模式 ( Observer Pattern )是一种行为型模式。 常用于对象间存在一对多关系时,比如,当一个对象被修改时,需要自动通知它的依赖对象。 2、实现思路 (1)、定义被观察者的行为&…

ETLCloud中如何执行SQL脚本

SQL脚本 在数据库管理与数据分析的广阔领域中,SQL(Structured Query Language,结构化查询语言)脚本扮演着举足轻重的角色。作为一门专为关系型数据库设计的编程语言,SQL不仅能够执行数据的检索、更新、插入及删除等基…

算法与数据结构:二叉排序树与AVL树

ACM大牛带你玩转算法与数据结构-课程资料 本笔记属于船说系列课程之一,课程链接: 哔哩哔哩_bilibilihttps://www.bilibili.com/cheese/play/ep66799?csourceprivate_space_class_null&spm_id_from333.999.0.0 你也可以选择购买『船说系列课程-年度会…

便携式应急气象站:应急气象监测装备

TH-BQX5便携式应急气象站,作为现代气象监测的重要装备,以其独特的便携性、高效性和灵活性,在应急气象监测领域发挥着至关重要的作用。这类气象站不仅为灾害预警、环境保护、农业生产等多个领域提供了实时、准确的气象数据,还在突发…