前言:
从今天起可以说是正式踏入操作系统编写的世界了,这里面会有很多未知副本等待我们,今天要去副本就是 “启动”,今天我们要了解计算机是如何启动的,揭开BIOS的神秘面纱,并在这打造去往下一关的钥匙 MBR,话不多说让我们开始吧!
一,计算机启动过程
一开始就跟大家讲过程,大家头肯定会晕,我们放在之后在讲吧。我们先来看看操作系统载入内存
- 为什么要载入内存?
操作系统一开始存储在各种硬盘中(也可以是软盘,U盘),内存速度快,容量大,CPU的电路被设计成只能运行内存中指令的程序
- 如何加载到内存中?
首先载入内存被分为两个部分
程序被加载器(软件或者硬件)加载到内存某个区域
程序计数器将cs:ip指向该内存区域
而操作系统的载入,离不开BIOS,BIOS也是一款软件,在按下开机键的一刻,最先加载的便是BIOS程序,那么问题来了
- 谁加载的BIOS?
- 加载到哪儿去了?
- 谁改变了他的cs:ip?
二,伟大的BIOS
BIOS,全名(Base Input&Output system),基础输入输出设备。
为了更进一步的了解他,我们来模拟一个环境,了解一下他的背景吧
① 实模式下的内存布局
在很久很久以前
Intel 8086有20条地址总线,所以其能访问的地址范围是 1MB:0~FFFFF,当时的1MB内存被硬件工程师分成了多个部分
由图可知
0~9FFFF处是 DRAM
0xF0000~0xFFFFF处是 ROM
🚨注意:这里的内存分布不是说你的内存条的分布,而是访问地址的分布,0~9FFFF是指该范围对应DRAM,牢记这一点
DRAM(知识科普,会的可以跳过)
DRAM被称为动态随机访问内存,动态指此种存储介质由于本身电气元件的性质,我们平常的物理内存就是DRAM,他是可以被写入的,那为什么是动态的呢,因为他要不断地刷新
刷新:这里的刷新即充电,为什么要充电(
好多为什么啊), 为了了解如何充电的,我们可以先来了解一下内存的存储结构,内存中的每一位都是由电容和晶体管组成的,而电容里面的电荷便来代表所含比特数量(不要在意这里贴的是固态的图,只是描述电容的大概样子,中间小圆点可以理解为电荷,大方块理解为电容)而这么多电容,在所难免的就是会漏电,所以需要及时的充电,这样才不会导致数据丢失。
ROM(知识科普,会的可以跳过)
Read Only Memory,代表只读存储器,非破坏性读出方式工作,只能读出无法写入信息。信息一旦写入后就固定下来,即使切断电源,信息也不会丢失
上面我们说到了0xF0000~0xFFFFF 是ROM,其中存放的是BIOS代码
在CPU眼里主板上的物理内存不是它眼里全部的内存,打比方32位的CPU可以访问的地址范围是4GB,但是这4GB并不是全部落在物理内存条上面的(从上面我们就发现有的在ROM,有的在DRAM嘛),还有一些地址线是和其他硬件进行通信的,(比如 0 DAY说的 显存访问)。所以要留给外设一些地址范围,分配够以后再给DRAM也就是我们本身的物理内存。
也就是说,你如果是32位电脑的话,最大可以装4G的内存条,但是实际可用内存是3.2~3.8G内存,看你的操作系统而决定,装再多其他的也访问不到
(都2022了,不会有人用32位吧)
同时还找到了windows关于此问题的回答
BIOS
主要工作:硬件检测,硬件初始化,定义中断向量表
终于进入BIOS正题了,BIOS的主要工作就是进行硬件检测,初始化,同时他还干了一个伟大的事情,就是定义中断向量表,有了这个中断向量表就可以通过 “int 中断号”,来实现硬件的调用,一般都是一些基本的IO调用,在进入保护模式下才会有更加丰富的功能。
那既然BIOS是程序,那肯定就要有程序入口对吧,那程序入口在哪里呢,我们来看看上面的图,图上所说,BIOS程序入口是 0xFFFF0~0xFFFFF,那这里就有三个问题了。
- BIOS被谁加载进来的?
ROM加载进入的,而且是永永远远固化在ROM,一个人享受岁月静好,沧海桑田,永不变迁。(除非你电脑坏了)
- CPU的cs:ip如何定位到0xFFFF0,他又是如何组合的?
- 0xFFFF0~0xFFFFF这个地址他只有16字节,怎么放得下BIOS程序的?
带着这几个疑问我们继续往下探索
问题②
聪明的同学看到这里可能会说,cs:ip的组合很简单嘛,它可以是 0xF000:0xFFF0,或者是0xFEEE:0x1110等等组合,我只能说你确实很聪明,但是我们应该要想明白一点,在计算机启动前,整个计算机世界是一片荒芜,连BIOS这第一个软件都没有诞生,又有什么软件去做这种事情呢?
实际上在开机通电的一瞬间,CPU会强制将自己的cs:ip给定位到 0xF000:0xFFF0,由于开机处于实模式,那么 段地址*16 + 基址 = 0xFFFF0。
问题③
仔细想想,16字节怎么可能放得下BIOS程序,写个a + b都不止16字节了吧(我猜的),所以我大胆一猜,这16字节肯定是 jmp 到某个地址继续去运行的。实际上也确实是这样的,它会jmp far f000: e05b ,具体的过程我们写完MBR会看到,跳转到这里便开始检测内存,显卡等等,检测完并初始化后呢,就开始在 0x000~0x3FF建立中断向量表填写中断例程。
当前路线总结:
电脑上电 - > CPU cs:ip 强制初始化为 0xF000:0xFFF0 -> 载入BIOS ->检测内存,显卡 -> 编写中断向量表
三,BIOS最后的波纹 -- 寻找引导文件
至此,BIOS的使命也算是完成了,它将永远的睡去(下次开机又会回来的,别怕),它是计算机这荒芜世界的开创者,是启明灯,一说到这里我就想到很久很久以前.....(不扯远了)
但实际上 BIOS还有着他的最后一项任务,就是校验启动盘中 0盘0道1扇区的内容,最后两个字节是不是魔数 0x55,0xaa,如果是的话,那就任务次扇区确实是可执行文件 MBR,便将其加载到0x7c00,然后 jmp 0:0x7c00继续执行
问题:
1,什么是盘,道,扇?
2,为什么不是0盘0道0扇区开始?(实际上是一样的)
这里在磁盘那里会细细说明。
3,为什么MBR是在0盘0到1扇区?
我认为是规定好的,不然BIOS还要跑一整圈磁盘找你那启动的512字节,还有那两个魔数,累不累呀。
4,为什么是0x7c00?
这下子真要在很久很久以前了,感兴趣的可以看看故事
四,终于要写MBR了
几经波折,终于来到了写代码的部分,准备冻手!!
首先要注意几点
- MBR是512字节,最后两个字节是0x55,0xaa,且采用的是小端字节序,最后两个字节内容0xaa55
- $和$$是NASM的关键字,$是代表本行代码前的标号的地址,$$代表是本section的地址
- section是程序员进行的分段,比如代码段,数据段
编写MBR文件(以下提供两种写法)
mbr.S(书上的写法)
SECTION MBR vstart=0x7c00 ;告诉编译器我的起始编译地址是 0x7c00
;利用cs(因为jmp 0:0x7c00,所以一开始是0) 来初始化ax,ds,es,ss,fs,gs,这类sreg寄存器是无法直接存储立即数的即直接赋值,所以需要通过cs来转移
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00 ;初始化栈指针,mbr是程序,程序就要用到栈
;利用0x06功能号进行清屏 INT 0x10 ah = 0x06 BIOS在运行过程中会有一些输出,这样子是为了更好看到我们的字符
mov ax,0x600 ;ah = 0x06 al = 0代表全部
mov bx,0x700 ;BH = 上卷行属性
mov cx,0 ;(CL,CH) = 窗口左上角 (x,y)
mov dx,0x184f ;(DL,DH) = 窗口右下角 (x,y) (0x18=24,0x4f=79) VGA文本一行只容纳 80个字符,25行
int 0x10
;获取光标位置,在光标处打印字符
mov ah,3 ; 3号功能即获取光标位置
mov bh,0 ; 存储带获取光标页号,第0页
int 0x10
;打印字符串
mov ax,message
mov bp,ax ; es:bp 为串首地址,es此时同cs一致
mov cx,6 ; cx 为串长度,不包括结束符0的字符个数
mov ax,0x1301 ;子功能号 13 显示字符及属性,要存入 ah 寄存器,
; al 设置写字符方式 ah=0 :显示字符串,光标跟随移动
mov bx,0x2 ; bh 存储要显示的页号,此处是第
; bl 中是字符属性,属性黑底绿字(bl = 02h)
int 0x10 ; 执行 BIOS Ox10 号中
jmp $
message db "Geniux"
times 510-($-$$) db 0 ;510 - 当前行号 - setcion行号得到剩余位置并填0
db 0x55,0xaa
mbr.asm
org 07c00h ;告诉编译器程序加载到7c00处
mov ax, cs
mov ds, ax
mov es, ax
call show ;调用显示字符串例程
jmp $ ;无限循环
show:
mov ax, Message
mov bp, ax ;ES:BP = 串地址
mov cx, 16 ;CX = 串长度
mov ax, 01301h ;AH = 13h, AL = 01h
mov bx, 000ch ;页号为0(BH= 0)黑底红字(BL = 0Ch,高亮)
mov dl, 0
int 10h ;10h号中断
ret
Message: db "Hello, Genius OS"
times 510-($-$$) db 0 ;填充剩下的空间
dw 0xaa55 ;结束标志
使用nasm编译 mbr.bin
nasm -o mbr.bin mbr.S
有了代码我们需要写入软盘中,使用dd命令来将bin文件写入geniux.img的第0块
sudo dd if=/usr/geniux/mbr.bin of=/usr/geniux/img/geniux.img bs=512 count=1 conv=notrunc
出现下图即成功
然后 调用
bochs -f 你的配置文件
#注:
1,如果文件没有写权限,请使用sudo操作
如果出现无法read img的情况,请去掉配置文件中 ata0-master,并采用软盘启动
megs: 32
romimage: file=/usr/share/bochs/BIOS-bochs-latest
vgaromimage : file=/usr/share/bochs/VGABIOS-lgpl-latest
floppya: 1_44="/usr/geniux/img/geniusos.img", status=inserted
#boot: disk
boot:floppy
log: bochs.out
mouse: enabled=0
keyboard:keymap=/usr/share/bochs/keymaps/x11-pc-us.map
ata0: enabled=1, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
#display_library: sdl
#gdbstub: enabled=1, port=1234, text_base=0, data_base=0, bss_base=0
成功结果图
🎉 芜湖!!!,Congratulations!!!🎉
忙碌了一天,终于有一个最基础的镜像文件了,这虽然是我们的一小步,但是确实在操作系统世界中的一大步,但是你觉得可以休息了吗,不不不,让我们把注意力放在bochs的输出面板上。
此时我们惊喜的发现,bochs此时执行的代码是 jmpf 0xf000:e05b,好熟悉啊这是哪里的代码,对呀这就是我们之前讲bios的入口的代码,跳转到要运行的地方,0xf000~0xfff0还记得吗,看样子我们的猜想是正确的,忙碌了一天了,还是先赶紧休息吧!
五,启动路线总结
开机上电 -> CPU cs:ip强制定位到 0xf000:0xfff0 -> BIOS加载 -> 硬件检测 -> 硬件初始化 -> 构建中断向量表 -> 读取 0盘 0道 1扇区 -> 校验魔数 0x55 0xaa -> 将MBR引导区内容读入0x7c00 -> 开始执行引导区内容
补档
在linux端运行突然发现了有物理内存读取错误,虽然有bochs界面,却没有实现mbr的功能,百思不得其解,现在还没解决,但是在windows是可以正常运行显示的。
明日预告
我们将更加深入的去了解MBR,并且在屏幕上painting something,拭目以待吧。