以下是读本书第二章的收获。
记得我大学学习操作系统的时候会遇到一些奇奇怪怪的问题,因为觉得问题太奇怪了,所以羞于问老师。诸如ROM到底是个什么东西,是不是个硬盘呢;如果用内存映射的方式访问外部设备,是不是内存条里专门有块内存空间来用于访问供外部设备,是不是先访问内存条这个地址,然后就直接跳到访问这个设备了等等。幸运的是,这本书都给我一一解答了。
实际上,ROM是下图这样的一种只读存储器(取自百度百科),是一种即使没有通电,也能保存信息的存储器。ROM其实是既可以读也可以写,只不过由于历史原因统称只读存储器。ROM种类不少,我们常用的固态硬盘就是基于闪存(一种ROM)的存储器。
我们下面将要说的BIOS正是存储在ROM设备中的程序,为什么BIOS放在ROM上?个人觉得,BIOS每次通电开机时都要运行,所以是不适合RAM这种断电即丢失信息的存储器了;至于磁盘也不太适合了,我们知道CPU能够直接访问的只有寄存器和内存,不包括磁盘这种外围设备。所以要执行在磁盘中程序的话,首先要将代码加载到内存里,再让CPU从内存取指令出来执行,但因为此时还未执行BIOS程序,来建立对硬件IO操作的功能,所以将代码从磁盘里加载出来的功能还不能使用。这样的话,磁盘程序放不出BIOS,BIOS也执行不了初始化操作,将BIOS放在硬盘估计就是让BIOS和硬盘俩对着对方干瞪眼,所以硬盘并不适合。而ROM正适合放那些一成不变的程序,那么放在主板上的一块ROM芯片理所当然地承载着BIOS这段程序了。
接着引出一个问题,访问ROM中BIOS是不是要先加载到内存里面在让CPU访问呢?不是的,其实ROM也是内存的一部分,是可以被CPU直接访问的。因此,我们插在主板上的内存条并不是我们眼里的所有内存,内存除了刚才所说的ROM和内存条外还包括外设,如显存,硬盘控制器等等,这些都是能够被CPU直接访问的。比如CPU访问0xC8000-0xEFFFF这块地址,并不是访问我们的内存条,而是直接访问到我们的硬件适配器的ROM或者内存映射的I/O。 下图解释了CPU访问内存各部分的流程:
解释完上面两个问题,我们正式进入主题。
BIOS
BIOS,Base Input & Output System,基本输入输出系统,它的主要工作是初始化硬件,硬件里有初始化的功能供BIOS调用;除此之外,BIOS还建立了中断向量表,建立之后,我们就可以通过int 中断号来对硬件进行IO操作,当然,BIOS也仅仅是初始化重要的硬件IO操作来保证计算机的运行,并不会面面俱到,在保护模式会对更多硬件做支持,但在实模式下保证基本的就够了,所以才说是“基本”输入输出系统。
加载BIOS的流程
开机时,BIOS不可能自己加载自己,必然时被硬件所加载的。上面说到存储BIOS的ROM是块内存,是可以被CPU直接访问的。这ROM被映射到了地址0Xf0000~0xfffff处,入口地址,即程序开始执行的地址是0xffff0。所以我们只需将CPU的cs:ip值设置到0xffff0即可,cs:ip组合成0xffff0的方式有很多,不过根据过去的做法,是将CPU的cs:ip寄存器强制初始化为0xf000:0xfff0。这样,在开机的瞬间,CPU就会执行0xffff0下的代码。
BIOS----->MBR
BIOS检测外设信息和初始化硬件,建立中断向量表后,BIOS就已经完成它的任务,最后就是将任务交给MBR了。BIOS最后会校验启动盘中的0盘0道1扇区,如过该上去末尾的两个字节是0x55和0xaa即说明这个扇区存储着MBR程序,于是就加载该扇区到物理地址0x7c00,执行jmp 0 :0x7c00跳到该扇区,继续执行MBR程序。
编写MBR
在写代码之前,先简单介绍几个nasm标号: $代表当前行地址。$$代表当前section的地址。section可以命名某段代码。如果section有vstart修饰,$和$$都是基于vstart顺延的值。这样说的有点简单粗暴,下面看个例子。
某个文件代码如下:
section data vstart=0x500
jmp $
jmp $$
Jmp $中的$实际就是0x500
Jmp $$中的$$实际就是0x500
如果想获得文件中真实的地址,就用section.节名.start,如section.data.start = 0
接下来直接给出MBR代码:
;主引导程序
;------------------------------------------------------------
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
; 清屏 利用0x06号功能,上卷全部行,则可清屏。
; -----------------------------------------------------------
;INT 0x10 功能号:0x06 功能描述:上卷窗口
;------------------------------------------------------
;输入:
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角的(X,Y)位置
;无返回值:
mov ax, 0x600
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, 5 ; cx 为串长度,不包括结束符0的字符个数
mov ax, 0x1301 ; 子功能号13是显示字符及属性,要存入ah寄存器,
; al设置写字符方式 ah=01: 显示字符串,光标跟随移动
mov bx, 0x2 ; bh存储要显示的页号,此处是第0页,
; bl中是字符属性, 属性黑底绿字(bl = 02h)
int 0x10 ; 执行BIOS 0x10 号中断
;;;;;;;;; 打字字符串结束 ;;;;;;;;;;;;;;;
jmp $ ; 使程序悬停在此
message db "1 MBR"
times 510-($-$$) db 0
db 0x55,0xaa
这个MBR的功能就是在屏幕上打印字符串”1 MBR”,背景色为黑色,前景色为绿色。根据注释看看代码意思,不需要太深入理解,后面还会讲到。
将代码文件命名为mbr.S,进入该文件所在位置,输入下面的指令:
nasm -o mbr.bin mbr.S
就会看到当前目录生成了mbr.bin:
然后我们需要将该可执行文件放入启动盘,具体来说是0盘0道1扇区中。
输入下列指令,将mbr.bin写入hd60M.img,hd60M.img是上一节安装bochs那里生成的。
dd if=/your_path/mbr.bin of= /your_path /hd60M.img bs=512 count=1 conv=notrunc
进入bochs安装目录,输入bin/bochs -f bochsrc.disk:
bin/bochs -f bochsrc.disk
回车后,输入c继续运行,显示结果如下图:
至此简单的MBR功能就完成了。