按下电源按钮后4.98秒发生了什么?好吧,这似乎是很多人都非常想了解的问题。有时候他们很疑惑,为什么这么一个看似简单的问题却找不到直截了当的答案呢?问得好,我不知道为什么会这样,但我猜是因为:第一,不知道是怎么回事的人太多了,只能从教科书上含糊其辞的说几句。第二,知道答案的人一定是大牛,大牛要么不回答这个问题,要么干脆不回答这个问题。至于我,我觉得我就在中间,现在我真的很想分享我所知道的,所以在这里你可以找到答案。我想当你搜索这个问题的答案时,你找到的大部分是这样的描述:BIOS将控制权转移到“启动顺序”中的第一个存储设备:硬盘。然后在硬盘中寻找主引导记录所在的分区。这个分区告诉计算机操作系统在哪里,并将操作系统加载到内存中。然后就可以看到经典的启动界面,启动过程就完成了。这描述简直太神奇了,为什么BIOS主宰着这一切?启动顺序怎么调用呢?为什么这个分区加载到内存中,它如何告诉计算机操作系统在哪里?受不了这么魔性的描述,不得不说清楚。首先,要学一些东西,必须有一个先在的知识,我们把它看作是已知的。我不可能从原子的组成说起原理。学习电脑开机流程需要哪些必备知识?请大家知道以下几点:内存是存放数据的地方,给定一个地址信号,内存可以返回该地址对应的数据。CPU的工作方式是不断地从内存中取出指令并执行。CPU从内存中的哪个地址取指令是由一个寄存器中的值决定的,该寄存器会被连续+1操作,或者它的值由跳转指令指定。那么,你只需要知道这三个前提,就可以专业的讲解电脑的启动过程了。1、为什么BIOS占优势?据说开机后,BIOS开始运行自己的程序,进行硬件自检,载入引导区。我不服气,为什么开机后运行的是BIOS里的程序?为什么它不在内存中?为什么它不在硬盘驱动器上?好了,不要怀疑前置知识,CPU的工作方式就是不断的从内存中取指令执行,那为什么说是执行BIOS中的程序呢?这就不得不说到内存映射了。2、内存映射CPU地址总线的宽度决定了可访问内存空间的大小。例如16位的CPU地址总线宽度为20位,地址范围为1M。32位CPU地址总线宽度为32位,地址范围为4G。可以算出我们目前64位机器的地址范围。但是这么大的可访问内存空间并不代表全部用于内存,也就是说可寻址的对象不仅仅是内存,还有一些外设必须通过地址总线才能访问,那么如何访问这些外设呢?就是在地址范围内划出一块区域,这块用于显存,那块用于硬盘控制器,等等。这样一来就不符合我们的预知,所以会出现一种不正确的理解方式,即内存中这个位置是显存,那个位置是硬盘控制器。当我们在相应的位置进行读写时,就相当于在显存等外设的相应位置进行读写,就好像这些外设的存储区域映射到了内存中的某个区域。这样一来,我们就不用操心那些外设了,重点还是简单的内存。这称为内存映射。太好了,现在可以用简单的前置知识来解释了,我们继续往下推。3、实模式下的内存分配刚刚提到内存是为各种外设划分区域的,那么问题自然就来了,哪个区域分配给哪个外设呢?如果是规定的话,最好有一张桌子。嗯,没错,真的有,就是实模式下的内存分配,作者给它画了一张图:在这里插入图片描述哦,我真是个小天使,我已经显示了比例,我可以找到网上的比例如果我比较直观,请给我留言。后面会解释实模式,现在简单的理解就是电脑刚开机时只有1M内存可用。我们看到内存被各种外设划分,也就是映射在内存中。BIOS比较狠,不仅它的空间映射到内存0xC0000-0xFFFFF这个位置,它里面的程序也开始占一些区域,比如把中断向量表写在内存的开头,所以——叫先到先得。4.如何从BIOS执行程序?现在我们知道BIOS中的信息映射到内存位置0xC0000-0xFFFFF,而最关键的系统BIOS映射到位置0xF0000-0xFFFFF。如果我现在说CPU开机就执行这块区域的代码,然后巴拉巴拉运行一小会就开机,你肯定喷我,为什么会执行到这里,为什么不从头开始呢?这就自然而然地引出了一个猜测,我们需要用到另外一个前置知识,即CPU从内存中的什么地方取到并执行?是PC寄存器中的地址值。BIOS程序的入口地址,即起始地址为0xFFFF0(人家就是这么写的),也就是电源键按下的时候,一定有神通将pc寄存器中的值改为0xFFFF0,然后CPU就开始不停的跑起来了。没错,下一句可能就是你寻找已久的答案,请做好心理准备:在你开启CPU的那一刻,CPU的PC寄存器被强制初始化为0xFFFF0。具体来说,CPU初始化段基地址寄存器cs为0xF000,初始化偏移地址寄存器IP为0xFFF0,根据实模式下的最终地址计算规则,将段基地址左移4位,加上偏移地址,得到最终的物理地址,即抽象出的PC寄存器地址为0xFFFF0。当我在学习这块知识的时候,看到这句话解开了我心中积攒了很久的疑惑。多么简单粗暴的道理。写到这里我也松了一口气,因为后面的过程几乎就是流水账一样的向前推了。至于怎么强制初始化,我觉得已经跨过了前置知识的界限,各个厂商的硬件实现不一定相同。方法有很多,而且非常简单。讨论意义不大。5.BIOS中写了什么程序?现在我们知道BIOS是映射到内存中的某个位置,CPU在开机的一瞬间会强制将自己的pc寄存器初始化为BIOS程序的入口地址。从这里开始,CPU就会不停的往前跑。那么接下来的问题好像问的很自然,那就是BIOS程序里面写的是什么?把所有的二进制信息都贴在BIOS程序里是不合适的,我们来分析一些主要的。我们先来猜一猜,可以看到入口地址是0xFFFF0,也就是说程序是从这里开始执行的。实模式下内存的下边界是0xFFFFFF,也就是只剩下16字节的空间可以写代码了。这有什么用?如果你有心,应该能猜到入口地址可能是一条跳转指令,跳转到更大的空间去执行自己的任务。没错,0xFFFF0存放的机器指令翻译成汇编语言:jmpfarf000:e05b的意思是跳转到物理地址0xfe05b开始执行(回忆一下前面说的实模式下的地址计算方法)。从地址0xfe05b开始是BIOS真正起作用的代码。这段代码会检测一些外设信息,初始化硬件,创建中断向量表并填充中断例程。这里的部分不要展开,这只是一个硬编码的程序,对理解引导过程没有帮助。后面再看精彩的部分,就是BIOS的最后一项工作:载入引导区。6、什么是0x7c00?该真实的地方就是真实的。我绝对不会让loading这种神奇的字眼出现在这里。现在让我们把它分解成人类的话。其实这个词并不神奇。加载在计算机领域是指将设备(如硬盘)上的程序拷贝到内存中的过程。加载引导区的过程转化为BIOS程序将引导区的内容复制到内存中的某个区域。那么问题自然又来了,bootzone在哪里呢?它在哪里复制到内存?然后呢?让我们一一回答。引导区是什么?就算你不知道,你也应该能猜到,那一定是一个满足某些特征的区域,所以人们称之为激活区,那么它必须满足什么特征呢?别着急,不知道大家有没有设置BIOS启动顺序的经验。通常有U盘启动、硬盘启动、软盘启动、CD启动等,BIOS会按照顺序读取位于0盘0磁道的启动盘。1扇区的内容。至于磁盘格式的划分,本文不做解释。简而言之,对于内存,我们可以通过给出一个数字地址来获取该地址处的数据。对于磁盘,我们需要给出磁头、柱面和扇区。只有一条信息可以定位到某个位置的数据,这只是描述位置的一种方式。那么,磁盘0、磁道1、扇区1的内容一共是512字节。如果末尾的两个字节是0x55和0xaa,那么BIOS就会认为这是一个引导区。如果没有,则继续依次在下一个设备中查找位于0盘1磁道1扇区的内容。如果最后发现一个条件都没有,那么就直接报nobootzone的错误。BIOS找到这个引导区后做什么?哦,前面说了,是loading,就是把这512字节的内容,一个bit都没有,复制到内存中0x7c00这个位置。你是怎么复制的?当然是命令。哪些指示?这里我只能简单的说指令集中有in和out,分别用来将外设中的数据复制到内存中,或者将内存中的数据复制到外设中,使用这两条指令,外设为我们提供的阅读方法可以做到这一点。此时引导区的内容已经被BIOS程序复制到内存的0x7c00位置,然后呢?这其实不难猜到。引导区的内容就是我们自己写的代码。复制到这里后,就开始执行了。之后,我们的程序就会接管下一个进程,BIOS的使命也就结束了。所以复制完之后,下一步应该就是跳转指令了!没错,就是这样,PC寄存器的值变成了0x7c00,指令从这里开始执行。嗯?不知道大家有没有发现,我们好像在不知不觉中把以前的魔法语言翻译成了人类的文字。一开始我们说:BIOS将控制权转移给排在第一位的存储设备。那么这句话是什么意思呢?即BIOS将引导区的512字节复制到内存的0x7c00位置,并使用跳转指令将pc寄存器的值指向0x7c00。你看,这里就不多说几句了,我就把这个问题解释清楚,简单明了。哦,对了,现在好像只剩下一个问题了,为什么一定要是0x7c00呢?好问题,当然答案很简单,就是BIOS开发组弄成这样了,以后不好改,不然不兼容。为什么很难改变?让我们看一个简单的引导区512字节的代码。(代码摘自《30 天自制操作系统》);hello-os;TAB=4ORG0x7c00;程序加载到内存0x7c00处;程序主入口:MOVAX,0;初始化寄存器MOVSS,AXMOVSP,0x7c00MOVDS,AX;段寄存器为初始化为0MOVES,AXMOVSI,msgputloop:MOVAL,[SI]ADDSI,1CMPAL,0;如果遇到以0结尾的字符,则跳出循环,不再打印新字符JEfinMOVAH,0x0e;指定文本MOVBX,15;指定颜色INT0x10;调用BIOS显示字符函数JMPputloopfin:HLTJMPfinmsg:DB0x0a,0x0a;newline,newlineDB"hello-os"DB0x0a;newlineDB0;0endingRESB0x7dfe-$;fill0to512bytesDB0x55,0xaa;bootabledeviceidentification我们看第一行:ORG0x7c00这个数字就是刚才说的引导区的加载位置。这行汇编代码简单的说就是把0x7c00加到后面的所有地址上。只是因为BIOS在这里加载了引导区的代码,有一个偏移量,所以所有写引导区代码的人都需要一开始就写这样的代码,否则都会串在一起。而且因为凡是写操作系统的,boot区第一行汇编代码都把这个数字写死了,那么BIOS开发者一开始设置的这个数字就不好改了,不然就得联系开发者了一个一个的操作系统,说唉,我改这个地址,你也跟着改。推另一个团队去改公司的代码,很麻烦。想想这样的推送需要多少人力。再说了,就算改了,以前的代码也不兼容了,总不能骂死人吧。再看最后一行:DB0x55,0xaa这也验证了我们之前说的512字节的最后两个字节一定是0x550xaa,BIOS会认为它是引导区并加载它,仅此而已。回过头来看,0x7c00这个值其实是一个规定值,但是还是会有人问,所以肯定有它的合理性。其实我的解释只能是人家定了这个值,后来人家给他们解释了其中的合理性。并不是说人一开始就一定是这样想的,就像我们做语文阅读理解题一样。第一个BIOS开发团队是IBMPC5150BIOS,当时第一个考虑的操作系统是DOS1.0操作系统,BIOS团队应该为之服务。但操作系统还没有出来,BIOS团队假设其操作系统所需的最小内存为32KB。BIOS希望自己加载的引导区中的代码越晚越好,这样更“安全”,不会过早被其他程序覆盖。但是,如果只剩下512字节,感觉太多了,还有一些栈空间需要保留,那就扩大到1KB。所以32KB的末尾是0x8000,减去1KB(0x400),正好等于0x7c00。哇,太精确了,这可能是解释它的一种方式。7、boot区的代码写了什么?其实写到这里,我的文章应该戛然而止了,因为原来的问题已经解决了,CPU从我们预期的位置开始不停的运行了。万事开头难,剩下的就看操作系统怎么玩了。但我觉得不够有品位,看来还有一些疑问在你的脑海里挥之不去。比如这个问题:boot区的代码写了什么?仅仅512字节就是整个操作系统的内容?这是一个很好的问题。512字节真的不能做太多。当前操作系统必须以M为单位进行计算。512字节远远不够。这是怎么回事?其实按照之前的思路我们可以猜测,BIOS用很少的代码就把512字节的引导区内容加载到内存中,并跳转到它开始执行。按照这个套路,512字节的引导区代码是不是也可以把更多存储在磁盘上的操作系统程序加载到内存中的某个位置,然后跳转到呢?没错,这就是诀窍。所以BIOS负责加载引导区,而引导区负责加载真正的操作系统内核。这是默契配合吗?由于做启动盘的盘是写操作系统的厂家制作的,俗称制作启动盘,所以他也必须知道操作系统的核心代码存放在磁盘的哪个扇区,所以启动area使用这个扇区,之后的很多很多扇区(取决于OS有多大)被读入内存,然后跳转到starting程序开始的地方。跳转到哪里?这不像数字0x7c00那样经典。不同的操作系统肯定是不一样的,不用事先指定。反正写操作系统的人应该给自己定一个。不要覆盖其他关键设备使用的区域。好的。8、操作系统的内核写了什么?BIOS程序的入口地址(一次跳转)入口地址是一条跳转指令,跳转到0xfe05b的位置开始执行(第二次跳转)。在进行了一些硬件检测工作之后,最后一步就是将引导区的内容加载到内存0x7c00,并跳转到这里(三跳)引导区的代码主要是加载操作系统内核,跳转到加载放置(四次跳跃)。连续四跳之后,我们终于来到了操作系统的世界。剩下的内容,可以说是整个操作系统课程中讲述的原理,比如分段、分页、中断的建立、设备驱动、内存管理、进程管理、文件系统、用户态接口等等.你可能在操作系统课程中或多或少地听过这些名词。如果你努力学习,你必须知道一般的原则。但是,对于笔者这样从头到尾研究过linux内核源码的铁杆狗来说,这些概念不仅仅是书本上枯燥的概念,而是在操作系统的每一行代码中都体现的淋漓尽致。有的展现了作者无比的智慧,有的让我看到了作者因硬件设置而投降。如果这篇文章引起了你对操作系统的好奇,建议你也抽空读一读,和我一起入坑,你会发现新世界的大门已经向你打开了。9.参考资料,这次真的要完结了。相信如果你真的看完了全文,你可以说对计算机的启动过程有了更具体的认识。如果您想深入了解细节,即了解流程的每一点,那就需要付出努力。
