当前位置: 首页 > ai >正文

从硬盘加载bootloader(setup)

从硬盘加载bootloader(setup)

首先明确一下内核启动的过程:
我们要写的代码共三个,分别为boot,bootloader(setup)以及内核代码。我们将这些代码编译完成写入硬盘之后,就要加载到内存中进行执行了。

加载过程是这样的:首先有BIOS将boot(也就是MBR主引导记录)加载到内存中,但是BIOS直接加载的话,在内存当中我们只有一个扇区的大小,也就是512字节,这是不足以我们进行加载内核的准备操作的,所以我们需要更大的内存空间。在前面也给过的内存布局图可以看到内存空闲位置。image-20250412104004378

那么在里面的两个可用区域就是我们扩展的目标,只要在boot中将bootloader(setup)加载入内存的可用位置,然后跳转到该位置执行,就摆脱了512字节的限制。

在摆脱限制之后,我们就可以做一系列加载内核的准备操作并用加载bootloader(setup)类似的操作将内核加载入内存并执行了,所以bootloader(setup)也可以看做是内核的安装程序。

然后来讲解一下怎么加载bootloader(setup),也就是怎么从硬盘将该内容加载到内存当中。

我们想要将硬盘当中的内容加载到内存中,首先要知道这一部分内容在硬盘的什么位置,这种情况下应当有一种寻址方式,即按照标号的形式标记硬盘当中的每一个扇区,使得我们有方法指定任意的扇区并加载内容。

在磁盘当中寻址有三种方法,即CHS,lba28以及lba48。(讲述不清楚的部分可以去B站或网络自行了解)
CHS即:Cylinder:圆柱,Head:磁头,Sector:扇区

因为在早期的存储方式,即软盘是圆片形状的,在上下两面都可以写数据,因此划分为圆柱标记上下两面;磁头记录的是磁道,由外而内在圆面上划分79个同心圆,从而将软盘划分为80个圆环,称为磁道;然后过圆心画直线将圆等分为18份,那么每个圆环(磁道)也被分为18份,磁道上的每一份称为一个扇区,每个扇区存储的容量一致为512字节。

所以一张盘扇区共有2 * 80 * 18 = 2880个。(后面容量扩展了是怎么实现的我也不知道,可以自己看课了解一下)

如果我们将这2880个扇区以数组的方式理解并编号,即分为1-2880扇区,那么就是lba的编码方式。

lba28是使用28位二进制数表示逻辑块地址,每个对应一个扇区,共允许寻址228个扇区;lba48是使用48位二进制数表示逻辑块地址,允许寻址2⁴⁸个逻辑块。
这里只讲解也只使用lba-28。

但是现在还面临一个问题,早期寄存器都是8位的,也就是说对于28位长的指令,需要四个寄存器才能存储并使用(其实就是硬件接口,在汇编中可以视为寄存器)。

那么使用那些寄存器呢?如图:

image-20250513110852796

假设我们要寻址逻辑块地址0x1A2B3C4,其二进制为0001 1010 0010 1011 0011 1100 0100

分配到寄存器这样:

LBA Low(低八位):1100 0100

LBA Mid(中八位): 1011 0011

LBA High(高八位):1010 0010

最高的四位使用Device/Head寄存器中的0-3bits,即低四位存储;这样的话这个寄存器还剩下四位未使用,用于控制信息,Bits5,7通常保留或用于其他控制目的,即默认设置为1,Bit6表示使用LBA模式(区分CHS),所以也是1,Bit4用于选择主从设备,我们首先肯定是使用主设备的,所以是0,所以Device/Head寄存器中的高四位常为1110。

前面所述仅为寻址格式的部分,但是在实际中读写硬盘还有很多的内容,包括操作几个扇区,有没有准备好,有没有错误等。

前面寄存器的图片中,我们应当知道的是0x1F0是数据传输的位置,因为是16位的寄存器,所以每次可以传输两个字节,读取一个扇区需要循环读取256次。

识别我们要进行的操作是通过0x1F7实现的,

在读的过程中,0x1F7寄存器用于获取硬盘的状态。

写的过程中,根据我们向0x1F7写入的数据识别命令。

命令码:

  • 0x20 读
  • 0x30 写
  • 0x1C 获取硬盘信息

接下来从代码角度分析具体使用方法:

