计算机加电后启动后的内存布局-BIOS-bootloader-OS

149次阅读
没有评论

共计 8144 个字符,预计需要花费 21 分钟才能阅读完成。

计算机加电后BIOS启动的内存布局

计算机启动流程

这张图可以清晰的看出来启动流程:

计算机加电后启动后的内存布局-BIOS-bootloader-OS

内存映射图和布局

计算机加电后启动后的内存布局-BIOS-bootloader-OS

区域起始地址 区域结束地址 大小 描述
0000h 03FFh 1 KB 中断向量表
0400h 04FFh 256 B BIOS 数据区
0500h 7BFFh 30 KB 自定义区域
7C00h 7DFFh 512 B MBR 被 BIOS 加载到的内存区域
7E00h 9FBFFh 608 KB 自定义区域
9FC00h 9FFFFh 1 KB 扩展 BIOS 数据区
A0000h AFFFFh 64 KB 彩色显示适配器
B0000h B7FFFh 32 KB 黑白显示适配器
B8000h BFFFFh 32 KB 文本显示适配器
C0000h C7FFFh 32 KB 显示适配器 BIOS
C8000h EFFFFh 160 KB 映射硬件适配器的 ROM 或内存映射 I/O
F0000h FFFEFh 64 KB-16 真正的入口
FFFF0h FFFFFh 16 B BIOS 入口地址(跳转使用,因为空间有限)

上电,按下开机键

按下主机的电源键后,计算机开始启动,主板上电后开始初始化其固件(firmware)。固件是一些固化在芯片组上的程序,它会试图去启动 CPU。如果启动失败(例如 CPU 坏了或没插好),计算机就会死机并给出错误提示(如某些版本的主板固件会发出蜂鸣警告)。这种状态称为“zoombie-with-fans”。

如果前一个阶段未出错,就开始加电工作,在多 CPU 或多核 CPU 情况下,某一个 CPU 会被随机选取作为启动 CPU(bootstrap processor,BSP)运行 BIOS 内部的程序。其余的 CPU(application processor,AP)保持停机直到操作系统内核显式地使用它们。

2000 年以前的计算机主板上均使用 BIOS,如今绝大多数计算机采用的是 EFI(Mac 用的就是 EFI)或 UEFI,BIOS 正在逐步被淘汰。基于 EFI、UEFI 的开机过程与传统的BIOS不尽相同,本文将以传统的 BIOS,Intel CPU 为例介绍开机过程。

此时 CPU 工作模式为实模式,该模式下地址总线是 20 位,寻址范围是 0x00000~0xFFFFF 的 1M 范围。这也就解释了为什么 BIOS 的容量只有 1MB。

CPU的工作模式是指CPU的寻址方式、寄存器大小等用来反应CPU在该环境下如何工作的概念。

Intel CPU 用三种运行模式: 实模式、32 位保护模式、64 位保护模式。

  • 实模式:实模式出现于早期8088CPU时期,当时由于CPU的性能有限,一共只有20位地址线(所以地址空间只有1MB)。实模式下地址计算为:物理地址 = 段基址<<4+段内偏移,这样就可以使用16位寄存器,表示20位地址。该模式下,CPU的得到的地址就是物理地址,可以直接访问,没有区分系统程序或者应用程序。没有虚拟地址的概念,实模式下没有分段或是分页机制,逻辑地址和物理地址相等。
    • 在实模式下最大寻址空间时1M,1M以上的内存空间在实模式下不会被使用。
    • 在实模式所有的内存数据都可以被访问。不存在用户态、内核态之分。
    • 在BIOS加载、MBR、ntdlr启动阶段都处在实模式下。
  • 保护模式:随着CPU、内存的发展,CPU的地址线的个数也从原来的20根变为现在的32根,所以可以访问的内存空间也从1MB变为现在4GB,寄存器的位数也变为32位。实模式下的内存地址计算方式就已经不再适合了,因此引入了保护模式,实现内存的安全访问。但是由于兼容性问题,操作系统启动前的启动过程依旧是实模式,操作系统内核启动后,切换为保护模式,该模式下,进程访问的都是虚拟地址,需要操作系统实现地址转换,这阶段才有的页表,权限控制等。保护模式的寻址方式是通过逻辑地址 (Logical Address) 的 segment:offset 中的 segment 作为 selector 来索引到 GDT/LDT 中的一项,然后通过 flags 判断当前程序是否可以访问这段内存,可以访问的话提取 baseoffset 相加得到线性地址 (Linear Address),最后将线性地址通过分页表得到物理地址 (Physical Address) 。
    • 现在应用程序运行的模式均处于保护模式。
    • 横向保护,又叫任务间保护,多任务操作系统中,一个任务不能破坏另一个任务的代码,这是通过内存分页以及不同任务的内存页映射到不同物理内存上来实现的。
    • 纵向保护,又叫任务内保护,系统代码与应用程序代码虽处于同一地址空间,但系统代码具有高优先级,应用程序代码处于低优先级,规定只能高优先级代码访问低优先级代码,这样杜绝用户代码破坏系统代码。

