计算机启动

计算机启动过程有哪些阶段

第一阶段:BIOS
第二阶段:MBR
第三阶段:硬盘启动
第四阶段:操作系统

第一阶段:BIOS:控制权为BIOS

概念

BIOS:BIOS是一组固化到计算机内主板上一个ROM芯片上的程序,它保存着计算机最重要的基本输入输出的程序、开机后自检程序和系统自启动程序,它可从CMOS中读写系统设置的具体信息。
CMOS:CMOS 是电脑主机板上一块特殊的RAM芯片,是系统参数存放的地方,而BIOS中系统设置程序是完成参数设置的手段。CMOS芯片由主板上的钮扣电池供电,即使系统断电,参数也不会丢失。
实模式:最早期的8086 CPU(16位)只有一种工作方式,那就是实模式,而且数据总线为16位(一次最多能取2^16=64KB数据,实模式下每个段最大只有64KB),地址总线为20位(寻址的能力是2^20=1MB,实模式下CPU的最大寻址能力),实模式下所有寄存器都是16位。8086处理器在实模式下的存储器寻址方式由段寄存器的内容乘以16作为基地址,加上段内的偏移地址形成最终内存中实际的物理地址。
这里回答一个问题:在8086/8088中,只有20根地址总线,所以可以访问的地址是2^20=1M,但由于8086/8088是16位地址模式,能够表示的地址范围是0-64K,所以为了在8086/8088下能够访问1M内存(16位寄存器怎么表示20位的寻址空间?),Intel用了分段的方法 —— segment:offset. 实际的地址是segment << 4 + offset。
保护模式:从80286开始就有了保护模式,从80386开始CPU数据总线和地址总线均为32位,而且寄存器都是32位。但80386以及现在的奔腾、酷睿等等CPU为了向前兼容都保留了实模式,现代操作系统在刚加电时首先运行在实模式下,然后再切换到保护模式下运行。

BIOS映射到内存

boot block: 当启动中计算机的时候,一开始BIOS并不会马上把Flash ROM中的内容copy到shadow memory,CPU会执行一部分代码,我们习惯把这一段代码叫做Boot Block。之所以要把Flash ROM中的内容loaded到memory,是因为当对CPU,memory进行完initialized,由于ROM的执行速度远比RAM要低,才会把BIOS shadow到memory中去,这样可以reduce POST的时间。所以我们在OS下会发现BIOS的content会shadow到memory的最顶端,举个例子,如果你是用的32bit的XP(support 4G),你就可以在FFF00000-FFFFFFFF(对1M的BIOS而言,如果是512K地址则是FFF7FFFF-FFFFFFFF)这段memory空间找到你的BIOS的content。

注:以下内容参考并摘抄自《CPU rest》
i386 CPU取的第一条指令地址与所处模式: 当CPU 抓到第一条指令,并执行的时候,如果说处于real mode,那么FFFFFFF0h的地址,早已超出了real mode CPU的寻址范围。也许很多人都讨论过这个问题。首先,可以确定的是:
此时CPU并不处于protect mode。CPU进入protect mode的标志是CR0的PE bit置1,而当CPU执行第一条指令时,此Flag bit并未置1。如果说此时CPU处于real mode, 那么此时CS=F000h, IP=FFF0h,(CS和IP的初始值应当是8086/8088-1M物理地址空间-时代编址BIOS第一条指令地址遗留下来的) 按照real mode的寻址方式, 此时形成的第一条指令的地址应该是 CS:IP=F000h 10h+FFF0h =FFFF0h, 而不是FFFFFFF0h。(有文档说, CPU第一条指令会从 FFFFFFF0h抓,而有文档说是从FFFF0抓, 那到底从那里抓呢?一切以 Intel的说法为准,Intel 说从FFFFFFF0h抓,那就是从FFFFFFF0h抓。)
CPU寻址和所在的模式无关,它只会使用内部的base address寄存器加上offset,实模式下base address是段寄存器×16,保护模式下就是加载段选择子的内容。自从386以来,CS不再是单个的段寄存器,而是包括Segment selector,segment base,和segment limit 3个register的一组寄存器。Segment Base决定着基地址的值。当 CPU 处于实模式段寻址方式时,如果段寄存器装入F000h, 那么 CPU 会自动将F000h
10h = F0000h装入Segment Base,那么最终形成的地址就是F0000h+IP, 即Segment Base+IP形成地址。然而实际情况是,CPU reset过后,虽然此时CPU的确处于段寻址模式,Segment Selector的值为F000h,但是Segment Base register的值却并没有按照段寻址模式的方式装入,而是被设置为Segment Base=FFFF0000h,按照Segment Base+IP的方式,最终形成的地址为FFFF0000h + FFF0h = FFFFFFF0h。这就是为何第一条指令会从 FFFFFFF0h抓,而不是FFFF0h抓的原因。
那么,其实我们可以说,CPU执行第一条指令时,它处于一种不普通的real mode中。
(段寻址模式:在寻址一个内存具体单元时,由一个基址再加上某些寄存器提供的16位偏移量来形成实际的20位物理地址。这个基址就是CPU中的段寄存器。在形成20位物理地址时,段寄存器的16位的数会自动左移4位,然后与16位偏移量相加,即可形成所需的内存地址。这种寻址方式的实质还是基址寻址。)

