目录
1. 计算机的启动过程
CPU 的硬件电路被设计成只能运行处于内存中的程序,这是硬件基因的问题。
程序载入内存分为两部分:程序被加载器(软件或硬件)加载到内存某个区域;CPU 的 cs:ip 寄存器被指向这个程序的起始地址。
操作系统在加载程序时,是需要某个加载器(本质上就是一堆函数组成的模块)来将用户程序存储到内存中的。
从按下主机上的 power 键后,第一个运行的软件是 BIOS。
本节🍊:不要因为未知的东西而感到畏惧!!!
2. 软件接力第一棒,BIOS
BIOS 全称叫 Base Input & Output System,即基本输入输出系统。
BIOS 的主要工作是检测、初始化硬件,硬件自己提供了一些初始化的功能调用,BIOS 直接调用就好了。BIOS 还做了一件伟大的事情,建立了中断向量表,这样就可以通过 “int中断号” 来实现相关的硬件调用。
本节🍊:人们给任何事物起名字,肯定都不是乱起的,必然是根据该事物的特点,通过总结,精练出一些文字来标识此事物,这个便是对一般事物取名的方法。通过名字,就能够反应出该事物的特性。
2.1 实模式下的 1MB 内存布局
内存空间(地址总线宽度—计算机寻址范围)需要分配给几个部分:ROM + 一些外设 +物理内存(也就是内存条 or DRAM)。
本节🍊:我们学习新的知识,很多时候都是建立在原有的知识上,用原有的知识帮助学习新的知识;想象、思考,帮助理解,对于一个新知识的掌握,能够自圆其说,就够了。
2.2 BIOS 是如何苏醒的
只读存储器 ROM:这种存储介质是用来存储一成不变的数据的。
BIOS 所在的 ROM 被映射在低端 lMB 内存的顶部,即地址0xF0000~0xFFFFF处,只要访问此处的地址便是访问了 BIOS,这个映射是由硬件完成的。BIOS 本身是个程序,程序要执行,就要有个入口地址才行,此入口地址便是 0xFFFF0。
在开机的一瞬间,也就是接电的一瞬间,CPU 的 cs:ip 寄存器被强制初始化为 0xF000:0xFFF0。由于开机的时候处于实模式,在实模式下的段基址要乘以 16 ,也就是左移一位,于是 0xF000:0xFFF0 的等效地址将是 0xFFFF0。
BIOS 是在实模式下运行的,而实模式只能访问 1MB 空间 (20位地址线, 2的20次方是 1MB)。——应该是为了兼容吧~
BIOS 入口处的代码是个跳转指令,用来跳转到真正的 BIOS 代码处,f000: e05b 也就是 0xfe05b 处才是 BIOS 代码真正开始的地方。
接下来 BIOS 便马不停蹄地检测内存,显卡等外设信息,当检测通过并初始化好硬件后,开始在内 存中 0x000 0x3FF 处建立数据结构、中断向量表 IVT 并填写中断例程。
BIOS 最后一项工作是校验启动盘中位于0盘0道1扇区的内容,(磁盘上最开始的那个扇区),如果此扇区末尾的两个字节分别是魔数 0x55,0xaa,BIOS 便认为此扇区中确实存在可执行的程序,(此程序便是久闻大名的主引导记录 MBR),便加载到物理地址 0x7c00(0:0x7c00),随后跳转到此地址,继续执行。
加载 MBR 的位置取决于操作系统本身所占内存大小和当前的内存布局。
通常,MBR 的任务是加载某个程序(这个程序一般是内核加载器,很少有直接加载内核的)到指定位置,并将控制权交给它,所谓的交控制权就是 jmp 过去而己。
BIOS总结:在实模式下工作
- BIOS 所在地址空间:0xF0000~0xFFFFF(低端 lMB 内存的顶部);
- 开机接电,cs:ip 寄存器被强制初始化为 0xF000:0xFFF0,即 BIOS 的入口地址 0xFFFF0;
- 入口地址处的跳转指令跳转至 0xf000: 0xe05b 的 BIOS 代码处,开始执行初始化等操作;
- 代码最后的工作是检查磁盘上最开始的那个扇区,若末尾两个字节为0x55,0xaa,则将该扇区512个字节的 MBR 程序加载到 0x7c00,cs从0xf000变为0。
至此,BIOS 的工作就结束了🎉🎉🎉
疑问???
内存中,是程序就要用到栈(🤔️)
3. 让 MBR 先飞一会儿
主引导记录 MBR 的任务是加载内核加载器到指定位置,并将控制权交给它。
MBR 位于磁盘上最开始的那个扇区,其大小必须是 512 字节—这是为了保证 0x55, 0xaa 这两个 魔数恰好出现在该扇区的最后两个字节处。
3.1 神奇好用的$和$$,令人迷惑的 section
伪指令是相对于 CPU 可识别的指令来说的,它(伪指令)只是编译器定义的,CPU 中并不存在这个指令,伪指令是编译器为了开发人员写代码方便而提供的一些符号,这些符号在编译时,会由编译器转换成 CPU 可识别的东西,如指令或地址等。
汇编语言中的标号是程序员“显式地”写在明处的,这个标号会被 nasm 认为是一个地址。
$属于“隐式地”藏在本行代码前的标号,也就是编译器(遇到的话)给当前行安排的地址,看不到却又无处不在,每一行都有。
$$指代本 section 的起始地址,此地址同样是编译器给安排的。
section 是给开发人员逻辑上规划代码用的,只起到思路清晰的作用,最终还是在编译阶段由 nasm 在物理上的规划说了算。
3.2 NASM简单用法
nasm 的基本用法:
nasm -f <format><filename> [-o <output> ]
-o 就是指定输出可执行文件的名称
-f 用来指定输出文件的格式
bin 是指纯二进制格式:纯二进制就是不掺杂其他的东西,直接给 CPU 后就能用,也就是可执行文件中什么样,内存中就什么样。
elf 或 pe 格式的二进制可执行文件,那里面有好多和指令无关的东西,里面掺杂了程序的内存布局、位置等信息,这是给操作系统中的程序加载器用的,是属于操作系统规划的范畴了。
3.3 请下一位选手 MBR 同学做准备
;主引导程序 MBR
;————————————————————————————————————————————————————————————
SECTION MBR vstart=0x7c00
mov ax,cs ;此时cs中的值为0x0
mov ds,ax ;用cs寄存器的值来初始化其他寄存器的值,
mov es,ax ;因为没有从立即数到段寄存器的电路实现,只有通过其他寄存器来中转
mov ss,ax
mov fs,ax
mov sp,0x7c00 ;初始化栈指针,栈是向下发展的,是程序就要用到栈???
;————————————————————————————————————————————————————————————
;清屏,因为在 BIOS 工作中,会有一些输出,如检测硬件的结果信息等。为了让大家看清楚我们在MBR 中的输出字符串,故先把 BIOS 的输出清掉,这里演示的是 BIOS 中断 int 0x10 的用法。
;清屏利用 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
;————————————————————————————————————————————————————————————
;获取光标位置,光标只是在视觉上告诉我们字符写到哪里了
;get_cursor 获取当前光标位置,在光标位置处打印字符
mov ah,3 ;输入: 3 号子功能是获取光标位置,需要存入 ah 寄存器
;(发现:一个中断例程好像有多个子功能,功能号要放入ah寄存器中来调用)
mov bh,0 ;bh 寄存器存储的是待获取光标的页号
int 0x10 ;输出: ch =光标开始行, cl =光标结束行
; dh=光标所在行号, dl =光标所在列号
;————————————————————————————————————————————————————————————
;打印字符串,还是用 10h 中断,不过这次调用 13 号子功能打印字符串
mov ax, message
mov bp, ax ;es:bp 为串首地址, es 此时同 cs 一致,开头时已经为 sreg 初始化
;bp寄存器???
;(note:立即数需要通过通用寄存器存入段寄存器)
;光标位置要用到 dx 寄存器中内容, cx 中的光标位置可忽略
mov cx,12 ;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
编译:nasm -o mbr.bin mbr.S (汇编语言是 .S格式?)
写入磁盘:
Linux命令 dd 是用于磁盘操作的命令
一些参数介绍:🍊够用就行了,需要时再学(man dd 查看帮助文件)
if = FILE: 此项是指定要读取的文件
of = FILE: 此项是指定把数据输出到哪个文件
bs = BYTES: 此项指定块的大小, dd 是以块为单位来进行 IO 操作的,得告诉人家块是多大字节,此项是统计配置了输入块大小 ibs 和输出块大小 obs,这两个也可以单独配置。
count = BLOCKS: 此项是指定拷贝的块数
seek = BLOCKS: 此项是指定当我们把块输出到文件时想要跳过多少个块
conv = CONVS: 此项是指定如何转换文件
append append mode (makes sense only for output; conv=notrunc suggested):
建议在追加数据时, conv 最好用 notrunc 方式,也就是不打断文件。
dd if=/your__path/mbr.bin of=/your__path/bochs/hd60.img bs=512 count=1 conv=notrunc
输入文件是刚刚编译出来的 mbr.bin,输出是我们虚拟出来的硬盘 hd60.img,块大小指定为 512 字节,只操作1块,即总共 1*512=512 字节,由于想写入第0块,所以没用seek指定跳过的块数。
启动bochs虚拟机软件:在bochs安装目录下,bin/bochs -f bochsrc.disk(启动 bochs 的时候,用 -f 来指定其配置文件)
奇怪,光标咋不在左上角🤔️,不过还是🎉🎉🎉啦~