系统启动时的故事

前言

其实这里的系统,偏向于Linux系统。写这篇文章的原因是,我在看牛客网的腾讯面经时,无意间发现了这个问题。这个问题于我应该是没问题的,这里我给大家科普一下,从按下电源开机键,到最后系统启动成功,这里面究竟发生了什么。

一点基础知识

程序究竟是如何运行的呢?CPU的电路被设计成只能运行处于内存中的程序,因此,一个存放在磁盘中的程序想被运行起来,那么一定需要被加载器加载到内存中,然后将CPU的指令寄存器cs:ip指向这个程序在内存中的起始地址,之后CPU就开始执行程序了。别害怕,程序加载器其实也就是几个函数组成的东西,本质上就是操作磁盘然后将内容复制到内存(当然还有一些与进程创建相关的东西,但是咱们不深究)。
CPU如何访问内存呢?显然是通过地址总线访问。早期8086只有20根地址线,那么能访问的地址范围是2^20=1048576Byte=1M。这么小???是啊,确实很小。那是不是CPU往地址总线上放0x0000就能访问内存中0x0000的地址呢?不全是。在计算机中,并不是只有咱们插在主板上的内存条需要通过地址总线访问,还有一些外设同样是需要通过地址总线来访问的,比如rom,比如显存,等等。因此,将这1M空间中的一部分预留出来,给这些其他外设使用,剩下的再还给内存条。
那么我很好奇,这1M内存究竟长啥样呢?
这里我引用@csdn sswillisss所写的一篇博客,介绍1M低地址空间的分布情况:点我
大家看一下,从地址0xF0000到0xFFFEF,这不到64KB的地址上,映射的就是BIOS程序。(BIOS程序是放在主板的ROM中的,ROM掉电保存)。
磁盘是什么结构呢?没关系,这个东西还是很重要的,咱们了解一下:
一张磁盘有正反两面,其中一圈一圈的灰色同心圆就是一个磁道,将磁道划分成一条一条的扇形,这就是一个个扇区,一般扇区大小是固定的,是512Byte。(白色的是空白区)
在这里插入图片描述
而一个硬盘,通常是由多个这样的磁盘所组成:
在这里插入图片描述
柱面其实就是虚拟出来的东西。磁盘读取数据时一般是先将一个磁柱的0磁道~最后一个磁道读取完成,然后再换道(因为同一磁柱的读取,不需要换道,只需要更换使能不同的磁头即可),所以咱们争取将同一份数据的各个部分保存在一个磁柱内。
编码格式是:0磁面,1磁面(正反),2磁面,3磁面…而磁道的划分是从外到内,依次是0磁道,1磁道…而扇区的划分是从1开始划分的,1扇区,2扇区…
当然上述描述方式是CHS方式,还有一种LBA方式:其实显而易见,CHS的描述方式是一种三维的,随着磁盘容量增加,外环的数据密度比内环低,那么我肯定希望将外环的扇区数量增大一些。这就对CHS的描述方式造成了困难。于是LBA方式就将三维的描述降低到一维线性描述,即都是逻辑扇区,如逻辑扇区0,逻辑扇区1,逻辑扇区2…而逻辑扇区对应的物理地址是哪里呢?硬盘控制器帮我们实现逻辑编号到物理地址的转换,妙啊!

开机第一棒:BIOS