地址映射:在CPU读到BIOSCode之前,一切工作都是由HW完成,其中南桥做了一些很重要的幕后工作。在CPUreset和BIST之后,CPU将会去读取第一条指令。对于传统的 CPU + 北桥 + 南桥类型的platform来说,CPU的request通过FSB到达北桥,北桥将这个request透过ESI送到南桥。而对于最新的chipset来说,北桥和CPU封装在一颗Chip里面,所以会看到这个request通过DMI/QPI被送到南桥。Request到达南桥后,南桥根据配置决定将request route 到SPI或者LPC。南桥自己有一张特殊的address mapping table,存在两个这样的区域,第一个是从地址空间4G向下,大小从4MB到16MB不等的一个区域,以4MB为例,地址空间从FFFC00000h~FFFFFFFFh,称之为Range4G。CPU reset后会从FFFFFFF0h抓第一条指令,这第一条指令正好处于南桥的4G范围内,能享受南桥的special service。第二个受到南桥关照的地址空间,一般是从1MB向下128KB的范围,即ESegment和FSegment,从E0000~FFFFF,称之为Legacy Range,也就是说,不仅FFFC00000h~FFFFFFFFh之间的request能够被route到SPI/LPC,E0000~FFFFF之间的request也同样会被route到SPI/LPC。
CPU reset的时候,内存还没初始化,所以不可能从内存XXXX的位置读取第一条指令。CPU从来不会直接从内存中读数据,CPU的读写,都是针对自己的地址空间而言。假设一个 CPU,其地址空间大小是4GB,这个系统拥有一根1GB的内存。内存初始化过后,其地址被映射到0~3FFFFFFFh的地址空间,假设CPU要访问一个地址10000h,CPU的request首先针对自己的地址空间,然后 CPU的硬件会将这个请求转化为一个bus的请求,比如 FSB or PCI,然后内存控制器从bus上收到这样一个请求后,会确认这个请求是否在内存的地址空间,如果是在内存映射的空间内,则将其重定向到内存中,然后就由内存回复这个请求。 如果CPU访问一个地址8FFFFFFFh,当这个请求到达bus被内存控制器收到后,经过比较,这个地址不在内存映射的范围内,所以内存控制器将不处理这个请求, 然后这个请求可能最终通过北桥,到达南桥。当CPU reset时,其第一条指令请求的地址为FFFFFFF0h,此时内存并未初始化,这个请求会从bus上被转发到南桥,南桥最终将其重定向到LPC/SPI的ROM上,所以boot block阶段,所有的代码都是从ROM上读取的。这也是从概念上区分boot block/PEI和POST/DXE阶段的方法。那么,如果内存初始化过后,CPU读取FFFFFFF0h的request是否有可能被内存接收到呢?No,在内存控制器初始化的时候,会设置一些参数,保留几段地址空间的区域给flash rom、PCI设备分配使用,它确保不会将内存映射到这些地址空间上。所以内存初始化过后,地址请求FFFFFFF0h还是会被北桥转发出去,不会decode到内存。但是,如果一个PCI设备,在南桥之前收到了这个请求,并且解码了这个地址,回复了CPU的请求,那么将不会从南桥的LPC/SPI boot。

