让MBR使用硬盘

前提知识:

BIOS中断

BIOS 和 DOS 都是存在于实模式下的程序,由它们建立的中断调用都是建立在中断向量表(Interrupt Vector Table,IVT)中的。它们都是通过软中断指令 int 中断号来调用的。中断向量表中第 0H~1FH 项是 BIOS 中断。,0x20~0x27 是 DOS 中断。而 Linux 内核是在进入保护模式后才建立中断例程的。

中断向量表中的中断例程是由 BIOS 建立的,它从物理内存地址 0x0000 处初始化并在中断向量表中添加各种处理例程。

硬件自己的功能调用例程及初始化代码就存放在这 ROM中。根据规范,第 1 个内存单元的内容是 0x55,第 2 个存储单元是 0xAA,第 3 个存储单位是该 rom 中以512 字节为单位的代码长度。从第 4 个存储单元起就是实际代码了

硬盘扇区表示

描述 0 盘 0 道 1 扇区用的便是其中的一种:CHS 方法,即柱面 Cylinder 磁头 Header 扇区 Sector(另外一种是 LBA 方式,暂不关心),“0 盘”说的是 0 磁头,因为一张盘是有上下两个盘面的,一个盘面上对应一个磁头,所以用磁头 Header 来表示盘面。“0 道”是指 0 柱面,柱面 Cylinder指的是所有盘面上、编号相同的磁道的集合,形象一点描述就是把很多环叠摞在一起的样子,组合在一起之后是一个立体的管状。“1 扇区”才是我们要解释的部分,将磁道等距划分成一段段的小区间,由于磁道是圆形的,确切地说是圆环,这些被划分出来的小区间便是扇形,所以称为扇区。在 CHS 方式中扇区的编号是从 1 开始的,不是 0,不是 0,原谅我说了两次,良苦用心你懂的,所以 0 盘 0 道 1 扇区其实就相当于 0 盘 0 道 0 扇区,它就是磁盘上最开始的那个扇区。

而LBA 方式中,扇区编号是从 0 开始的。LBA 的定义,是一种逻辑上为扇区址的方法,全称为逻辑块地址(Logical Block Address)。

SSD固态硬盘控制器管理LBA到物理存储的映射关系,并负责数据的读取和写入。它使用逻辑地址映射到闪存芯片中的特定页或块,而不是基于CHS模式的物理位置。
在这里插入图片描述

访问外设有两种方式:
(1)内存映射:通过地址总线将外设自己的内存映射到某个内存区域(并不是映射到主板上插的内存条中)。
(2)端口操作:外设都有自己的控制器,控制器上有寄存器,这些寄存器就是所谓的端口,通过 in/out指令读写端口来访问硬件的内存。

硬盘的随机存取是靠磁头臂不断移动实现的,磁头臂移动到目标位置的时间称为寻道时间,如果存储的数据不连续,这一块那一片的,磁头就得不断调整位置,这是机械式硬盘不可避免的,这便是硬盘的瓶颈所在,所以一般的硬盘都将寻道时间作为重要参数。

在这里插入图片描述

在这里插入图片描述

(1)先选择通道,往该通道的 sector count 寄存器中写入待操作的扇区数。
(2)往该通道上的三个 LBA 寄存器写入扇区起始地址的低 24 位。
(3)往 device 寄存器中写入 LBA 地址的 24~27 位,并置第 6 位为 1,使其为 LBA 模式,设置第 4位,选择操作的硬盘(master 硬盘或 slave 硬盘)。
(4)往该通道上的 command 寄存器写入操作命令。
(5)读取该通道上的 status 寄存器,判断硬盘工作是否完成。
(6)如果以上步骤是读硬盘,进入下一个步骤。否则,完工。
(7)将硬盘数据读出。

一旦 command 寄存器被写入后,硬盘就开始工作

32位CPU在实模式下使用32位寄存器

32 位处理器可以执行16 位的程序,包括实模式和16 位保护模式。为此,在16 位模式下,处理器把所有指令都看成是16 位的。举个例子,机器指令码0x40 在16 位模式下的含义是 inc ax
当处理器在16 位模式下运行时,也可以使用32 位的寄存器,执行32 位的运算。为此,必须使用指令前缀0x66 来临时改变这种默认状态,因为同一个指令码,在16 位模式下和32 位模式下具有不同的解释。
因此,当处理器在16 位模式下运行时,机器指令码对应的指令不再是inc ax,而是 66 40对应的指令也不再是inc ax,而是inc eax