1981年IBM PC机刚推出时系统只带有640KB的RAM。由于所采用的8088/8086 CPU只有20根地址线,因此内存寻址范围最高为1024KB(1MB)。现如今,CPU的物理内存寻址范围已经可以到4GB。但是为了与原来的PC兼容,系统1MB以下物理内存使用分配上任然保持与原来的PC机基本一致。ROM BIOS 都在物理内存能寻址的最高端位置处,所以在大于 1M 内存的新计算机上,ROM BIOS 最后会被映射到 1M 末尾的区域中(也就是ROM BIOS映射区域)。内存映射图如下图所示:

计算机加电后启动后的内存布局-BIOS-bootloader-OS

计算机上电初始化时,物理内存被设置成从地址 0 开始的连续区域。除了地址从 0xA0000 ~ 0xFFFFF(640K~1M)存放显存和 ROM BIOS 和 0xFFFE0000~0xFFFFFFFF(4G 的最后 64K)存放 ROM BIOS 以外,其他部分都可以用作主要内存。

我们可以通过cat /pro/iomem查看系统物理内存映射信息。
计算机加电后启动后的内存布局-BIOS-bootloader-OS

对照上图,可以看到0xF0000 ~ 0xFFFFF(最靠近1M的64KB这个区间是ROM BIOS的映射区域),0xA0000 ~ 0xBFFFF这段是显示缓冲区,0xC0000 ~ 0xC7FFF这段是VGA的ROM BIOS区域。

计算机加电后启动后的内存布局-BIOS-bootloader-OS

问题汇总

问题1 CPU到底是怎么加载BIOS的?

现在我们知道了大致的内存分布,也知道一般存放BIOS的ROM硬件会被映射到最靠近4GB(0xFFFFFFF0)的那个地址空间,然后因为该地址空间在实模式下无法被寻址,只能将数据映射到1M以内的空间,那么这个CPU到底是怎么找到存放在ROM里的BIOS代码呢?

这就需要提到一个概念:重置向量,又叫复位向量

重置向量的设计是CPU标准,为了统一标准,方便CPU找到相应的固件代码执行,并且由于兼容性考虑,依旧是采用1M一下内存空间运行BIOS代码,所以在32位甚至64位系统中需要重置向量,将CPU执行的寄存器设置到1M以内。

具体的,在CPU上电后,大部分寄存器都被初始化,包括指令指针寄存器(EIP)、CS代码段寄存器,它记录了下一条即将被CPU执行的指令所在的内存地址。其中EIP会被初始化为0xFFF0,CS会被初始化为0xF000,但是CS的基址会被初始化为0xFFFF0000,通常情况下此模式下CPU寻址方式位cs << 6 + EIP,但是CS还包含了隐藏的基址部分,此时 CPU 会有一个特殊行为,其会对 EIP 的初始值加上一个CS基址寄存器的值,生成一个 32 位的地址 CS基址+EIP =0xFFFF0000+0xFFF0 = 0xFFFFFFF0 。之所以称为特殊行为,是因为实模式下 CPU 只能寻址 1MB 地址空间,而这个 32 位地址已经大于 1MB 的内存限制。因此,0xFFFFFFF0 也被称为重置向量(reset vector).