BIOS,即basic input output system,即基本的输入输出系统。这个BIOS程序是开机后自动执行的第一个程序。这个BIOS程序其实是存放在主板的ROM中的,然后呢,被映射到内存的0xF0000到0xFFFEF这块地址上。你的小手刚一按电源开关键,CPU的指令寄存器就强制写入0xF000:0xFFF0。???为啥是这么个奇怪的地址呢?其实吧,内存中0xFFFF0到0xFFFFF这16Byte地址,存放的是,BIOS在内存中的入口地址。其实就是存放了一句指令:
jmp f000:e05b
也就是说,硬件开机时自动将Cs:ip指向0xFFFF0处,而这里存放一句跳转指令,跳转到实际的BIOS开始地址处。
为啥段基地址是0xF000?为啥不是0xF0000呢?这就涉及到实模式的寻址方式了。当时虽然地址总线设置成20bit,可是存放地址的寄存器却是16bit的!!!那完了,那肯定没法直接通过段基+段偏移,覆盖所有的1M内存空间。咋办?硬件工程师们给出了解决办法:自动将段基地址向左偏移4bit,这不就够了嘛!
然后就是运行BIOS程序咯!BIOS做点硬件检测,如检测内存,显卡等外设,初始化硬件。之后呢,BIOS还会将低地址1M的0x00000~0x003FF这1KB空间,构造成一个中断向量表,并完善一些中断例程。
最后一件事情,BIOS会检测启动盘的0盘0道1扇区内容。
至此,BIOS的使命结束了。

开机第二棒:MBR

MBR是什么呢?它叫主引导记录。我们的操作系统一般可都是先存放在磁盘中的,可是究竟如何找到它并将它加载到内存中呢?如果有多个操作系统咋处理呢?这些其实就是MBR做的事情。MBR是磁盘中的一段长度在512字节内的代码,它被BIOS加载到低地址内存1M中的0x7C00起始处,截止在0x7DFF,一共是512Byte的内存中,并执行之。
OK,接力棒交接完成!
MBR扇区里究竟有啥呢?
在这里插入图片描述
这里包含了MBR代码,和4个磁盘的主分区信息。最后还有个MN用来校验。现在,我们来看一个MBR磁盘分区记录的实例:

80 01 01 00, 0B FE BF FC, 3F 00 00 00, 7E 86 BB 00

其中, “80”是一个分区的激活标志,表示系统可引导;“01 01 00”表示分区开始的磁头号为01,开始的扇区号为01,开始的柱面号为00;“0B”表示该分区的系统类型是FAT32,其他比较常用的有04(FAT16)、07(NTFS);“FE BF FC”表示分区结束的磁头号为254,分区结束的扇区号为63、分区结束的柱面号为764;“3F 00 00 00”表示首扇区的相对扇区号为63;“7E 86 BB 00”表示总扇区数为12289622。
哦吼,这样一来,MBR就可以快速的检索4个分区是否可引导,可以的话就跑过去,然后将控制权移交给下一棒。
别着急呀,你难道没发现有个地方很奇怪么?卧槽,凭什么我的磁盘只能划分四个分区?当然不会啊,那咋办?扩展分区了解一下?
扩展分区其实就是为了解决当初MBR设计上的缺陷(MBR这种东西就应该被淘汰?兼容还是要兼容的鸭),理论上可以存在无数个扩展分区,将每个扩展分区的头一个扇区拿出来作为EBR使用。EBR也是512字节,因此结构划分上与MBR类似,前400多字节为空,然后中间64字节分成4部分,第一部分指向自己的分区的内核加载器(哇哦,这是用来加载操作系统内核到内存中的!!!),第二部分指向下一个扩展分区的EBR。第三部分和第四部分没有使用到。
有一说一,我都有点不太想用MBR分区管理方式了。有没有更棒的分区管理方式呢?有啊,GPT了解一下?
在这里插入图片描述
结构就是上面这样,它可以兼容MBR分区方式,而支持GPT分区的程序会自动跳过MBR扇区,找到GPT分区信息(包括GPT头部和分区表)。
与MBR相比,GPT的优点简直不要太多!
1.GPT可管理的空间近乎无限大,假设一个扇区大小仍为512字节,可表示扇区数为,算下来,可管理的硬盘容量=18EB(1EB=1024PB=1,048,576TB)(因为LBA支持到了64位逻辑)
2.自带保险,由于在磁盘的首尾部分各带一个GPT表头,任何一个受到破坏后都可以通过另一份恢复,极大地提高了磁盘的抗性
3.循环冗余检验值针对关键数据结构而计算,提高了数据崩溃的检测几率

OK,anyway,我们的MBR成功找到了分区中可以执行的内核加载器,于是MBR就将内核加载器程序搬运到内存低地址1M空间中。我们随表找个地址好了,就0x900挺不错的。这没啥讲究,空间合理安排就好。

开机第三棒:内核加载器

内核加载器现在已经被MBR搬运到低地址1M空间中了,现在内核加载器准备搬运内核程序了,对么?肯定没有这么简单呀!内核加载器确实需要加载内核,加载完了就别的啥也不干了?那也不是,还是要再做点其他事情的。
先说一下,内核究竟是啥。反正本质上讲,它就是个可执行程序。在此之前,我还得给各位介绍一下什么是elf。
Windows下的可执行文件格式是PE(不是exe啊,那只是扩展名),而Linux下的可执行文件格式是ELF。当然您可千万别误会,别觉得elf格式就是Linux的可执行文件格式,错了。不仅如此,像汇编阶段的.o文件,最后链接阶段生成的.bin文件,还有.so这种动态库文件,都可以是elf格式。在这里插入图片描述
这张表刻画的太完美了。elf格式文件从两个角度描述,一个是链接角度,一个是执行角度。左侧链接角度看,单位是sections,下面有个sections的总表;右侧执行角度看,单位是segments(其实关于sections和segments,其实很好理解,sections是程序员们自己划分了,用来表达这是一个逻辑块,而许许多多的逻辑块最后要整合成几个segment,比如我写了一个sectionA是代码块,另一个sectionB也是代码块,于是编译器会认为这两个section可以组合放在一起,就出现了segment,当然了,你也可以定义许多的数据区section,最后也可能会被编译器组合成一个大的数据部分segment),其头部有个segment总表。最后是两种角度下都有的elf header。
再说多了我怕你觉得恶心想吐,咱们直接进入正题:elf格式的可执行文件被内核加载器从磁盘加载到内存中,那么我该如何执行它呢?
很简单,咱们现在是要执行它,所以这个elf格式的bin文件咱们就得从右侧看,通过elf header的一些信息,找出所有的segments,并将其搬运到低地址空间的新的地方,再将cs:ip指向这块地方即可。(有点类似于解压缩一样)
就这?
显然内核加载器不止做了这些工作。那它还干啥了呢?实模式和保护模式了解一下?这个地方的内容感觉有点多,展开的话会稍微浪费你的一点时间。开始。
最早的8086时代,地址线是20bit,而CPU的寻址方式是16位的段基+16位的段偏移量(当然硬件会帮咱们讲段基地址自动左移四位)。这种寄存器是16位的工作环境其实就是实模式。“实”在哪里?你想啊,程序想访问哪个内存地址,就直接可以通过“段基地址+段偏移量”的方式完成,那么岂不是说随随便便的一个程序都可以访问甚至修改内存么!于是,缺点就暴露出来了:
1.实模式这种指哪打哪的寻址方式,使得系统的安全性无法得到保障;
2.A程序用到了某一块地址,而B程序也想用,可是已经被A抢先一步,于是B程序无法得到执行,即不利于多任务的支持。

没办法了,改呗。于是中间经历了80286(这个东西有点不伦不类,咱们就不仔细去研究了)

