第二步 完善MBR

admin2024-05-15  0

文章目录

  • 前言
  • 一、什么是MBR?
  • 二、我们需要什么样的MBR?
  • 三、设计我们的MBR!
    • 1、打印“1 MBR”
    • 2、加载次引导程序——loader
  • 四、实践检验!

前言

  在上一篇文章 第一步 从启动BIOS开始 中,我们介绍了BIOS系统,知道了它只负责做一些硬件检测和初始化的工作,然后控制权就到 MBR 手中了,那我们这一节就来介绍一下 MBR 是什么,又有什么作用叭!


一、什么是MBR?

  MBR 也叫做“ 主引导程序 ”,从名字不难看出,它是主要的引导程序。引导的程序就是“ 次引导程序 ”,可以叫做“ 内核加载器 ”,总的来说就是加载内核也就是操作系统的程序。

  也就是 MBR 并不加载操作系统,其原因在于有时候我们希望计算机可以在不同的分区使用不同的操作系统,所以 MBR 的工作就只能是挑选合适的“ 次引导程序 ”。至于BIOS为什么不直接完成挑选过程,原因很简单,因为空间太小不够用,心有余力不足。

二、我们需要什么样的MBR?

  知道了什么是 MBR 后,我们就来来编写属于我们的 MBR 了,首先我们先来看看真正的 MBR 是什么样的,如下所示:

  • 446字节的引导程序及参数;
  • 64字节的分区表;
  • 2字节的魔数,0x55和0xaa;

  这是真正的 MBR ,那我们的 MBR 并不需要和上述那样,因为我们注定只会加载我们自己以后编写的操作系统,因此我们不需要分区表,引导程序要实现的功能也仅仅是把“ 次引导程序 ”从磁盘加载到内存中,并跳转过去就可以了。

  但是为了方便我们看到结果,我们再在屏幕上输出一句话“1 MBR”,表示 MBR 被成功加载运行。

  解下来我们就要开始编写 MBR 了,如果你还不了解如何在屏幕输出内容请看x86 文本模式显示适配器,如果你还不知道如何操控硬盘,请看x86 汇编如何控制硬盘?。

三、设计我们的MBR!

  上一节我们已经讲了我们的 MBR 长什么样了,因此我们的程序的“ 主函数 ”就像下面展示的一样,非常简洁!

	mov eax, LOADER_START_SECTOR   ;待读入扇区的起始地址(0x2)
    mov bx, LOADER_BASE_ADDR       ;数据从硬盘读入后存放的地址(0x900)
    mov cx, 4                      ;待读入的扇区数目
                                   ;当前loader只有一点点大小,不过为了方便,直接设置为4
    call rd_disk_m_16              ;执行该函数在"16位模式下读硬盘"
    jmp LOADER_BASE_ADDR           ;跳转到LOADER程序

  可以看到我们将地址设置成了常量,因为以后还有非常多的常量,因此,我在code目录下新建目录include,并新建boot.inc文件,专门用来存储将来会用到的常量。
第二步 完善MBR,在这里插入图片描述,第1张

; 加载器在硬盘上的位置(LBA)
LOADER_START_SECTOR  equ 0x2

; 加载器加载到内存中的位置
LOADER_BASE_ADDR equ 0x900

  本节就一些关键步骤和代码做出解释说明,完整代码见文章末尾。

1、打印“1 MBR”

(1)清屏:

	mov ax, 0x0600   ;AL:上卷的行数(为0表示全部),AH:功能号,0x06表示清屏功能
    mov bx, 0x0700   ;上卷行属性
    mov cx, 0x0      ;左上角(0,0)
    mov dx, 0x184f   ;右下角(80,25),在VGA文本模式中,一行只能容纳80个字符,一共25行
    int 0x10         ;调用BIOS的视频服务中断

(2)打印:

	;0x1010 0100 -> 前景色(字的颜色)为红色,背景色为绿色,闪烁(字闪烁)
    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
    mov byte [gs:0x06], 'B'
    mov byte [gs:0x07], 0xA4
    mov byte [gs:0x08], 'R'
    mov byte [gs:0x09], 0xA4

2、加载次引导程序——loader

(1)第1步:设置带读入的扇区数

	mov dx, 0x1f2
    mov al,cl      ;使用cx寄存器存储待读入的扇区数
    out dx,al

(2)第2步:设置LBA地址

	;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

    ;设置device
    shr eax, cl
    and al, 0x0f   ;设置LB地址24~27位
    or al, 0xe0    ;0xe0 -> 11100000 ,设置7~4位为1110,表示为LBA模式
    mov dx, 0x1f6
    out dx, al     ;将配置信息写入端口0x1f6

(3)第3步:发送读命令

	mov dx, 0x1f7
    mov al, 0x20
    out dx, al

(4)第4步:检测硬盘状态

.not_ready:
    mov dx, 0x1f7   ;同一端口,写时表示写入命令,读时表示读入硬盘状态
    in al, dx
    and al, 0x88    ;第3位为1表示硬盘控制器已经准备好数据传输
                    ;第7位为1表示硬盘忙
    cmp al, 0x08
    jnz .not_ready  ;若为准备好,则跳回not_ready处继续等待

(5)第5步:从硬盘读数据

    mov ax, di   ;di为待读入的扇区数
    mov dx, 256  ;一个扇区512字节,每次读入一字,即两字节,共要读256次
    mul dx       ;乘扇区数
    mov cx, ax
    mov dx, 0x1f0