于是,CPU 开始执行 0xFFFFFFF0 地址处的指令,该地址处是一条 JUMP 指令,这条指令清空了基址寄存器的值,并让指令跳回到 BIOS 开始处(物理地址为 0xF0000,参考上图 0xF0000 处的标识)以执行 BIOS。

ps: 完整的CS段寄存器由两部分组成,分别是可见部分和隐藏部分,可见部分就是能偶被程序设置和读取的部分,隐藏的部分就是基址部分。

如下图,0xFFFFFFF0处是一个跳转指令, 为了和以前的 BIOS 代码兼容,故地址 0xFFFFFFF0 处的指令还是一条长跳转指令jmp F000:E05B

计算机加电后启动后的内存布局-BIOS-bootloader-OS

将要跳转的地址:F000:E05B,其中F000代表CS寄存器的值,E05B代表EIP的值。当执行完重置向量之后,其实CS段寄存器的值已经被修改了,CPU此时寻址,不再是特殊方式(CS基址+EIP),而是实模式通用寻址:CS << 4+EIP,也就是说F000:E05B,将会跳转到0xFE05B执行,刚好位于1M的空间内。

于是 Intel 设计了一种映射机制,将内存高端的 BIOS ROM 映射到 1MB 以内的 RAM 空间里,并且可以使这一段被映射的 RAM 空间具有与 ROM 类似的只读属性。所以 PC 机启动时将开启这种映射机制,让 4GB 地址空间的最高一个 64KB 的内容等同于 1MB 地址空间的最高一个 64K 的内容,从而使得执行了长跳转指令后,其实是回到了早期的 8086 CPU 初始化控制流,保证了向下兼容。

问题2 cpu加电执行的第一句代码,究竟是从BIOS的ROM中执行,还是从内存中执行?

cpu加电执行的第一句代码,究竟是从ROM中执行?还是从内存RAM中执行?请教,cpu加电执行的第一句代码,究竟是从bios中执行,还是从内存中执行?-CSDN社区

这个问题,网上大家讨论的很激烈,大致的意思就是ROM和RAM是统一编址,ROM不需要载入内存,CPU也能寻址到指令进行执行,就像前面分析的,执行的第一条重置向量跳转指令,就是在ROM BIOS之中。

问题3 ROM BIOS是什么时候映射到RAM中的

ROM具有掉电不会丢失数据的特性,RAM具有访问速度快的特性。因此,在真正执行BIOS代码前,ROM中的一部分boot block已经被映射或者拷贝到了RAM中,执行复位向量后,跳转到RAM中执行。

那ROM BIOS是如何映射到RAM中的?这是在执行复位向量之前,CPU还有一些过程,还有内存控制器等,就已经将ROM BIOS映射过去了,过去是映射,现在大多是拷贝。具体的细节,我这里就不继续寻找了。

问题4 RAM中留给system bios的地址空间是64K,可是现在主板bios动辄2M,甚至4M,那么整个real mode内存才1M,它怎么来寻址?

BIOS起关键作用的只是一个block而已, 即boot block, 其他的块通过接近4G的高端地址访问就可以了。boot block是BIOS中一段特定区域,包含用于引导的最小指令集。

MBR bootloader

由内存分布可知7C00h -7DFFh是MBR加载的区域,大小为512字节。

x86 体系的 PC 启动之后由 BIOS 完成自检,然后开始搜索外存上的引导信息,查找设备进行系统启动。

BIOS 会按照设定扫描不同的设备(硬盘,CD-ROM,Flash Drive,等等)并判断设备中是否可引导 (Bootable)。这个判断的方式通常是去 **查看设备的第一个 Sector 的最后两字节(即 511 和 512 字节)是否为0x55 和 0xaa(注意是低字节序 Little-Endian 还是高字节序 Big-Endian),是的话该设备就是可引导的。

当 BIOS 发现了可引导设备后,他就会把该可引导设备的 第一个 Sector 拷贝到 RAM 中的 0x7c00 (从 0x7c00 开始的 512 字节),并跳到 0x7c00 开始执行拷贝的程序。这一段程序通常被叫做 Bootloader。常见的 Boot Loader 有 grub、lilo、spfdisk。下图可以帮助大家理解 MBR 的结构。

计算机加电后启动后的内存布局-BIOS-bootloader-OS

以GRUB为例MBR bootloader

可以说GRUB 是bootloader中比较常用的,它本身可以看作一个小型操作系统,内置了简单的文件系统,可以读取分区, grub 启动之后首先就是加载它自己的一个简单的文件系统,然后读取自己的相关配置文件。

总体上GRUB更像是一个mini os,只不过这个mini os的作用只是加载其他的操作系统,在GRUB中包括stage1、stage1.5(可选)和stage2,其中stage1和stage1.5属于boot loader,stage2属于mini os的内核部分。

GRUB中stage1过程主要位于MBR的前446字节中(对于支持GPT分区的磁盘,同样有最开始的512字节作为保护MBR,保护MBR与正常的MBR区别不大,主要是分区表上的不同,在保护MBR中只要一个表示为0xEE的分区,以此来表示这块硬盘使用GPT分区表,不能识别GPT硬盘的操作系统通常会识别出一个未知类型的分区,并且拒绝对硬盘进行操作),之后的64字节为硬盘的分区表,最后两个字节为MBR结束标志位(0xAA55)。

grub stage 1

主要作用:加载下一阶段代码。因为该扇区只有512B,功能单一。

其中stage1要被安装(也就是写入)某个硬盘的主引导记录,或者某个活动分区(这个分区要用fdisk标记成可启动的)的启动扇区。 stage1的主要的也是唯一的作用就是找到你存放在硬盘上某个地方的stage2文件,来完成后续的工作。

MBR中前 446个字节,如果把这里面的内容损坏,那么系统会认为当前磁盘没有启动引导功能,会尝试从光盘或者网络启动系统。

stage1部分占用了446字节,其代码文件是源码目录下stage1/stage1.S文件,汇编后生成一个512字节的boot.img,被写在硬盘的0面0道1扇区中,作为硬盘的MBR。stage1的工作很简单,就是加载0面0道2扇区上的512字节到0x8000,然后跳转到0x8000执行。在0面0道2扇区上的512字节内容为stage1/start.S文件汇编后生成。该扇区上的内容的作用是加载stage1.5或是stage2过程,并将控制权转交。

问题汇总

1 为什么需要grub 1.5

因为stage1的容量有限(主引导记录MBR和启动扇区的大小只能够是512字节),所以它对文件系统是无法识别的,那如果你把 stage2存放在ext2或者fat格式的文件系统上,它如何来找到这个文件呢?这就要用到上面提到的那些stage1_5的文件了,它们负责解释文件系统。你的stage2放在什么格式的文件系统上,就要调用对应的那个stage1_5文件。比如,你把stage2存放在ext2格式的文件系统上,就 需要e2fs_stage1_5;stage2存放在fat格式的文件系统上,就需要fat_stage1_5了。

总结

grub被分为三个阶段

  • stage1:由于只有512字节,它的功能就是加载stage1.5然后转移控制权

  • stage1.5:stage1.5通常是之后的n个扇区,大小通常是10几k到几十k这么大。当然这样也只能装一种类型的文件系统,所以stage1.5就很多种,分别对应不同的文件系统,在grub安装时按需装入。识别出文件系统后,找到文件系统中的stage2的core.img加载,并移交控制权。

  • stage2:真正干活的,读取配置文件grub.cfg并生成启动菜单,根据用户选择的操作系统,加载内核并转移权限。

    grub stage 1.5

主要作用:加载文件系统。

