[3.2] 操作系统学习 (完善 MBR,进入 loader)

·

4 min read

查看本系列文章目录

下列内容可能有出错的地方, 毕竟只是个人理解, 有问题评论区🥲🥲

本节将会彻底完善 mbr 扇区,进入 loader

硬盘介绍

现在硬盘大多分为 2 类

  • 固态硬盘
  • 机械硬盘

我们主要讲机械硬盘

机械硬盘其实就像唱片机一样, 一个磁头不断的在磁盘上读取数据

一个机械硬盘由许多的磁头和磁盘组成

1.png

而每一个磁盘的构造都是一样的

2.png

我们的 mbr 扇区就是里面最开始的扇区

这是一个简单的磁盘运行过程图

3.png

我们可以用 CHS 和 LBA 的方式来表示每一个扇区

CHS 就是以硬盘的逻辑来表示的,也就是"柱面:磁头:扇区", 一个扇区的东西为 512 字节; LBA 也就是以人的逻辑来表示扇区的, LBA 分 2 种

  • LBA28 : 用 28比特(3.5 字节)来表示一个扇区都地址, 寻址范围为268435456个扇区, 也就是 128GB
  • LBA48 : 用 48比特(6 字节)来表示一个扇区都地址, 寻址范围为281474976710656个扇区, 也就是 128PB

    那么CPU 要怎么操作硬盘呢? 当然当然是通过 IO 接口了, 有一种叫硬盘控制器的东西,

它和硬盘的关系就和 显卡和显示器 一样, CPU 通过控制硬盘控制器来操作键盘, 这个接口被称为集成设备电路(IDE), 后来基于 IDE 就出现了 ATA全球硬盘标准,但是大家还是习惯叫成 IDE 硬盘, 后来又出现了 SATA硬盘串行接口, 而以前的 ATA 接口也改名为PATA

SATA想要连接几个硬盘都可以, 这完全取决于主板的能力, 而 PATA 只能连接 2 个硬盘, 主盘,和从盘.


操作硬盘

操作硬盘主要就是通过 IO 接口

我们来看看一些需要用到的端口

4.png

我们看这个表里的命令是分Primary 通道Secondary 通道的, 这是为什么呢?

这是因为以前的主板只能接 4 个IDE硬盘, 4个硬盘需要 2 个硬盘控制器, 他们一个叫IDE0, 一个叫 IDE1, 为了区分他们, 所以IDE0 是叫Primary 通道, 而 IDE1 叫Secondary 通道

我们输入不同的命令也就代表不同的通道, 这样我们就可以操作不同的硬盘了

在表中我们可以看到一些端口在读和写的时候用处是不同的, 这是因为以前寄存器是很宝贵的, 所以能少用一个就一个, 也就变成了一个寄存器多个功能的局面

我们要重点看一下 0x1f6 这个端口, 因为一个通道里可能会连接 2 个硬盘(主盘,从盘)所以我们要用这个端口指明要操作哪个硬盘, 这个寄存器是 8 位的,我们在第 4 位用 0,1指明是主盘还是从盘, 那么剩下 7 位是一些硬盘的信息杂项

5.png

还有就是在 读操作(注意) 的0x1f7 的 Status 的 8 位寄存器, 里面储存了硬盘的状态信息

6.png

在写操作 的0x1f7 这是 cmd 寄存器, 我们可以通过在里面写目录来控制硬盘

我们要使用的主要指令有

  • 0xEC : 硬盘识别
  • 0x20 : 读扇区
  • 0x30 : 写扇区

我们是先往 0x1f7 写入命令,确定操作(读,写...),如果是读操作, 那 0x1f7 就变成status

通常的操作硬盘方法

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

我们获取硬盘准备好的数据的方法

当我们操作好硬盘后, 就要取出我们要的数据

通常的方法如下:

  • 无条件传送 : 适用于随时准备好数据的设备, 如内存, CPU随时拿都没问题
  • 查询传送 : 适用于速度慢的设备, 如硬盘 , CPU 获取数据时会先查看它工作状态, 如果 ok, CPU 才会读取
  • 中断传送 : 因为 查询传送 有一个缺陷, 就是 CPU 要不断查看设备的情况, 这样就很浪费资源, 所以工程师们发明了 中断传送 ,这样当设备准备好时就触发中断来通知 CPU 可以读取了
  • 直接存储器存取(DMA) : 在中断中,CPU 要压找, 执行传输指令, 最后还要恢复, 这样也会浪费 CPU 资源, DMA就解决了这一问题, 它可以直接在内存中拿数据, 需要 DMA 控制器实现
  • I/O 处理机传送 : 就是 DMA 的超强升级版, 引入了I/O处理机(替代 DMA 控制器),它专门用于处理 IO,并且它其实是一种处理器,只不过用的是另一套擅长 IO 的指令系统,随时可以处理数据

让 MBR 使用硬盘

我们将在 mbr 中使用 查询传送 的方法, 因为其他方式对我们来说太难理解了

直接看代码

; 文件位置 ./mbr.S
; <<操作系统真象还原>>代码
;主引导程序 
;------------------------------------------------------------
%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

   ; 输出字符串: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
      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
   db 0x55,0xaa
; 文件位置 ./include/boot.inc
LOADER_BASE_ADDR equ 0x900 
LOADER_START_SECTOR equ 0x2

运行这个代码, 就会读0x2(LBA)扇区, 并加载到0x900, 最后跳转到0x900, 结束 mbr, 进入 loader

因为我们还没 loader,所以随便写一个来测试我们的 mbr

; 文件位置 ./loader.S
; <<操作系统真象还原>>代码
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR

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

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

mov byte [gs:0x04],'L'
mov byte [gs:0x05],0xA4   

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

mov byte [gs:0x08],'A'
mov byte [gs:0x09],0xA4

mov byte [gs:0x0a],'D'
mov byte [gs:0x0b],0xA4

mov byte [gs:0x0c],'E'
mov byte [gs:0x0d],0xA4

mov byte [gs:0x0e],'R'
mov byte [gs:0x0f],0xA4

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

编译:

nasm -I ./include mbr.S
nasm -I ./include loader.S
# -I 指定 inc 包括目录
dd if=./mbr of=/home/fengzi/WorkSpace/Study_OS/c.img bs=512 count=1 seek=0 conv=notrunc
# seek可以指扇区序号
dd if=./loader of=/home/fengzi/WorkSpace/Study_OS/c.img bs=512 count=1 seek=2 conv=notrunc
# 这里是吧 loader 储存到第 2 个扇区

运行图:

7.png