注:以下内容参考自OldLinux’s Archiver上关于内存的讨论。
8086时代 - 1M物理地址空间:机器加电成功后,CPU自设定为初试状态,开始准备运行(以实模式),此时RAM是空的。不同BIOS厂商生产的BIOS代码量不一样,因此习惯将BIOS的第一条指令编码到特定地址上(CS:IP = 0xF000:0xFFF0处),CPU则被预编程查找这个固定的位置。该位置放置一个jmp指令以跳转到真正执行的代码位置,通过jmp指定位置来适配不同数量的BIOS代码。事实上CS:IP = 0xF000:0xFFF0离实模式的极限地址0xF000:0xFFFF只有16个字节了,也就安排得开一个跳转指令和其他几条简短指令。而我们实际究竟需要使用多长的冷启动代码由jmp 0xF000:0xXXXX中的偏移0xXXXX来把握,如果使用得多,0xXXXX就小,使用得少,0xXXXX就大。BIOS冷启动代码编址到1M顶部保证启动代码尽量靠后,而不浪费多余的地址空间,由于地址空间安排在最后,也不会把整个地址空间隔离成两段。
i386时代:我们说的bios rom,当然这包括很多bios的rom,如视频bios rom和SCSI卡bios rom等,当然还包括我们传统意义上的包含冷启动程序代码以及一系列原始中断服务程序的bios的rom,由于技术的发展,这一部分rom现在变得非常大,如现在一般的传统bios rom就达2M以上,而视频bios rom等都更大。以前习惯上把这些rom的地址空间安排在640k以上1M以下的空间里(也就是说640k~1M这一段地址指向rom而不是指向ram),但这一点空间是明显安排不下现在的rom的,又为了不让这一段rom的地址将ram的地址隔离成两段,一般都将这段rom的地址空间安排在cpu所能访问到的地址的最高端。因此,为了保持对8086/8088的兼容性,所以intel采用了一个变通的方法,具体就是:
1)CPU复位后得到机器执行的第一条指令地址0xFFFFFFF0(cpu取指令时,由于是从物理上使A20~A31地址线为1,所以当cs为f000,指令偏移为FFF0时,形成物理地址FFFF0,再加上A20~A31都为1,实际得到的物理地址是:FFFFFFF0)。执行一条跳转指令jmp f000:xxxx跳转到启动代码的开始处。(段内跳转,跳转的位置仍然是rom的位置)
2)在实模式下,intel把BIOS中的冷启动代码映射到32位cpu所能寻址的4G地址空间的最后64k,把A20~A31地址线全置为1,把CS = 0xf000,这样,cpu所得到的物理地址就是0xffff0000 - 0xffffffff,即4G地址空间的最后64K。(猜测映射过程并没有复制数据到4G顶部,也就是说,可能被映射的物理地址空间没有数据但仍然无法被使用!)
3)通过执行启动代码(或者别的方法)把BIOS中的ROM内容关于实模式的代码和数据拷贝到物理地址640K~1Mb处,这样使得在实模式下,cpu也能通过动态映射访问BIOS中的部分内容。
4)执行一个段间跳转,屏蔽掉地址的A20~A31线(intel规定任何一个段间转移指令可以置触发器的输出为0),从而来到低地址空间,接着执行从rom bios拷贝过来的代码。
(内存控制器控制CPU对BIOS中ROM内容的访问到底转发到RAM还是ROM,而4G处则一直映射着全部的BIOS数据。按照《CPU rest》的分析,内存控制器初始化的时候,会设置一些参数,保留几段地址空间的区域给flash rom、 PCI设备分配使用,它确保不会将内存映射到这些地址空间上,那么复制BIOS的数据到4G处将没有多大的意义,因为也读不了,而且实模式复制数据到4G又得采取特殊的方式。)

