明确几个概念
- BIOS:基本输入输出系统,它是存放在内存的固件,也就是ROM区。具体功能下面再说。
- x86的实模式:
- CS:Code Segment Register,代码段寄存器,存放地址信息,指向CPU当前执行代码段所在的区域。补充:D(Data)S就是数据段寄存器,S(stack)S:堆栈段寄存器,E(extra)S:附加段寄存器
- IP:Instruction Pointer,指令指针寄存器,存放的是当前要执行的指令在我们这个代码段(CS指向的)中的偏移量。所以他与CS相结合,可以表示我们要执行的指令在内存中的地址。形式为CS:IP,是将CS左移4位,然后加上IP的内容即可。这里CS左移不是逻辑或者算术移位,它是带扩充的。就是0xFFFF,左移4位为,0xFFFF0,低位扩充了4位。所以假设CS=0xFFFF,IP=0x0000。那么他们所表示的指令地址为0xFFFF0+0x00000=0xFFFF0.
- EIP:前面的IP是是实模式下的指令指针,实模式为绝对地址,所以IP是16位。这里EIP是保护模式下的指令指针,保护模式为线性地址,所以EIP为32位。注意EIP中的内容和IP是一样的属性,也是偏移量,所以指令的地址还是EIP的内容加上段基址。
具体过程
-
计算机加电前的状态: 我们知道内存是由DRAM和ROM芯片制作而成的。其中DRAM占主要部分,DRAM是断电易失性芯片,也就是断电之后内存的DRAM区没有任何数据。我们CPU要执行程序中的指令来完成一系列的动作,那么加电开机时执行的指令来自哪里呢?就是来自ROM,ROM是只读存储器,断电不易失,所以开机时只有这里面有数据与指令。其中,BIOS就是存放在ROM中的。所以,我们开机之后要到ROM中读取BIOS的指令。
-
怎么执行BIOS: 是由0xFFFF0来执行,也就是说是由硬件来执行的。具体来说是加电后,CPU中的CS和IP分别被强制置为0xFFFF和0x0000,这样CS:IP就指向了0xFFFF0这个地址,也就是我们BIOS程序的起始地址,也可以同样说是BIOS程序第一条指令的地址。CPU根据地址取出指令,然后执行下去。
-
BIOS程序任务1:在内存中加载中断向量表和中断服务程序 BIOS首先会在内存的开始位置0x00000(注意实模式下只能寻1MB内存空间)用1KB的空间,即0x00000~0x003FF这段空间构建中断向量表。紧接着在后面花256B构建BIOS数据区(0x00400——0x004FF)。大约在距离起始位置56KB处(0x0E05B)加载了8KB左右与中断向量表对应的中断服务程序。
- 注:中断向量表中大约有256个表项,也就是记录了256个中断服务程序,一个表项占4B,构成方式是CS:IP,也就是前2B记录的是CS,后2B记录的是IP,根据我们前面所说的方式可以寻到实模式下的任一地址,当然这里的地址就是中断服务程序的地址了。
-
BIOS程序任务2: 负责自检和程序服务请求,不详述。
-
加载OS内核并为保护模式做准备: 完成上面的任务1之后,接下来1.先由中断int 0x19h的中断服务程序将磁盘的引导扇区的内容bootsect读入内存 2.执行bootsect中的代码再将其后的4个扇区(setup区)和随后的240个扇区(Kernel的代码)的内容读至内存注意我们说的这些扇区在启动盘也就是磁盘中是连续,但是并不是将他们也加载到连续的内存空间中。
-
在启动盘中连续
- 详细描述如下:
-
- 加载引导扇区bootsect:引导扇区是磁盘的第一个扇区(512B),即磁盘0号柱面,0号盘面(也可以说叫0号磁头),1号扇区,也叫主引导记录MBR。经过我们上面说的任务1,2之后,由于我们把磁盘作为启动设备,硬件会让CPU接收到一个int 0x19h中断请求,并传递中断类型号。中断类型号就是19h中断在中断向量表的物理位置。根据这个物理位置找到中断向量表的表项,利用CS:IP获得该中断服务程序的入口地址0x0E6F2(19h中断的中断服务程序入口地址),然后转而执行该中断服务程序将磁盘的第一个扇区内容读入内存。最后是加载到内存的0x07C00处还有要注意的是:这个中断服务程序是BIOS设计好的,与我们使用的OS无关,无论OS是何,该中断服务程序都只是将磁盘的第一个扇区内容读入内存。
-
- 引导扇区的内容是由汇编语言写成的程序,全称为bootsect.s(简称为bootsect),扩展名s代表汇编语言文件,我们也叫他为引导程序。然后我们就要执行引导程序中的代码将setup和kernel(即OS内核代码)调入内存,但是这里面内存规划的问题,因为你不能出现内存数据覆盖的情况。实模式下,寻址范围1MB,这里我们给出实模式下内存的规划。如下图:

- 上图所示的4位16进制实际上是CS(CPU中的CS,处理这个段时CS会被赋值相应的值)的内容,在bootsect.s的代码中,他们会设置不同段的CS,比如设置加载程序的CS为0x07C0,setup区的CS为0x9020,kernel区的CS为0x1000;而他们的IP都是0x0000。所以上图的他们的实际地址是0x07C00,0x90200,0x10000。不过因为PC始终是0x0000,所以不同段的位置也和他们的CS值位置相同。所以看懂上图就可以。

- 上面就是设置每个段CS的代码,BOOTSEG=0x07C0,就是说bootsect的代码区的CS=0x07C0.
-
- 现在我们bootsect已经调入了0x07C00处,我们要知道一个前提,就是BIOS和OS通常是不同团队制作的,所以为了更好的工作,必须协调他们之间的关系,一般我们采用的是“两头约定”和“定位识别”。两头约定对应OS设计者而言,约定就是他们必须要把最开始执行的程序(就比如bootsect)放到磁盘的第一个块中,其余的程序可以按照系统设计者来决定后序程序放在哪些扇区中。对于BIOS设计者而言,约定就是,好比19h这个中断,他就是负责将第一个扇区内容读到0x07C00处(定位识别),至于读进来的是什么,有什么内容他不管。所以说由于两头约定和定位识别,Linux的bootsect“被迫”加载到0x07C00处,现在我们又根据Linux的需求要把bootsect从0x07C00移到0x90000处了。其实就是从上面的BOOTSEG(0x07C0)移到INITSEG(0x9000,是3个0).
-

- 这里需要一些汇编知识,1,2是把BOOTSEG的内容写到ax,然后再把ax内容写到ds中,3,4也是一样,把INITSEG内容写入es中。5是指要移动的数据的大小,256个字,也就是512B,正好一个扇区的大小。6,7是自己减自己,将si和di置为0。然后ds:si(0x07C0:0x0000)构成了源地址0x07C00,es:si(0x9000:0x0000)构成了目的地址0x90000。89其实写乱了,他们应该是在一行的,即rep movw,这句代码是用来复制的,这里movw是移动字,前面的蚕食是256,也就是移动256个字。。

- 更加完整的代码,可以看到复制代码之后还有一条jmpi指令,go给出了IP的内容,INITSEG给出了CS的内容。所以go是偏移量。这个偏移量是相对于起始也就是bootsect开头的偏移量。

- 为什么需要这条jmpi指令呢,原因是这样的,当上面的rep复制完之后,内存里面有两段bootsect的代码了,而我们现在要到新的位置去执行后序代码了,也就是到新的位置执行后面的mov,而go正是这条mov相对于bootsect起始部分的偏移量。所以就jmpi指令设置了CS为新位置起始地址,go为偏移量。这样CS:IP就可以执行新位置的MOV了。
- 慢慢更,写累了
相关文章: