OS——从开机加电到执行main函数之前的过程(Linux 0.11)

明确几个概念

  • Intel的CPU采用的都是x86架构,我们这里讲到的CPU是80X86系列机的CPU。
  • 8086:典型的16位机,寄存器最大长度,数据总线的宽度,ALU能处理数据的位数都是16位。它采用的寻址方式是我们后面称之为实模式的寻址方式,即通过段地址:偏移地址的方式寻址。这样的形式也叫做逻辑地址,经过线路将段地址扩充4位,然后加上偏移地址,我们得到了一个20位的地址送至地址线,所以8086地址线只有20位,寻址空间1MB.
  • 80286,80386:从286开始,CPU就有了两种工作模式,称之为实模式和保护模式,原因是后面的机型向前兼容所设计的。
  • 8086CPU寄存器全家福:在这里插入图片描述
  • 上图AX,BX,CX,DX,是通用寄存器组的数据寄存器。他们有共同的特点,以AX为例,AX是16位寄存器,但是他的值又是两个寄存器AH,AL的值。AX的高8位是AH(high)的内容,低8位是AL(low)的内容。其他BX,CX,DX一样。
  • 段寄存器有DS,ES,SS,CS.偏移寄存器有,IP,SP,BP,SI,DI。 CS:Code Segment Register,代码段寄存器,由于指令和数据都是以二进制存放在内存中,但是CS:IP的内容指向的地址,里面存放的始终是指令。
  • BIOS:基本输入输出系统。它是存放在内存的固件,也就是ROM区。具体功能下面再说。
  • CS:Code Segment Register,代码段寄存器,存放地址信息,指向CPU当前执行代码段所在的区域。补充:D(Data)S就是数据段寄存器,S(stack)S:栈段寄存器,E(extra)S:附加段寄存器
  • IP:Instruction Pointer,指令指针寄存器,存放的是当前要执行的指令在我们这个代码段(CS指向的)中的偏移量。所以他与CS相结合,可以表示我们要执行的指令在内存中的地址。
  • x86CPU的两种模式:①实模式:实模式下寻址范围为1MB,也就是只需要20根地址线,而他们的寻址方式采用的是段基址:偏移地址的方式。只不过计算方式不同于保护模式。比如形式为CS:IP,是将CS左移4位,然后加上IP的内容即可。这里CS左移不是逻辑或者算术移位,它是带扩充的。就是0xFFFF,左移4位为,0xFFFF0,低位扩充了4位。所以假设CS=0xFFFF,IP=0x0000。那么他们所表示的指令地址为0xFFFF0+0x00000=0xFFFF0.②保护模式:寻址范围为4GB,地址线32根。实模式和保护模式是通过修改CR0寄存器中的PE位来实现的。
  • 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的代码)的内容读至内存注意我们说的这些扇区在启动盘也就是磁盘中是连续,但是并不是将他们也加载到连续的内存空间中
  • 在这里插入图片描述 在启动盘中连续
  • 详细描述如下
    1. 加载引导扇区bootsect:引导扇区是磁盘的第一个扇区(512B),即磁盘0号柱面,0号盘面(也可以说叫0号磁头),1号扇区,也叫主引导记录MBR。经过我们上面说的任务1,2之后,由于我们把磁盘作为启动设备,硬件会让CPU接收到一个int 0x19h中断请求,并传递中断类型号。中断类型号就是19h中断在中断向量表的物理位置。根据这个物理位置找到中断向量表的表项,利用CS:IP获得该中断服务程序的入口地址0x0E6F2(19h中断的中断服务程序入口地址),然后转而执行该中断服务程序将磁盘的第一个扇区内容读入内存。最后是加载到内存的0x07C00处还有要注意的是:这个中断服务程序是BIOS设计好的,与我们使用的OS无关,无论OS是何,该中断服务程序都只是将磁盘的第一个扇区内容读入内存。
    1. 引导扇区的内容是由汇编语言写成的程序,全称为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值位置相同。所以看懂上图就可以。
  • 加载bootsect:
  • 在这里插入图片描述
  • 上面就是设置每个段CS的代码,BOOTSEG=0x07C0,就是说bootsect的代码区的CS=0x07C0.
    1. 现在我们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了。于此同时,移到新的地方,各个寄存器自然也要发生变化,CS就是0x9000,现在说DS,SS,ES.
  • 在这里插入图片描述
    -上述代码主要是通过ax寄存器将DS,SS,ES全部置为0x9000 ;同时将栈放置到0x9FF00处,也就是栈底在0x9FF00处,借助的方式是将栈段寄存器SS置为0x9000,栈顶指针SP置为0xFF00;这样SS:SP就指向了0x9FF00,即我们的栈底。
    在这里插入图片描述
  • 如图这就是一个大致的情况,栈顶在0x9FF00处,栈顶指针是向地址减小的方向移动。有些书上以堆栈来命名栈,我们这里是把堆和栈区分开来说,堆(heap)就是malloc函数创建,free函数释放的动态内存空间。现在从加载bootsect,到复制bootsect到0x9000处已经完毕,下面就是加载setup了。抱歉这里写着写着就跟记流水账一样了
  • 加载setup到内存: 加载setup程序的四个扇区就要使用到BIOS提供的int 0x13h号中断的中断服务程序(磁盘服务程序)了。这个中断服务程序的执行和前面的19h号中断服务程序的执行不同,前面的19h中断服务程序是由BIOS启动执行的,而这里的13h中断服务程序是由Linux自身的启动代码bootsect执行的。前面也说过19h中断只管把磁盘的第一块扇区加载到0x07C00处,而我们这里的13h不同,他可以由OS的设计者来决定将指定扇区的内容加载到内存的指定位置。因此,13h的中断服务程序在执行前,要事先传递指定的扇区位置和内存位置信息给中断服务程序,也即传参。下面给出代码与解析:
  • 在这里插入图片描述 从载入setup模块讲起
  • 我画红线的位置是给cx赋值0002h,这里的cx的内容就是当作cl,即读磁盘的开始扇区,所以我们是从2号扇区开始读的(1号扇区放的是bootsect)。bx赋值为0200h,意思是偏移量为0x0200;然后ax被赋值为0x0200+SETUPLEN,SETUPLEN就是我们要读的扇区的个数,我们要读4个,所以SETUPLEN=0x0004,所以ax的值为0x0204,ax的前8位我们是用作AH的取值,AH是13H这个中断的功能号,一个中断服务程序也是有许多功能,下面给出13h中断服务程序的部分功能描述。
  • 在这里插入图片描述
  • 如图所示,我们看到13h中断的功能号为02h,表示读扇区,而我们这里正是要读扇区。ax的后8位(04h),就赋值给了AL,表示读4个扇区。然后的int 0x13标志执行13h号中断。注意,之前我们说了bx被赋值为0x0200,而在前面移动bootsect的时候我们就将ES赋值为了0x9000,现在es:bx就构成了一个地址,这个地址是setup程序放入到内存的起始位置,也就是0x90200,我们发现这不就刚好在我们移动后的bootsect代码区的后方吗,之前bootsect移动到了0x90000处,又是移动512B,所以bootsect的结尾是在0x901FF处,下一个地址就是0x90200,setup就到这儿来了。现在setup程序也加载入内存了。然后就是加载sysyem模块了,加载system模块是由bootsect.s中的子程序read_it来完成的。
  • 在这里插入图片描述
  • 如上图所示,call read_it就是无条件转移指令,转去执行read_it子程序。
  • 加载kernel(system)模块: 在read_it子程序的代码中,加载system模块还是用13h号中断,前面也说了,它是磁盘服务程序程序。跟读setup一样,AH也就是功能号还是02H,只不过读入的扇区数AL=F0H(240个,大约120KB),cx值为6(0x0006),system模块调入内存的起始地址为0x10000.大致就是这样,就是修改了要传递的一些参数。
  • 在这里插入图片描述
    图示就是system模块加载到内存的位置。
    注意在加载system模块时,时间较长,用户可能会认为系统出问题了,所以在屏幕输出提示信息也是有必要的,比如“Loading system…”。注意此时还没有执行操作系统的main函数,所以字符串的输出不是printf这个C语言语句完成的,它是由bootsect的汇编语言代码实现的。
    确认根设备号 上面的system模块加载进来之后,bootsect的任务基本完成。还剩一点小事,就是再次确认一下根设备号。如下图所示
    在这里插入图片描述
    可以了解一下,不必细究。经过一系列检测之后,发现磁盘为根设备。就把根设备号保存在ROOT_DEV之中,对后面的根文件系统由很大作用。注:Linux 0.11的文件系统管理方式需要一个根文件系统,其他的文件系统挂在这个根文件系统上,所以不是同等低位。因此说Linux 0.11没有提高在设备(磁盘)上直接创建文件系统的工具,因此我们要在现成的文件系统(根文件系统)利用工具(类似FDISK)去创建一个文件系统,然后加载入内存,挂在根文件系统上。故Linux 0.11的启动需要两部分数据,即内核镜像和根文件系统。
    至此,bootsect的任务全部完成了,现在就是要去执行setup的代码了,我们通过执行jmpi 0,SETUPSEG来跳转到setup的代码段处执行,执行完这条代码后,CS=SETUPSEG=0x9020,IP=0x0000,CS:IP就指向了地址0x90200,我们就正式开始执行setup的代码了。
    累了,慢慢更
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值