自制操作系统——DeepOs (二):启动 MBR

DeepOs(二)- MBR 主引导记录

这次,我们实现 BIOS 加载 MBR,并在显示器上显示一些信息。

BIOS

BIOS 即 Base Input \& Output System,基本输入输出系统,由于软件大小的缘故所以需要完成的事务很少。在计算机启动后,CPU cs:ip 设置为 0xF000:0xFFF0,此地址便是 BIOS 的入口地址,这里存放着的实际是一条跳转指令,指向 BIOS 代码存放的内存地址。跳转后,BIOS 按照代码检测外设信息,并在内存 0x000-0x3FF 保存这些信息。

实模式和保护模式

Intel 8086 由 20 条地址总线,总共可以访问 1MB 的内存空间,可以使用 32 位指令。但是因为寄存器宽度只有 16 位,因此需要使用特殊的方式来存储地址:使用段基址寄存器和偏移寄存器,如 CS:IP 访问下一条指令。后续 Intel 推出了一些列基于上述处理器的 CPU,即 x86 系列。

80286 处理器引入了地址保护模式的概念,即限制某些地址的访问(中断向量表和内核代码等等),寻址使用 32 位段和偏移量,可以访问 4GB 的段和段内空间。

保护模式下可以通过全局描述符表 GDT 和 局部描述符表 LDT 来访问不同上下文的段。操作系统都必须定义一个 GDT,而每一个程序都需要一个 LDT 来访问自己的段。

实模式下的段宽 16 位,即共 16 个段,段大小 64KB,此时 CPU 直接访问对应的物理内存地址(没有重定位),而这隐患是非常大的。在保护模式下操作系统介入,程序需要地址转换才可以访问内存。再者,CPU 启动时为实模式(兼容),然后可以切换为保护模式,但不允许再切换为 实模式。

0x7C00

BIOS 执行完检测代码后,便会校验 0 盘 0 道 1 扇区的内容,若 512 字节最后两字节的内容为 0xAA55(小端序,即按顺序 0x55、0xAA),便认为这个扇区存放着 MBR,将其加载到物理地址 0x7C00 后执行 MBR 代码。

放在 0x7C00 是因为历史遗留,当时 DOS1.0 要求 32 KB 的内存空间,且 8086CPU 会在 0x000-0x3FF 建立中断向量表,为了给其他程序空出尽可能多的空间,MBR 会被加载到在 32KB 的最后 1KB,又 MBR 会使用栈来服务自己,所以会被加载到 0x7C00。

MBR 1.0

首先给出我们的项目树结构

DeepOs/
├── day1
│ └── mbr.asm
├── deepos.img
├── os_img
└── run.sh --> bash 脚本,用于启动虚拟机

下面代码摘自《操作系统真象还原》。

; mbr.asm
section MBR vstart=0x7C00; BIOS 检测后跳转到此处

init:
   mov ax, 0
   mov ds, ax
   mov es, ax
   mov ss, ax
   mov fs, ax
   mov sp, 0x7C00 ;栈顶设置为 0x7C00

clear_screen:
;清屏:BIOS 中断 0x06 功能
;AH = 0x06 --> 功能号
;AL = 上卷行数(0 为全部)
;BH = 上卷属性
;(CL, CH) = 窗口左上角 (x,y)
;(DL, DH) = 窗口有下角 (x,y)
   mov ax, 0x600
   mov bx, 0x700
   mov cx, 0
   mov dx, 0x184F

   int 0x10

get_cursor:
;获取光标位置:0x03 号功能
;AH = 0x03
;BH = 待获取光标页号
;输出:CH = 光标所在行 CL = 光标结束行
;     DH = 光标行号   DL = 光标列号
   mov ah, 3
   mov bh, 0

   int 0x10

print:
;打印字符串:0x13 号功能
   mov ax, message
   mov bp, ax

   mov cx, 6
   mov ax, 0x1301
   mov bx, 0x2

   int 0x10

end:
   ;jmp [cs:0x7C32]
   jmp $; 死循环,代码停在这里
   message db "DeepOs"
   resb    510-($-$$)  ;填充至 510 字节
   db 0x55, 0xAA

使用命令

nasm -fbin ./day1/mbr.asm -o source

这里 bin 格式指的是可以直接被 CPU 执行,elf 和 win 格式都有汇编的附加信息。

代码说明

上面 init 标签后的指令负责进行初始化寄存器。通过将 ax 设置为 0 间接将 ds 等寄存器设置为 0,然后初始化栈顶指针为 0x7C00。

后面的三个标签中的代码都是调用了 BIOS 中断提供的功能,这里不过多叙述。

vstart

vstart 和 align 是属于 NASM 中节的属性。之前提过,汇编语言中的 section 和 segement 都是程序员自己划分的逻辑区域,要在可执行程序中附加信息才可以被划分到属性不同的段。vstart 和 align 则是用来调整节的编址信息。

上面的源程序中使用了 \$ 和 \$\$ 符号,分别代表本行指令的地址节的地址。汇编器会自动计算这些节的地址,通过偏移 \$ 的地址也可以计算,但这些地址是以程序地址为 0 为前提计算。比如

jmp $

源程序第一条指令原意是在开头处暂停,但如果该程序被加载到其他地址(如上面的 0x7C00),则 jmp 指令的地址便不为 0x0,所以该程序仍然会跳转到物理地址 0x0 处,最后会访问不该访问的内容。

至于在 Windows 和 Linux 中可以直接使用这种写法,则是因为加载器的缘故。加载器载入程序到内存后,会重定位其中使用的地址,并和计算后的段对应的基址相加得到要访问的物理地址,这里先按下不表。

想要在裸机上跑上面的程序,则需要使用 vstart 属性,如 MBR1.0 中所写的那样。汇编器会按照程序起始地址为 0x7C00 开始计算各处使用的地址,所以 \$ 会指向正确的位置。至于 align 属性,则是为了代码在内存对齐,方便访问。

为了验证,可以将 jmp $ 一行注释掉,并将上一行反注释。上一行我按照物理地址直接运行,放在 qemu 虚拟机发现还是可以正常运行。

运行

我写的 bash 脚本如下(有些简陋 :)

##!/bin/bash

nasm -fbin ./$1/$2 -o $3
dd bs=512 conv=notrunc if=$3 of=./deepos.img skip=$4 seek=$5
qemu-system-i386 ./deepos.img

会将源码会变为 bin 文件 img_file,然后使用 dd 命令将其写入我们准备好的虚拟磁盘。运行后弹出 qemu 虚拟机,效果如下

可以看到正确加载了 MBR 并得到了我们想要显示的内容。

改进 MBR

上面的代码实际做的工作很少,只是清屏和打印字符串而已,

作 者:Owmaker
来 源:www.owmaker.cn
链 接:https://www.owmaker.cn/programming/operating-systems/deepos/mbr-2/
版 权 声 明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0许可协议。文章版权归作者所有,未经允许请勿转载!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