BIOS硬件自检

注:以下内容参考自计算机是如何启动的
BIOS程序首先检查,计算机硬件能否满足运行的基本条件,这叫做”硬件自检”(Power-On Self-Test),缩写为POST。
如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。如果没有问题,屏幕就会显示出CPU、内存、硬盘等信息。

启动顺序

注:以下内容参考自维基百科-主引导记录计算机是如何启动的
当BIOS检查到硬件正常并与CMOS中的设置相符后,BIOS需要知道,”下一阶段的启动程序”具体存放在哪一个设备。也就是说,BIOS需要有一个外部储存设备的排序,排在前面的设备就是优先转交控制权的设备。这种排序叫做”启动顺序”(Boot Sequence)。按照CMOS中对启动设备的设置顺序检测可用的启动设备。BIOS将相应启动设备的第一个扇区(也就是MBR扇区)读入内存地址为0000:7C00H处,然后检查0000:7CFEH-0000:7CFFH(MBR的结束标志位)是否等于55AAH,若不等于则转去尝试其他启动设备(往往还对磁盘是否有写保护、主引导扇区中是否存在活动分区等进行检查),如果没有启动设备满足要求则显示”NO ROM BASIC”然后死机。当检测到有启动设备满足要求后,BIOS将控制权交给相应启动设备。根据MBR中的引导代码启动某个分区的引导程序。

第二阶段:MBR:控制权为满足要求的启动设备

注:以下内容参考自维基百科-主引导记录计算机是如何启动的

概念

主引导记录:主引导记录(Master Boot Record,缩写:MBR),又叫做主引导扇区,是计算机开机后访问硬盘时所必须要读取的首个扇区,它在硬盘上的三维地址为(柱面,磁头,扇区)=(0,0,1)。在深入讨论主引导扇区内部结构的时候,有时也将其开头的446字节内容特指为“主引导记录”(MBR),其后是4个16字节的“磁盘分区表”(DPT),以及2字节的结束标志(55AA)。因此,在使用“主引导记录”(MBR)这个术语的时候,需要根据具体情况判断其到底是指整个主引导扇区,还是主引导扇区的前446字节。(这512个字节的最后两个字节是0x55和0xAA,表明这个设备可以用于启动;如果不是,表明设备不能用于启动,控制权于是被转交给”启动顺序”中的下一个设备。)
BIOS POST之后call INT 19h,主引导记录本身就是中断信号INT 19h的处理程序。

MBR结构

启动代码

主引导记录最开头是引导代码。其中的硬盘引导程序的主要作用是检查分区表是否正确并且在系统硬件完成自检以后将控制权交给硬盘上的引导程序(如GNU GRUB)。它不依赖任何操作系统,而且启动代码也是可以改变的,从而能够实现多系统引导。
随着计算机操作系统越来越复杂,位于主引导记录的空间已经放不下引导操作系统的代码,于是就有了第二阶段的引导程序,而MBR中代码的功能也从直接引导操作系统变为了引导第二阶段的引导程序。例如在普通的个人电脑上,引导程序通常分为两部分:第一阶段引导程序(Boot Loader)位于主引导记录(MBR),用以引导位于某个分区上的第二阶段引导程序(Boot Loader),如NTLDR、BOOTMGR和GNU GRUB等。

分区表

考虑到每个区可以安装不同的操作系统,”主引导记录”因此必须知道将控制权转交给哪个区。分区表的长度只有64个字节,里面又分成四项,每项16个字节。所以,一个硬盘最多只能分四个一级分区,又叫做”主分区”。每个主分区的16个字节,由6个部分组成:

1
2
3
4
5
6
(1) 第1个字节:如果为0x80,就表示该主分区是激活分区,控制权要转交给这个分区。四个主分区里面只能有一个是激活的。
(2) 第2-4个字节:主分区第一个扇区的物理位置。
(3) 第5个字节:主分区类型。
(4) 第6-8个字节:主分区最后一个扇区的物理位置。
(5) 第9-12字节:该主分区第一个扇区的逻辑地址。
(6) 第13-16字节:主分区的扇区总数。

最后的四个字节(”主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2的32次方。如果每个扇区为512个字节,就意味着单个分区最大不超过2TB。再考虑到扇区的逻辑地址也是32位,所以单个硬盘可利用的空间最大也不超过2TB。如果想使用更大的硬盘,只有2个方法:一是提高每个扇区的字节数,二是增加扇区总数

下面是一个例子:
如果某一分区在硬盘分区表的信息如下

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

则我们可以看到,最前面的”80”是一个分区的激活标志,表示系统可引导[1];”01 01 00”表示分区开始的磁头号为1,开始的扇区号为1,开始的柱面号为0;”0B”表示分区的系统类型是FAT32,其他比较常用的有04(FAT16)、07(NTFS);”FE BF FC”表示分区结束的磁头号为254,分区结束的扇区号为63、分区结束的柱面号为764;”3F 00 00 00”表示首扇区的相对扇区号为63(小端序);”7E 86 BB 00”表示总扇区数为12289662(小端序)。

主引导记录的内存地址为0x7C00的原因

注:以下内容参考自Why BIOS loads MBR into 0x7C00 in x86
0x7C00这个地址来自IBM PC 5150 BIOS Developer Team,IBM PC 5150使用Intel 8088处理器,是现代x86(32bit) IBM PC/AT兼容机的祖先。DOS 1.0要求至少32KiB的内存空间,所以BIOS团队不再考虑16KB的启动情况。BIOS开发团队基于以下原因设计了0x7C00地址:
1、他们想在32KiB空间内留出尽可能多的空间来加载操作系统本身。
2、8086/8088使用0x0 - 0x3FF存储中断向量,BIOS数据存在它后面。
3、启动扇区大小是512字节,启动程序的stack/data区域需要512字节。
4、操作系统加载后,主引导记录就没有用处了,此后它所在的内存地址可以被操作系统重新利用。
5、所以,32KiB的最后1024B被选来加载MBR。计算过程为 0x7FFF - 512 - 512 + 1 = 0x7C00
操作系统加载后,内存布局如下:

第三阶段:硬盘启动:控制权为硬盘上的启动程序

注:以下内容参考自计算机是如何启动的

情况A:卷引导记录

四个主分区里面,只有一个是激活的。MBR的引导代码读取激活分区的第一个扇区,叫做”卷引导记录”(Volume boot record,缩写为VBR),并将控制权交给它。
“卷引导记录”包含了操作系统引导代码,它的主要作用是,告诉计算机,操作系统在这个分区里的位置。然后,计算机就会加载操作系统了。
NTFS文件系统的卷引导记录,该文件始终位于卷的第一个簇,其中包含引导代码(用于定位并启动NTLDR/BOOTMGR,NTLDR–NT loader的缩写,是微软的Windows NT系列操作系统的引导程序,包括Windows XP和Windows Server 2003)、BIOS参数块(其中包含卷序列号),以及$MFT和$MFTMirr所在的簇编号。

情况B:扩展分区和逻辑分区

随着硬盘越来越大,四个主分区已经不够了,需要更多的分区。但是,分区表只有四项,因此规定有且仅有一个区可以被定义成”扩展分区”(Extended partition)。
所谓”扩展分区”,就是指这个区里面又分成多个区。这种分区里面的分区,就叫做”逻辑分区”(logical partition)。在MBR分区表中最多4个主分区或者3个主分区+1个扩展分区,也就是说扩展分区只能有一个,然后可以再细分为多个逻辑分区。
计算机先读取扩展分区的第一个扇区,叫做”扩展引导记录”(Extended boot record,缩写为EBR)。和MBR结构类似,它里面也包含一张64字节的分区表,但是最多只有两项(也就是两个逻辑分区),其分区表的第一项指向该逻辑分区本身的引导扇区,第二项指向下一个逻辑驱动器的EBR,分区表第三、第四项没有用到。
计算机接着读取第二个逻辑分区的第一个扇区,再从里面的分区表中找到第三个逻辑分区的位置,以此类推,直到某个逻辑分区的分区表只包含它自身为止(即只有一个分区项)。因此,扩展分区可以包含无数个逻辑分区。
但是,似乎很少通过这种方式启动操作系统。如果操作系统确实安装在扩展分区,一般采用下一种方式启动。

情况C:启动管理器

注:以下内容参考自GNU GRUB
计算机启动后,BIOS将寻找第一个可启动的设备(通常为硬盘),而后从MBR中载入启动程序,然后把控制交给这段代码,情况C控制权给了GNU GRUB。MBR位于硬盘的前512字节内。

GNU GRUB在MBR分区表的硬盘上

GRUB第一版
GRUB的步骤1包含在MBR中。由于受MBR的大小限制,步骤一所做的几乎只是装载GRUB的下一步骤(存放在硬盘的其它位置)。步骤1既可以直接装载步骤2,也可以装载步骤1.5:GRUB的步骤1.5包含在MBR后面的30千字节中。步骤1.5载入步骤2。
当步骤2启动后,它将呈现一个界面来让用户选择启动的操作系统。这步通常采用的是图形菜单的形式,如果图形方式不可用或者用户需要更高级的控制,可以使用GRUB的命令行提示,通过它,用户可以手工指定启动参数。GRUB还可以设置超时后自动从某一个内核启动。

GRUB第二版本
与GRUB第一版相似的是,boot.img像步骤1一样在MBR或在启动分区中,但是,它可以从任何LBA48地址的一个扇区中读取,它(boot.img)将读取core.img(产生于diskboot.img)的第一个扇区以用来后面读取core.img的剩余部分。core.img正常情况下跟步骤1.5储存在同一地方并且有着同样的问题,可是,当它被移动到一个文件系统或一个纯粹的分区时会比在步骤1.5移动或删除引起更少的麻烦。一旦完成读取,core.img会读取默认的配置文件和其他需要的模块。
GRUB配置文件的文件名和位置随系统的不同而不同;如在Debian(GRUB Legacy)和OpenSUSE中,这个文件为/boot/grub/menu.lst,而在Fedora和Gentoo中为/boot/grub/grub.conf。Fedora、Gentoo Linux和Debian(GRUB 2)使用/boot/grub/grub.conf。 Fedora为了兼容文件系统层次结构标准提供了一个从/etc/grub.conf到/boot/grub/grub.conf的符号链接。
当GRUB启动后
一旦选择了启动选项,GRUB把选择的内核载入内存并把控制交给内核。在此步骤中,对于Windows之类不支持多启动标准的操作系统,GRUB也可以通过链式启动把控制传给其它启动器。在这种情况下,其它操作系统的启动程序被GRUB保存了下来;与内核不同,其它操作系统如同直接自MBR启动。类似Windows的启动菜单,也许是另一个启动管理器,它允许在多个不支持多启动的操作系统中做进一步的选择。(在已有Windows的系统上面,或者包含多个Windows版本的系统上安装现代的Linux而不修改原操作系统,即属于这类情况。)

GNU GRUB在GPT分区表的硬盘上

第四阶段:操作系统:控制权为操作系统内核

注:以下内容参考自计算机是如何启动的
操作系统的内核首先被载入内存。
以Linux系统为例,先载入/boot目录下面的kernel。内核加载成功后,第一个运行的程序是/sbin/init。它根据配置文件(Debian系统是/etc/initab)产生init进程。这是Linux启动后的第一个进程,pid进程编号为1,其他进程都是它的后代。
然后,init线程加载系统的各个模块,比如窗口程序和网络程序,直至执行/bin/login程序,跳出登录界面,等待用户输入用户名和密码。
至此,全部启动过程完成。

显示 Gitment 评论