;boot.asm;位于0柱面0磁道1扇区
[ORG 0x7c00][SECTION .data]
BOOT_MAN_ADDR equ 0x500 ;BOOT_MAN_ADDR是变量名,equ是伪指令表示=,0x500是我们要加载setup的地址[SECTION .text]
[BITS 16]
global _start
_start:;设置屏幕为文本模式,清除屏幕mov ax,3int 0x10;将setup读入内存mov edi,BOOT_MAN_ADDR ;用edi存放地址,读到哪里mov ecx,1 ;从那个扇区开始读mov bl,2 ;读几个扇区call read_hd;跳转到setup位置mov si,jmp_to_setupcall printjmp BOOT_MAN_ADDR;主函数逻辑到这里就结束了,后面是实现用到的函数read_hd:; 从磁盘读取的函数; 0x1f2 指定读取或写入的扇区数mov dx,0x1f2mov al,blout dx,al;0x1f3 写入lba28的低八位(这里看不懂的话自己了解下ECX,CX,CL与CH的关系inc dx ;inc是+1指令mov al,clout dx,al;0x1f4,中八位inc dxmov al,chout dx,al;0x1f5 inc dxshr ecx,16 ;右移以操作八位mov al,clout dx,al;0x1f6,低四位是地址,高四位为1110inc dxshr ecx,8 ;将操作数移到cl上,用ch是可行的,我也不理解为什么要弄到cl上,问就是cl更方便,但从代码角度讲确实麻烦了,可能是硬件问题之类的and cl,0b1111 ;高位自动补0mov al,ob1110_0000or al,clout dx,al;0xaf7,状态或命令端口inc dxmov al,0x20 ;读指令out dx,al;设置loop次数,读多少个扇区就loop多少次mov cl,bl
.start_read:push cx ;保存loop次数,防止被修改call .wait_hd_preparecall read_hd_datapop cx ;恢复loop次数loop .start_read.return: ret; 一直等待到硬盘状态为:不繁忙且数据已经准备好
; 也就是第7位是0,第3位是1,第0位为0
.wait_hd_prepare:mov dx,0x1f7.check:in al,dxand al,0b1000_1000cmp al,0b0000_1000jnz .checkret; 读硬盘256次
read_hd_data:mov da,0x1f0mov cx,256.read_word:in ax,dxmov [edi],axadd edi,2loop .read_wordret; print 函数
print:mov ah, 0x0emov bh, 0mov bl, 0x01
.loop:mov al, [si]cmp al, 0jz .doneint 0x10inc sijmp .loop
.done:retjmp_to_setup:db "jump_to_setup...",10,13,0times 510- ($-$$) db 0
db 0x55,0xaa

http://www.xdnf.cn/news/5848.html

相关文章:

  • 仿射密码的加密与解密
  • LlamaIndex 第八篇 MilvusVectorStore
  • 【图像处理基石】什么是油画感?
  • rocketMq实例
  • Java Spring MVC -01
  • Feign+Resilience4j实现微服务熔断机制:原理与实战
  • spark Mysql数据库配置
  • 百度导航广告“焊死”东鹏特饮:商业底线失守,用户安全成隐忧
  • YOLO11解决方案之物体模糊探索
  • 【自学30天掌握AI开发】第1天 - 人工智能与大语言模型基础
  • MySQL数据库——视图
  • JavaWeb 开发的核心基础知识
  • Stapi知识框架
  • ubuntu---100条常用命令
  • C++GO语言微服务之数据卷实践
  • 分式注记种表达方式arcgis
  • 大语言模型RLHF训练框架全景解析:OpenRLHF、verl、LLaMA-Factory与SWIFT深度对比
  • 华为海思系列----昇腾张量编译器(ATC)模型转换工具----入门级使用指南(LINUX版)
  • AD PCB布局时常用的操作命令
  • Python作业练习2
  • Go语言——docker-compose部署etcd以及go使用其服务注册
  • Spark处理过程—转换算子
  • 0.66kV0.69kV接地电阻柜常规配置单
  • 仓颉Magic亮相GOSIM AI Paris 2025:掀起开源AI框架新热潮
  • 裸金属服务器 VS 传统物理机
  • 鸿蒙next播放B站视频横屏后的问题
  • Linux之进程控制
  • 【Linux网络】HTTPS
  • k8s v1.26 实战csi-nfs 部署
  • 深度剖析:Vue2 项目兼容第三方库模块格式的终极解决方案