.go_on_read:
    in ax, dx
    mov [bx], ax      ;存入内存
    add bx, 2         ;指向下一个地址
    loop .go_on_read
    ret               ;返回

四、实践检验!

  现在我们来检验一下,我们的 MBR 是否编写正确,等一下,我们貌似还没有loader程序,虽然我们还不知道loader要干嘛,不过不妨碍我们验证 MBR,我们直接随便在code目录下新建一个假的loader,打印几个字符“2 LOADER”,宣告一下现在是我LOADER的天下啦!

%include "boot.inc"
; 加载器加载到内存中的位置
SECTION LOADER vstart=LOADER_BASE_ADDR

	mov byte [gs:0x50], '2'
    mov byte [gs:0x51], 0xA4
    mov byte [gs:0x52], ' '
    mov byte [gs:0x53], 0xA4
    mov byte [gs:0x54], 'L'
    mov byte [gs:0x55], 0xA4
    mov byte [gs:0x56], 'O'
    mov byte [gs:0x57], 0xA4
    mov byte [gs:0x58], 'A'
    mov byte [gs:0x59], 0xA4
    mov byte [gs:0x5a], 'D'
    mov byte [gs:0x5b], 0xA4
    mov byte [gs:0x5c], 'E'
    mov byte [gs:0x5d], 0xA4
    mov byte [gs:0x5e], 'R'
    mov byte [gs:0x5f], 0xA4
    
	jmp $

  下面我们来看看完整 MBR 长什么样。

%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

    ;清屏
    mov ax, 0x0600   ;AL:上卷的行数(为0表示全部),AH:功能号,0x06表示清屏功能
    mov bx, 0x0700   ;上卷行属性
    mov cx, 0x0      ;左上角(0,0)
    mov dx, 0x184f   ;右下角(80,25),在VGA文本模式中,一行只能容纳80个字符,一共25行
    int 0x10         ;调用BIOS的视频服务中断

    ;在屏幕上显示字符串"1 MBR"
    ;0x1010 0100 -> 前景色(字的颜色)为红色,背景色为绿色,闪烁(字闪烁)
    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
    mov byte [gs:0x06], 'B'
    mov byte [gs:0x07], 0xA4
    mov byte [gs:0x08], 'R'
    mov byte [gs:0x09], 0xA4

    ;接下来用eax,bx,cx三个寄存器传递参数,故先将值存进寄存器中
    mov eax, LOADER_START_SECTOR   ;待读入扇区的起始地址(0x2)
    mov bx, LOADER_BASE_ADDR       ;数据从硬盘读入后存放的地址(0x900)
    mov cx, 4                      ;待读入的扇区数目

    call rd_disk_m_16              ;执行该函数在"16位模式下读硬盘"
    jmp LOADER_BASE_ADDR           ;跳转到LOADER程序

; ============================================================
; 功能:读取硬盘n(由cx寄存器决定读几个扇区)个扇区
; ============================================================
rd_disk_m_16:
    ;备份eax,cx
    mov esi, eax
    mov di, cx

;第一步:设置要读取的扇区数
    mov dx, 0x1f2 
    mov al,cl
    out dx,al

	mov eax, esi

;第二步,将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

    ;设置device
    shr eax, cl
    and al, 0x0f   ;设置LB地址24~27位
    or al, 0xe0    ;0xe0 -> 11100000 ,设置7~4位为1110,表示为LBA模式
    mov dx, 0x1f6
    out dx, al     ;将配置信息写入端口0x1f6

;第三步:向0x1f7端口写入读命令(0x20)
    mov dx, 0x1f7
    mov al, 0x20
    out dx, al

;第四步:检测硬盘状态
.not_ready:
    ;同一端口,写时表示写入命令,读时表示读入硬盘状态
    nop
    in al, dx
    and al, 0x88    ;第3位为1表示硬盘控制器已经准备好数据传输
                    ;第7位为1表示硬盘忙
    cmp al, 0x08
    jnz .not_ready  ;若为准备好,则跳回not_ready处继续等待

;第五步:从0x1f0端口读入数据
    mov ax, di   ;di为待读入的扇区数
    mov dx, 256  ;一个扇区512字节,每次读入一字,即两字节,共要读256次
    mul dx
    mov cx, ax
    mov dx, 0x1f0

.go_on_read:
    in ax, dx
    mov [bx], ax
    add bx, 2
    loop .go_on_read
    ret

;填充空白符,保证整段程序大小为512字节
times 510-($-$$) db 0

;定义MBR中最后两个字节的魔数,表示这个扇区包含可加载的程序
db 0x55, 0xaa

  接下来就是编译和传输,如下:

  	nasm -I ./include/ -o mbr.bin mbr.S
  	dd if=./mbr.bin of=../bochs/hd60M.img bs=512 count=1 conv=notrunc

	nasm -I ./include/ -o loader.bin loader.S
	dd if=./loader.bin of=../bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc

  注意,传输loader的时候,跳过两个扇区,避免覆盖了我们的 MBR

第二步 完善MBR,在这里插入图片描述,第2张

  然后就可以运行我们的bochs啦!

第二步 完善MBR,在这里插入图片描述,第3张

  可以看到我们的 MBR 已经成功完成任务啦!


  持续更新中~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明原文出处。如若内容造成侵权/违法违规/事实不符,请联系SD编程学习网:675289112@qq.com进行投诉反馈,一经查实,立即删除!