BIOS- Base Input&Output System

  • - BIOS 是计算机上电后第一个运行的程序
  • - BIOS 首先检测硬件状态,检测通过后立即进行硬件初始化
  • - BIOS 会在内存中建立中断向量表( 提供硬件访问的方法,比如int 10h中断向量是BIOS提供的中断例程
  • - BIOS 最后将控制权交由主引导程序执行

注意:BIOS 不是软件( Software ) , 而是固件( Firmware ) !

固件是固化于硬件中的程序 ,在硬件出厂前已经烧写固定。

系统启动流程:

image-20220423204007317

BIOS运行机制

  • BIOS 存储于 ROM(只读内存,硬盘) 中 , 地址映射为 0xF0000 - 0xFFFFF ( 实地址 )
  • BIOS的入口地址为 : 0xFFFF0
  • 硬件电路的特殊设计使得 :开机后 , CPU 从 0xFFFF0 处开始执行

BIOS不被谁加载执行,通过硬件设计后,CPU直接从BIOS入口地址开始执行

BIOS 最后的使命

  1. 按照用户设置扫描各个存储介质( 光驱,软驱,u盘,等 )
  2. 发现主引导区后 , 将主引导区中的主引导程序载入内存
  3. 主引导程序被加载到内存中的入口地址为 0x7c00
  4. 将控制权交由主引程序执行 ( jmp 0x7c00 )

BIOS 如何在存储介质中寻找主引导区(MBR)?

主引导区( MBR : Master Boot Record )

  • 位置 : 位于存储介质的最开始位置处,大小为 512 字节
  • 特点 : 前 512 字节的最后 2 个有效字节为 0x55aa
  • 数据 : 0x55aa 之前的数据被视为主引导程序

两个问题:

  1. 每一个存储介质的前512个字节是否一定是主引导区?
    不一定,只有最后 2 个有效字节为 0x55aa会被视为主引导区

  2. 是不是主引导区存储的数据一定是合法的程序呢?
    不一定,可能加载到内存后执行出错

image-20220423205018934

小结

  • BIOS 是计算机上电后第一运行的程序
  • BIOS进行必要的初始化 ,并加载运行主引导程序
  • 主引导程序位于存储介质的最开始512 字节处
  • 主引导程序负责后续初始化 , 并加载运行操作系统内核

FYOS(实现一个最简单的OS)

主引导程序是软件还是固件? 如果是软件 ,那么由谁开发? 如何开发?

image-20220423205438949

一个实验:

  • - 编写一个主引导程序( 汇编语言 )
  • - 可独立运行于 x86 架构的主机( 无操作系统 )
  • - 运行后在屏幕上打印”Hello,FYOS! “

实现思路

  1. 将关键寄存器的值设置为0 ( mov ax,0 )
  2. 定义需要打印的数据( db “Hello,FYOS!” )
  3. 打印预定义好的字符数据(借助BIOS int 0x10)

一些必备汇编指令:

  1. mov : 赋值操作,将右操作数赋值给左操作数
    mov ax,0 ;持 0 赋值给 ax 寄存器
  2. int : 触发中断
    int 0x10 :触发 0x10 中断 , 对屏幕操作
  3. hit : 停止运行 , CPU 进入暂停状态 , 不执行任何操作
    hit :使程序进入睡眠状态
  4. 汇编中地址的访问方式 : 段地址 : 段内偏移地址
    mov byte [0xb800:0x01], 0x07; 意思是0xb800:0x01 0xb8000 + 0x01
  5. 标签
    用于标识后续指令的地址( 可等同为 C 语言中的标签 )
  6. $ vs $$
    $表示当前指令行地址, $$表示当前汇编段起始地址

image-20220423210304665

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
org 0x7c00 ;ORG,其作用是告诉汇编程序,在开始执行的时候,将某段机器语言装载到内存中的哪个地址。

start: ;标签另一层含义,这些指令的地址
mov ax, cs ;设置关键寄存器的值为0,cs代码段初始恰为0
mov ss, ax
mov ds, ax
mov es, ax

mov si, msg ;msg代表的地址赋值到si寄存器

print:
mov al, [si] ;[si]类似于C语言中* 取出msg数据
add si, 1 ;si 地址+1
cmp al, 0x00 ;是否结束
je last ;跳转到last
mov ah, 0x0e ;设置参数
mov bx, 0x0f ;设置参数
int 0x10 ;借助10号中断打印数据
jmp print

last:
hlt ;cpu停止
jmp last ;死循环

msg:
db 0x0a, 0x0a ;\n 换行符
db "Hello, FYOS!" ; 字符串
db 0x0a, 0x0a ;\n 换行符
times 510-($-$$) db 0x00 ;512大小的主引导程序这部分全部填0
db 0x55, 0xaa ;注意表示这段代码是主引导程序,末尾0x55aa

那么如何验证编写的主引导程序?

解决方案设计

  1. 汇编源码编译为二进制机器码( nasm )
  2. 创建虚拟盘( bximage )
  3. 二进制代码写入虚拟盘起始位置即第0个扇区处( dd )
  4. 在虚拟机中将虚拟盘作为启动盘执行( vmware )
1
2
3
4
5
nasm boot.asm -o boot.bin ;编译汇编代码变为二进制代码

bximage a.img -q -fd -size=1.44 ;虚拟软盘 1.44M

dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc ;if代表输入 of代表输出 bs写入单元大小 notrunc连续写入无间隔

首先我在虚拟机里自建了一个空的虚拟机,并且把软盘复制到自建操作系统的目录下面

image-20220423212800469

image-20220423212720077

打开自己创建的操作系统,打印出了传说中的hello world,质的飞跃

image-20220423213424100

ORG指令

  • 首先,无论你的汇编代码头部加不加一句org 0x 7c00指令,你的主引导扇区代码都会被BIOS自动加载到内存0x0000:7C00处,自动加载这是焊死在硬件上面的规范,说白了,自动加载与org指令半点关系都没有;
  • 其次,是不是主引导扇区代码,看的是你扇区最后两个字节是不是0x55aa,也和org指令没有半点关系;
  • 然后,有些主引导扇区汇编代码不加org指令也可以正常运行,就是因为那些代码里面没有涉及标号label 相关的代码,不需要对 标号label 进行绝对地址的计算,然而我的上述主引导区代码使用了标号label,因此主引导区代码首句需要org 0x7c00

[举例]下面这就是没有使用label也没有使用org指令的主引导扇区代码
https://www.jianshu.com/p/207aaf0f986b

  • 接着,标号是标号,标号不是寄存器,标号是程序员可以自由任意随便取名字的,取的那个名字只是帮助你阅读代码用的,在代码经过汇编编译器之后标号会被解读成一个具体的数值,org影响的就是最后算标号的那个具体数值
  • 最后,这个标号代码的那个具体的数值是什么?
    是一个offset,即当前标号所在代码行距离代码开始处的字节数

为什么需要 org 指令?

  • 假设,你的标号距离你的代码开始处是八个字节,那个标号的值原本应该就是0x08的,这个原本可以是不写org指令,也可以是写个org 0x0000,反正无论哪种这时标号会被解读成数值0x00000008
  • 由于你是主引导扇区的代码,你的代码被自动加载到内存0x0000:7C00处了,代码被加载到内存,实际上是说机器码被加载到内存,也就说是说无论你的代码被加载到0x0000:7C00还是被加载到0x1234:5678这里, 数值0x00000008还是数值0x00000008
  • 那么问题就来了?
    数值0x00000008是一个绝对地址,你现在代码全部在0x0000:7C00你真正想要找的标号是被放到了从0x0000:7C00开始后的八个字节的地方,它应该是0x00007C000+0x00000008 = 0x00007C08而不是0x00000008
  • 那么如何解决?

[1] 要么直接修改标号的绝对地址,在使用标号赋值的时候,强制加上0x7c00,比如 mov ax,bootmsg 写成 mov ax,bootmsg+0x7C00
[2] 要么,在代码开头使用org 0x7C00,代码还是写mov ax ,bootmsg 但是它会悄悄地把bootmsg解读成 bootmsg+0x7C00

转载于https://www.jianshu.com/p/dbbbc714f942

调试环境的搭建

Bochs( 另一款优秀的虚拟机软件)

  • 专业模拟 X86 架构的虚拟机
  • 开源且局度可移植 , 由C++ 编写完成
  • 支持操作系统开发过程中的断点调试
  • 通过简单配置就能够运行绝大数主流的操作系统

支持调试功能的 Bochs 版本

  1. 下载源码 : https://sourceforge.net/projects/bochs/files/
  2. 解压缩 bochs-2.x.x.tar.gz 今 bochs-2.x.x
  3. 进入源码目录 : cd bochs-2.x.x
  4. ./configure -enable-debugger -enable-disasm
  5. make
  6. sudo make install

配置bochs.txt

image-20220423215158808

启动 bochs 虚拟机

  • - 显示方式: bochs-f bochsrc_file
  • - 隐式方式 : bochs

image-20220423215316453

image-20220423220914146

boch调试

image-20220423220959949

1
2
3
4
5
<bochs:1> break 0x7c00
<bochs:2> info break
Num Type Disp Enb Address
1 pbreakpoint keep y 0x000000007c00
<bochs:3> continue

可以看到执行到我们上述编写的汇编的第一条语句

1
2
3
(0) Breakpoint 1, 0x0000000000007c00 in ?? ()
Next at t=14034555
(0) [0x000000007c00] 0000:7c00 (unk. ctxt): mov ax, cs ; 8cc8

8cc8是编译mov ax, cs后得到的16进制数

小结

  • Bochs 是一款专业模拟 x86 架构的虚拟机
  • 从 源 码 安 装 Bochs 可以获得调试功能的支持
  • Bochs 的启动配置文件是正确运行关键
  • Bochs 支持断点调试 , 其调试命令与 GDB 类似