前言:

从今天起可以说是正式踏入操作系统编写的世界了,这里面会有很多未知副本等待我们,今天要去副本就是 “启动”,今天我们要了解计算机是如何启动的,揭开BIOS的神秘面纱,并在这打造去往下一关的钥匙 MBR,话不多说让我们开始吧!

一,计算机启动过程

bios如何加载系统 bios程序如何被加载到内存_linux

一开始就跟大家讲过程,大家头肯定会晕,我们放在之后在讲吧。我们先来看看操作系统载入内存

  • 为什么要载入内存?

操作系统一开始存储在各种硬盘中(也可以是软盘,U盘),内存速度快,容量大,CPU的电路被设计成只能运行内存中指令的程序

  • 如何加载到内存中?

首先载入内存被分为两个部分

程序被加载器(软件或者硬件)加载到内存某个区域

程序计数器将cs:ip指向该内存区域

而操作系统的载入,离不开BIOS,BIOS也是一款软件,在按下开机键的一刻,最先加载的便是BIOS程序,那么问题来了

  • 谁加载的BIOS?
  • 加载到哪儿去了?
  • 谁改变了他的cs:ip?

二,伟大的BIOS

BIOS,全名(Base Input&Output system),基础输入输出设备。

为了更进一步的了解他,我们来模拟一个环境,了解一下他的背景吧

① 实模式下的内存布局

在很久很久以前

Intel 8086有20条地址总线,所以其能访问的地址范围是 1MB:0~FFFFF,当时的1MB内存被硬件工程师分成了多个部分

bios如何加载系统 bios程序如何被加载到内存_windows_02

由图可知

0~9FFFF处是 DRAM  

0xF0000~0xFFFFF处是 ROM

🚨注意:这里的内存分布不是说你的内存条的分布,而是访问地址的分布,0~9FFFF是指该范围对应DRAM,牢记这一点

 DRAM(知识科普,会的可以跳过)

DRAM被称为动态随机访问内存,动态指此种存储介质由于本身电气元件的性质,我们平常的物理内存就是DRAM,他是可以被写入的,那为什么是动态的呢,因为他要不断地刷新


刷新:这里的刷新即充电,为什么要充电(好多为什么啊), 为了了解如何充电的,我们可以先来了解一下内存的存储结构,内存中的每一位都是由电容和晶体管组成的,而电容里面的电荷便来代表所含比特数量

 (不要在意这里贴的是固态的图,只是描述电容的大概样子,中间小圆点可以理解为电荷,大方块理解为电容)

bios如何加载系统 bios程序如何被加载到内存_windows_03

而这么多电容,在所难免的就是会漏电,所以需要及时的充电,这样才不会导致数据丢失。

ROM(知识科普,会的可以跳过)

Read Only Memory,代表只读存储器,非破坏性读出方式工作,只能读出无法写入信息。信息一旦写入后就固定下来,即使切断电源,信息也不会丢失

上面我们说到了0xF0000~0xFFFFF 是ROM,其中存放的是BIOS代码

在CPU眼里主板上的物理内存不是它眼里全部的内存,打比方32位的CPU可以访问的地址范围是4GB,但是这4GB并不是全部落在物理内存条上面的(从上面我们就发现有的在ROM,有的在DRAM嘛),还有一些地址线是和其他硬件进行通信的,(比如 0 DAY说的 显存访问)。所以要留给外设一些地址范围,分配够以后再给DRAM也就是我们本身的物理内存。 

bios如何加载系统 bios程序如何被加载到内存_初始化_04

也就是说,你如果是32位电脑的话,最大可以装4G的内存条,但是实际可用内存是3.2~3.8G内存,看你的操作系统而决定,装再多其他的也访问不到(都2022了,不会有人用32位吧)


同时还找到了windows关于此问题的回答


bios如何加载系统 bios程序如何被加载到内存_初始化_05

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?

这下子真要在很久很久以前了,感兴趣的可以看看故事

bios如何加载系统 bios程序如何被加载到内存_加载_06

bios如何加载系统 bios程序如何被加载到内存_初始化_07

四,终于要写MBR了

几经波折,终于来到了写代码的部分,准备冻手!!

首先要注意几点

  • MBR是512字节,最后两个字节是0x55,0xaa,且采用的是小端字节序,最后两个字节内容0xaa55
  • $和$$是NASM的关键字,$是代表本行代码前的标号的地址,$$代表是本section的地址
  • section是程序员进行的分段,比如代码段,数据段

bios如何加载系统 bios程序如何被加载到内存_加载_08

 编写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

出现下图即成功 

bios如何加载系统 bios程序如何被加载到内存_linux_09

 然后 调用

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

成功结果图

bios如何加载系统 bios程序如何被加载到内存_linux_10

                                           🎉  芜湖!!!,Congratulations!!!🎉

忙碌了一天,终于有一个最基础的镜像文件了,这虽然是我们的一小步,但是确实在操作系统世界中的一大步,但是你觉得可以休息了吗,不不不,让我们把注意力放在bochs的输出面板上。

bios如何加载系统 bios程序如何被加载到内存_加载_11

 此时我们惊喜的发现,bochs此时执行的代码是 jmpf 0xf000:e05b,好熟悉啊这是哪里的代码,对呀这就是我们之前讲bios的入口的代码,跳转到要运行的地方,0xf000~0xfff0还记得吗,看样子我们的猜想是正确的,忙碌了一天了,还是先赶紧休息吧!

五,启动路线总结

开机上电 -> CPU cs:ip强制定位到 0xf000:0xfff0 -> BIOS加载 -> 硬件检测 -> 硬件初始化 -> 构建中断向量表 -> 读取 0盘 0道 1扇区 -> 校验魔数 0x55 0xaa -> 将MBR引导区内容读入0x7c00 -> 开始执行引导区内容

补档 

在linux端运行突然发现了有物理内存读取错误,虽然有bochs界面,却没有实现mbr的功能,百思不得其解,现在还没解决,但是在windows是可以正常运行显示的。

bios如何加载系统 bios程序如何被加载到内存_初始化_12

 

bios如何加载系统 bios程序如何被加载到内存_初始化_13

 明日预告

我们将更加深入的去了解MBR,并且在屏幕上painting something,拭目以待吧。