在stage1过程将控制权转交后,接下来就是GRUB的核心过程了。该过程之所以区分stage1.5和stage2,主要原因是GRUB和GRUB2的区别。在GRUB2中,将stage1.5过程集成到了stage2的过程中,所以stage1.5过程仅仅是针对GRUB的。下面将介绍GRUB。

Stage1.5过程很无辜,它的作用很单一,但是非常关键。它的主要功用就是构造一个boot分区系统对应的文件系统,这样可以通过文件系统的路径(/boot/grub/)寻找stage2过程需要的core.img,进而加载到内存中开始执行。

Stage1.5存在于0面0道3扇区开始的地方,并一直延续十几k字节的区域,具体的大小与相应的文件系统的大小有关(文中涉及到了0面0道1-3+x扇区,这部分扇区为保留扇区,BIOS不会放置任何数据。正因为如此如果转换到GPT分区形式,系统将不能被正确引导,MBR后面的扇区都被其他内容所占据)。e2fs_stage1_5(针对ext2fs,可引导ext2和ext3文件系统)、fat_stage1_5(针对fat文件系统,可引导fat32和fat16)、ffs_stage1_5、jfs_stage1_5、minix_stage1_5、reiserfs_stage1_5、vstafs_stage1_5和xfs_stage1_5,这些文件被称为stage1.5过程,这些文件每个至少都在11k以上。除此之外还有两个比较特殊的文件,分别为nbgrub和pxegrub,这两个文件主要是在网络引导时使用,只是格式不同而已,他们很类似与stage2,只是需要建立网络来获取配置文件。

grub stage 2

主要作用:bootloader核心,在文件系统建立以后选择合适的操作系统进行加载并转交控制权,达到最后引导操作系统的目标。

由于GRUB属于multi boot loader,因此在引导的时候要进行选择,选择哪种操作系统来运行。在GRUB内部主要包括两种方式,首先是从grub. cfg中读取显示到屏幕让用户选择,其次是通过grub-shell中定义的命令手动进行启动。通常,只要我们看到内核的启动菜单,表示已经成功的进入了grub的stage 2阶段,因为启动菜单是在stage 2 生成的。

OS Kernel加载

Bootloader 会寻找操作系统内核所在分区,找到将其加载进入内存RAM,然后跳转到开始的程序开始的位置。至于拷贝多少扇区,跳转到哪里执行?这个就不像 0x7c00 这个数那么经典了,不同的操作系统肯定也不一样,也不用事先规定好,反正写操作系统的人给自己定一个就好了,别覆盖其他关键设备用到的区域就好。

以grub为例,加载哪个操作系统,到哪个位置,不是明文规定的。

在一些早期的PC系统中,操作系统内核确实是被加载到物理内存地址 0x100000(也称为1MB)开始启动的。这是因为在这些系统中,BIOS中断13h的读取扇区的函数所支持的最大扇区数是256个,每个扇区的大小为512字节,因此最大支持的读取大小为128KB(256 * 512)。因此,为了尽可能地利用可用的内存空间,并避免与其他系统组件冲突,操作系统内核通常被加载到物理内存 0x100000 开始的位置上。

一个从0x100000加载的系统内存图如下:

开始从 RAM 中的 0x100000 加载系统内核。在这个过程中,Bootloader 或者是操作系统本身会把 CPU 的运行模式从 实模式 切换到 保护模式 。于是我们的操作系统就被启动了。

当然,这里省略了很多细节

计算机加电后启动后的内存布局-BIOS-bootloader-OS

但是,随着计算机硬件的不断发展,现代计算机通常有更大的物理内存和更高的处理能力,因此操作系统内核的加载和启动方式也随之发生了变化。例如,在64位x86架构的计算机上,操作系统内核通常是被加载到更高的物理内存地址上,例如 0x1000000(也称为16MB)或更高的位置,放在哪儿,取决于内核开发人员。

参考文档

正文完
 
landery
版权声明:本站原创文章,由 landery 2023-06-08发表,共计8144字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)