引言: 这是我的一小步,却是…好吧还是一小步

计算机的启动过程

第一个运行的软件是BIOS(base input and output system),所以了解计算机的启动过程,我们就得了解BIOS。

BIOS存在只读存储器ROM里面, 在8086(一个cpu)的实模式内存布局下,BIOS有1MB的大小空间,也就是(2^20bits),BIOS的载入地址为0xF0000~0xFFFFF。这64KB
是ROM,里面存了BIOS的代码, BIOS建立了中断向量表, 所以我们可以用 “int 中断号” 来实现硬件调用.

0-0x9FFFF是DRAM (动态随机访问内存), 640kb的大小, 对于8086来说,这就是主存部分, 我们可以发现主存总是比地址总线要小, 因为地址总线还要分配地址给各种外部设备,不只是内存.

由于CPU访问内存是分段访问。所以具体为段基址往左边偏移四位(十六进制下就是一位)加上偏移地址,所以在开机的一瞬间,cs:ip 寄存器会被强制初始化为 0xF000:0xFFF0,我们来简单换算一下, F0000 + FFF0 -> FFFF0, 也就是说,BIOS的入口地址是0xFFFF0。而我们前面说BIOS的终止地址是 0xFFFFF. 这么小的一个空间(只有16个字节)什么也干不了,所以必然是一个跳转指令. (jmp f000:e506), 这条指令的意思是,从地址 0xFE506 开始执行。接下来,BIOS在这个位置不断的检测各种内存,显卡等设备有没有问题, 然后在 0x000 - 0x3FF 建立数据结构中断向量表.

执行到这,BIOS完成了自己的使命. 接下来就轮到磁盘了, BIOS认为如果磁盘扇区最后连个字节分别是0x55 和 0xaa 就认为存在程序,会继续执行, 把第一个可用的磁盘加载到 0x7c00 (魔数). MBR的大小要为512字节, 占第一个扇区让上面两个magic number 正好出现在最后两个字节.

简单讲一下 bin 格式和 ELF 格式的区别, bin格式是纯二进制文件, 在可执行文件中什么样子,在内存中就什么样子, 而ELF还包含了,程序的内存布局和位置等信息.

接下来是一个简单的MBR调用BIOS中断来显示输出的程序. 这个程序的作用是显示一个字符串 “Hello, world!” 到屏幕上. 将这个程序编译完后,如何放到第一个扇区作为mbr运行呢. 用dd这个命令 就可以把编译后的程序放到第一个扇区了.dd if=/home/meiran/Documents/OS/mbr.bin of=./hd60M.img bs=512 count=1 conv=notrunc

编译mbr

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
;主引导程序
;-----------------------------------------------------
SECTION MBR vstart=0x7c00 ;告诉编译器,起始地址是0x7c00
mov ax,cs ;因为BIOS执行完毕后cs:ip为0x0:0x7c00,所以用cs初始化各寄存器(此时cs=0)
mov ds,ax ;ds、es、ss、fs不能给立即数初始化,需要用ax寄存器初始化
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00 ;初始化堆栈指针,因为目前0x7c00以下的内存暂时可用

;清屏利用0x06号功能,上卷全部行,则可清屏
;-----------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;-----------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:

mov ax,0x600 ;上卷行数:全部 功能号:06
mov bx,0x700 ;上卷属性
mov cx,0 ;左上角:(0,0)
mov dx,0x184f ;右下角:(80,25)
;VGA文本模式中,一行只能容纳80字节,共25行
;下标从0开始,所以0x18=24,0x4f=79
int 0x10 ;int 0x10

;;;;;;;; 下面这三行代码获取光标位置 ;;;;;;;;
;字符的位置与显存中的地址有关,和光标无关,光标只是为了好看而加上去的
;.get_cursor获取当前光标位置,在光标位置处打印字符。
mov ah,3 ;输入:3号子功能是获取光标位置,需要存入ah寄存器
mov bh,0 ;bh寄存器存储的是待获取光标的页号

int 0x10 ;输出:ch=光标开始行,cl=光标结束行
;dh=光标所在行号,dl=光标所在列号
;;;;;;;; 获取光标位置结束 ;;;;;;;;

;;;;;;;; 打印字符串 ;;;;;;;;
;还是用10h中断,不过这次调用13号子功能打印字符串
mov ax,message
mov bp,ax ;es:bp为串首地址,es此时同cs一致,
;开头时已经为sreg初始化

;光标位置要用到dx寄存器中内容,cx中光标位置可忽略
mov cx,11 ;cx为串长度,不包括结束符0的字符个数
mov ax,0x1301 ;子功能号13时显示字符及属性,要存入ah寄存器,
;al设置写字符串方式al=01:显示字符串,光标跟随移动
mov bx,0x2 ;bh存储要显示的页号,此处时第0页,
;bl中式字符属性,属性黑底绿字(bl = 02h)
int 0x10 ;执行BIOS 0x10 号中断
;;;;;;;; 打字字符串结束 ;;;;;;;;
jmp $ ;使用程序悬停在此

message db "hello world"
times 510-($-$$) db 0
db 0x55,0xaa

运行结果如下图
运行mbr