80286以后(80386以及以后),就发展出了保护模式和虚8086模式(其实就是虚拟的在保护模式中运行实模式,可以暂时不去理会)。那么保护模式,究竟保护的是什么呢?其实本质上讲,保护的,就是进程的独立的地址空间。也就是说,它改变了CPU寻址方式,以前是指哪打哪,现在不了,现在委婉一点,搞了个叫段描述符和全局描述符表GDT的东西。在这里插入图片描述
以前实模式下段基址放在段基址寄存器中,现在咱们将段的一些信息(包括段基地址以及一些访问权限,最大内存范围等等)都封装成一个64bit的段描述符,并放到GDT中(GDT对所有进程开放)。以后我们想在进程中做一些寻址操作时,就不再是段基地址+段偏移量了,而是先用一个叫“段选择子”的东西,这个东西你可以基本看做是个偏移量(啥偏移量?GDT内的偏移量),用来在GDT中找到段描述符,进而找到段基地址,然后,段偏移量还是可以咱们直接提供的。(放心,段描述符中有最大内存访问限制的)
在这里插入图片描述
于是呢,保护模式下的内存访问方式变成:
选择子---------->GDT下的段描述符(获取段基址)----------->物理地址。
在这里插入图片描述
肯定没完呀,保护模式下的分页功能了解一下?
什么是分页功能呢?我这么说吧,A进程想访问实际的物理内存的0xF0000000,B进程也想访问实际的物理内存的0xF0000000,咋办?你虽然搞了GDT,但是你还是没有阻止内存打架的事情发生啊!分页功能就出现了。分页功能上来就说,“你们搞出来的所谓的地址,都是虚拟地址!必须还得经过我的一手转换,才能最后变成实际的物理地址!”这怎么可能实现呢?其实还真的好实现。我不得不说,这又是一个马蜂窝,还得花些时间讲清楚里面的道理。在这里插入图片描述
上面这张图是一个很漂亮的二级页表的东西。刚刚你不是算出来一个还不错的地址嘛,32bit对吧,先将高10位作为一级分页表的偏移量,找到对应的二级分页表的入口地址,然后再将那个地址的中间10bit作为二级分页表的偏移量,找到实际的一块(4K)的起始物理地址,最后再加上最后的12bit,作为实际的物理地址,完成了虚拟地址到逻辑地址的转换。
这里我是将所有的用户内存(差不多3G)全部拿出来,然后用一个map管理这些内存(4K为一个单位,然后一个bit表示这个4K是否被占用,也就是说我这个map最大也就不到100KB)这个物理内存管理表可是全局唯一,不管是用户进程还是内核进程,都用这一个表,所有的内存申请都要在这里留下浓墨重彩的一笔(这就防止不同的进程在内存上打架了)。
刚刚咱们说的二级分页表是将输入的32bit地址逐层扒皮,最后得到一个实际的物理地址,如果逐层扒皮的过程中发现,卧槽,二级分页表里没有对应到实际的物理内存咋办?那就通过全局唯一的物理内存管理表,申请一块4K的内存,留下浓墨重彩的一笔,然后带走地址,放到你的二级分页表中。
我不得不说实在是漂亮,就很漂亮,每个进程都觉得自己拥有了全世界。
你现在知道了如何通过虚拟地址得到物理地址,那你知道如何通过虚拟地址得到一级分页表的指针和二级页表的指针嘛?(这话说的有点绕,其实就是说,你是否可以获取到一级分页表的物理地址以及二级分页表的物理地址?)为啥要获取这个东西呢?我能通过他俩找到对应的物理地址不就完了?等你也自己实现一遍OS,你可就不会这么说了。
这里在一级分页表处有一个很棒的操作:将一级分页表的最后一个表项改为存放指向自己这个一级分页表的指针。
于是访问的方式变成了:前面10个1,中间10个1,后面12个0,这就能得到一级分页表的物理地址了。如果想访问二级分页表呢?也很简单,前面10个1,中间是正常的原先的前10位,最后12位是正常的原先的中间10位。
有种齿轮滑档的感觉,你体会一下?

开机第四棒:内核

终于算是跑到内核了。至于内核做了啥,这就不是本文的介绍范围了,想了解内核里有哪些东西,等下次再更,谢谢。

总结

以上就是Linux系统上电启动到内核程序被加载后,中间经历的一些流程,欢迎批评指正。

猜你喜欢

转载自blog.csdn.net/weixin_44039270/article/details/106598265