loader加载地址

我们的 MBR 受限于 512 字节大小的,在那么小的空间中,没法为内核准备好环境,更没法将内核成功加载到内存并运行。loader 在哪里?如何跳过去执行?这就是新款 MBR 的使命,简而言之就是,负责从硬盘上把 loader 加载到内存,并将接力棒交给它。

MBR 从第 2 扇区(设定)中把它读出来。读出来放到哪里呢?

loader 加载到内存后不能被覆盖,尽量把 loader 放在低处,多留出一些空间给内核,作者将 loader 的加载地址选为 0x900。

代码附加详细解释

boot.inc

1 ;-------------loader 和 kernel-----------
2 LOADER_BASE_ADDR equ 0x900      ;宏定义
3 LOADER_START_SECTOR equ 0x2

主引导程序 :

;主引导程序 
;------------------------------------------------------------
%include "boot.inc" 
SECTION MBR vstart=0x7c00    ;告诉编译器,把我的起始地址编译为 0x7c00,段内偏移0x7c00     
   mov ax,cs      	;BIOS 是通过 jmp 0:0x7c00 跳转到 MBR 的,故cs 此时为 0
   mov ds,ax		;初始化
   mov es,ax
   mov ss,ax		;FS 和 GS 附加段寄存器是在 32 位CPU 中增加的
   mov fs,ax		;16位
   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

   ; 输出字符串:1 MBR
   mov byte [gs:0x00],'1'
   mov byte [gs:0x01],0xA4

   mov byte [gs:0x02],' '
   mov byte [gs:0x03],0xA4

   mov byte [gs:0x04],'M'
   mov byte [gs:0x05],0xA4	   ;A表示绿色背景闪烁,4表示前景色为红色

   mov byte [gs:0x06],'B'
   mov byte [gs:0x07],0xA4

   mov byte [gs:0x08],'R'
   mov byte [gs:0x09],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,out会修改al的值
      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	   ; 设置7~4位为1110,表示lba模式
      mov dx,0x1f6
      out dx,al

;第3步:向0x1f7command端口写入读命令,0x20,硬盘开始工作 
      mov dx,	0x1f7
      mov al,0x20                        
      out dx,al

;第4步:检测硬盘状态
 .not_ready:
      ;同一端口,写时表示写入命令字,读时表示读入硬盘状态
      nop				;为了增加延迟,目的是减少打扰硬盘的工作
      in al,dx			;将 Status 寄存器的值读入到 al 寄存器
      and al,0x88	   ;第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
      cmp al,0x08
      jnz .not_ready	   ;若未准备好,继续等。

;第5步:从0x1f0端口读数据
      mov ax, di		
      mov dx, 256		;256 超过了 8 位寄存器表示的范围
      mul dx
      mov cx, ax	   ; di为要读取的扇区数,一个扇区有512字节,每次读入一个字,
			   ; 共需di*512/2次,所以di*256
      mov dx, 0x1f0
  .go_on_read:
      in ax,dx
      mov [bx],ax	;bx 只会访问到 0~FFFFh 的偏移,此处加载的程序不能超过 64KB
      add bx,2		 
      loop .go_on_read
      ret

   times 510-($-$$) db 0
   db 0x55,0xaa

loader的测试程序:

%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR

; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x20],'2'
mov byte [gs:0x21],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色

mov byte [gs:0x22],' '
mov byte [gs:0x23],0xA4

mov byte [gs:0x24],'L'
mov byte [gs:0x25],0xA4   

mov byte [gs:0x26],'O'
mov byte [gs:0x27],0xA4

mov byte [gs:0x28],'A'
mov byte [gs:0x29],0xA4

mov byte [gs:0x2a],'D'
mov byte [gs:0x2b],0xA4

mov byte [gs:0x2c],'E'
mov byte [gs:0x2d],0xA4

mov byte [gs:0x2e],'R'
mov byte [gs:0x2f],0xA4

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

注意我们通过dd命令将loader的二进制文件放在2扇区

测试结果:
在这里插入图片描述


用于学习笔记:操作系统真象还原第三章

猜你喜欢

转载自blog.csdn.net/weixin_61631200/article/details/131407349