[Make MyOS] After power on
学习嘛,当然要有浓厚的兴趣先,所以需要百度学习了一下,按下电源键之后的故事。操作系统这玩意虽说玄乎,但是参考资料还是很多的
参考书籍是《30 天自制操作系统》和《一个 64 位操作系统的设计与实现》,以 30 天为主,但是这玩意用的是 Windows 环境,这显然是不友好的,最终选择环境位 Ubuntu18,能换的都换换呗
Intel 手册:Intel® 64 and IA-32 Architectures Software Developer Manuals
Vol1:用来介绍 Intel x86 平台的基本架构和执行环境。
Vol2:介绍 x86 指令,学汇编的时候可以参考啊
Vol3:介绍 x86 系统编程的基础,包括内存管理、保护、任务管理、中断和异常处理、多处理器支持、散热和电源管理功能、调试、性能监控、系统管理模式、虚拟机扩展 (VMX) 指令、英特尔虚拟化技术 (Intel VT) 和英特尔软件防护扩展 (Intel SGX)。
Vol4:介绍 x86 CPU 上的 MSR,是 CPU 内部的一组特殊的寄存器(这个是从原来的 Vol3 中独立出来的)。
1 Why Booting is called Booting
为什么计算机的启动叫 boot,兴奋把这个疑问敲向了 Google,我就知道肯定会有这个疑问的
参考:
Why Booting is called "Booting"
操作系统的启动为什么被称之为 booting,而实际的程序又被称之为 loading。这个原因要追溯到 1950 年代,booting 是实际上是 bootstrapping 的缩写,而 bootstrapping 又是来自美国的一句谚语
pull oneself up by one's bootstraps
字面意思就是用自己额鞋带把自己拉起来,这可能吗?这要可以,我左脚踩右脚直接起飞。但是计算机它得可以,因为计算机启动是一个很矛盾的过程:必须先运行程序,然后计算机才能启动,但是计算机不启动就无法运行程序。所以必须想尽各种办法,把一小段程序装进内存,然后计算机才能正常运行。久而久之就被称之为 booting 了..
接着再看计算机整个启动过程
2 BIOS
参考:Intel manual volume 3 chapter 9.1
当按下电源键或者 RESET 引脚被触发时,系统总线上每个 CPU 都会执行初始化 (硬件复位) 和可选的内置自检 (build-in self-test)。硬件复位将每个处理器的寄存器设置为已知状态并将处理器置于实地址模式。它还会使内部缓存、转换后备缓冲区 (TLB) 和分支目标缓冲区 (BTB) 失效。此时,采取的操作取决于处理器系列,Intel 手册上举了四类,三大类,比较古老的就不看了,IA-32 and Intel 64 processors,系统总线上的所有处理器(包括单处理器系统中的单个处理器)都执行多处理器(MP)初始化协议。通过该协议被选为自举处理器 (BSP) 的处理器然后立即开始执行从 EIP 寄存器中的偏移量开始的当前代码段中的软件初始化代码。
这一段描述 RESET 引脚相关启动,还有一种 INIT 引脚,这俩差别不大,只是 INIT 是从保护模式转到实地址模式,抛开上电之后的 BIST (毕竟是可选的),来瞅瞅第一行代码是怎么被执行的
参考 Intel 手册卷 3 第 9.1.1 节,首先上电之后,寄存器 CR0 被设为 60000010H,用以确保 CPU 进入实地址模式。EIP 被设定为 0000FFF0H,CS 基地址被设定为 FFFF0000H (选择器是 F000H),通过CS.BASE+EIP得出第一条指令的地址 FFFFFFF0H。
参考 Intel 手册卷 3 第 9.1.4 节,硬件复位后取出并执行的第一条指令位于物理地址 FFFFFFF0H。该地址比处理器的最高物理地址低 16 个字节。包含软件初始化代码的 EPROM 必须位于该地址。在实地址模式下,地址 FFFFFFF0H 超出了处理器的 1MB 可寻址范围。CS 寄存器有两部分:可见段选择器部分和隐藏基地址部分。在实地址模式下,基地址通常是通过将 16 位段选择器值向左移动 4 位以产生 20 位基地址来形成的。但是,在硬件复位期间,CS 寄存器中的段选择器装入 F000H,基地址装入 FFFF0000H。因此,起始地址是通过将基地址与 EIP 寄存器中的值相加而形成的(即,FFFF0000 + FFF0H = FFFFFFF0H)。为确保 CS 寄存器中的基地址在基于 EPROM 的软件初始化代码完成之前保持不变,代码不得包含远跳转或远调用或允许发生中断。
那么为啥地址要是 FFFFFFF0H 呢?我们知道开机第一件事是读取 BIOS,其实 FFFFFFF0H 的位置就是 BIOS ROM 的末尾,并且包含一条跳转指令,跳转到包含启动代码的 BIOS 区域。因为每家计算机生产厂商生产的机器外设不同,所以其 BIOS 程序大小不尽相同,有的可能是 1K,有的可能是 2K,如果把 BIOS 程序放在 0x0000 处,那么会造成在机器 A 上用户写的程序是从 0x0400 开始,机器 B 上用户写的程序就是 0x0800 开始,非常不统一。如果把这程序放在 1M 内存的顶部,再规定计算机第一条指令位置是 FFFFFFF0H,然后就跳到各家 BIOS 开始处,那么用户写的程序都可以从 0x0000 开始了。
CPU 工作模式:实模式、保护模式、长模式。CPU 同时在安全性上也要提升,从只有实模式【可以随意执行全部 CPU 指令,内存可以直接通过物理地址访问,随意访问随意读写】,到了 32 的保护模式【将指令划分为 ring0 到 ring3,CPU 指令不是你想调用就能调用;内存不是你想访问就能访问,首先 CPU 要允许,而且操作系统允许】,而 64 的长模式在安全方面与 32 并没有本至区别;
实模式又称实地址模式,实,即真实,这个真实分为两个方面,一个方面是运行真实的指令,对指令的动作不作区分,直接执行指令的真实功能,另一方面是发往内存的地址是真实的,对任何地址不加限制地发往内存。另外 16 位 CPU 是没有实模式这一说的,在实模式下地址总线只使用了 20 根,寻址范围为 220B=1MB。可参考 Intel manual volume 3 chapter 8.7.13.4,有个 A20M# pin,会屏蔽掉
保护模式相比于实模式,增加了一些控制寄存器和段寄存器,扩展通用寄存器的位宽,所有的通用寄存器都是 32 位的,还可以单独使用低 16 位,这个低 16 位又可以拆分成两个 8 位寄存器
长模式又名 AMD64,因为这个标准是 AMD 公司最早定义的,它使 CPU 在现有的基础上有了 64 位的处理能力,既能完成 64 位的数据运算,也能寻址 64 位的地址空间。这在大型计算机上犹为重要,因为它们的物理内存通常有几百 GB。
更多查看:Intel manual volume 3 chapter 2.2
经过上面的解释,终于到了 BIOS 了,BIOS 执行过程如下:
开机自检 (POST),如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止
检测显卡(芯片)的 BIOS 并执行其代码以初始化视频硬件
检测任何其他设备 BIOS 并调用它们的初始化函数
显示 BIOS 启动画面
执行简短的内存测试(确定系统中有多少内存)
设置内存和驱动参数
配置即插即用设备(传统上是 PCI 总线设备)
分配资源(DMA 通道和 IRQ)
识别引导设备,此时 BIOS 需要知道,“下一阶段的启动程序” 具体存放在哪一个设备。当 BIOS 识别引导设备(通常是被标记为可引导磁盘的几个磁盘之一)时,它从该设备读取块 0 到内存位置 0x7c00 并跳转到那里。
3 MBR
MBR (Master Boot Record),硬盘的 0 柱面、0 磁头、1 扇区称为主引导扇区,FDISK 程序写到该扇区的内容称为主引导记录(MBR)。该记录占用 512 个字节,它用于硬盘启动时将系统控制权交给用户指定的,并在分区表中登记了的某个操作系统区。BIOS 中启动顺序排第一的存储设备,软盘,硬盘,U 盘等.. 不管什么盘,一般都是读取其第一个扇区,也就是 512 字节,加载到 0x7c00 处,如果这 512 字节的最后两个字节是 0x55AA,那么就表示这个设备可以启动,如果不是,那就用下一个储存设备。512 字节太小了,所以作用很明确,就是指明操作系统的入口在哪。MBR 的内容是:
1 | 第一阶段引导加载程序(≤ 440字节) |
一个扇区的硬盘主引导记录 MBR 由 4 个部分组成。
| 字节 | 内容 | 分类 |
|---|---|---|
| 0000H~0088H | 主引导程序 | 主引导程序 |
| 0089H~00E1H | 出错信息 | 数据区 |
| 00E2H~01BDH | 全为 0 | |
| 01BEH~01CDH | 分区项 1 | 分区表 |
| 01CEH~01DDH | 分区项 2 | |
| 01DEH~01EDH | 分区项 3 | |
| 01EEH~01FDH | 分区项 4 | |
| 01FEH | 0x55 | 结束标志 |
| 01FFH | 0xAA |
占用 512 个字节的 MBR 中,偏移地址 01BEH~01FDH 的 64 个字节,为 4 个分区项内容(分区信息表)。它是由磁盘介质类型及用户在使用 FDISK 定义分区时确定的。在实际应用中,FDISK 对一个磁盘划分的主分区可少于 4 个,但最多不超过 4 个。每个分区表的项目是 16 个字节,其内容含义如下表所示。
| 存贮字节位 | 内容及含义 |
|---|---|
| 第 1 字节 | 引导标志。若值为 80H 表示活动分区,若值为 00H 表示非活动分区。 |
| 第 2、3、4 字节 | 本分区的起始磁头号、扇区号、柱面号。其中:磁头号——第 2 字节;扇区号——第 3 字节的低 6 位;柱面号——为第 3 字节高 2 位 + 第 4 字节 8 位。 |
| 第 5 字节 | 分区类型符。00H——表示该分区未用(即没有指定);06H——FAT16 基本分区;0BH——FAT32 基本分区;05H——扩展分区;07H——NTFS 分区;0FH——(LBA 模式)扩展分区(83H 为 Linux 分区等) |
| 第 6、7、8 字节 | 本分区的结束磁头号、扇区号、柱面号。其中:磁头号——第 6 字节;扇区号——第 7 字节的低 6 位;柱面号——第 7 字节的高 2 位 + 第 8 字节。 |
| 第 9、10、11、12 字节 | 本分区已用的扇区数。 |
| 第 13、14、15、16 字节 | 本分区的总扇区数。 |
最后的四个字节(“主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过 232。
如果每个扇区为 512 个字节,就意味着单个分区最大不超过 2TB。再考虑到扇区的逻辑地址也是 32 位,所以单个硬盘可利用的空间最大也不超过 2TB。如果想使用更大的硬盘,只有 2 个方法:一是提高每个扇区的字节数,二是增加扇区总数。
BIOS 完了以后就到了硬盘的某个分区了,在进硬盘分区之前,先来看一个疑问。
为什么是 0x7c00?这玩意在 Intel 手册没搜到,所以应当和 CPU 啥的没关系。觉知此事要 Google:Why BIOS loads MBR into 0x7C00 in x86 ?
"0x7C00" First appeared in IBM PC 5150 ROM BIOS INT 19h handler.
When power on, BIOS processes "POST"(Power On Self Test) procedure, and after, call INT 19h. In INT 19h handler, BIOS checks that PC has any of floppy/hard/fixed diskette or not have. If PC has any of available diskkete, BIOS loads a first sector(512B) of diskette into 0x7C00.
Now, you understand why you couldn't find out this magic number in x86 documents. This magic number belongs to BIOS specification.
"0x7C00" was decided by IBM PC 5150 BIOS developer team (Dr. David Bradley).
BIOS 开发团队决定 0x7C00 是因为:他们希望为操作系统在 32KB 内加载自己留下尽可能多的空间。8086/8088 使用 0x0~0x3FF 作为中断向量,BIOS 数据区在它之后。引导扇区为 512 字节,引导程序的堆栈/数据区需要更多的 512 字节。因此,选择了 0x7C00,即 32KB 的最后 1024B。一旦操作系统加载并启动,引导扇区将永远不会使用,直到电源重置。因此,操作系统和应用程序可以自由使用 32KB 的最后 1024B。
系统加载后,内存布局
1 | +--------------------- 0x0 |
相对 MBR,还有一种应该不会用到的 EBR (Extended Boot Record)..
4 VBR
一旦 BIOS 将控制权转移到加载到内存中的 MBR 的开头,MBR 代码就会扫描其分区表并加载该分区的卷引导记录 (VBR)。VBR 是从指定分区的第一个磁盘块开始的一系列连续块。VBR 的第一个块标识分区类型和大小并包含初始程序加载器 (IPL),该代码将加载构成第二阶段引导加载器的附加块。在 Windows NT 派生系统(例如,Windows Server 2012、Windows 8)上,IPL 加载一个名为 NTLDR 的程序,然后该程序加载操作系统。
低级引导加载程序难以加载完整操作系统(尤其是可能由多个文件组成的操作系统)的一个原因是,这样做需要解析文件系统结构的能力。这意味着了解目录和文件名的布局方式以及如何找到与特定文件对应的数据块。没有太多代码,只读取连续的块要容易得多。更高级别的加载程序,例如 Microsoft 的 NTLDR,可以读取 NTFS、FAT 和 ISO 9660 (CD) 文件格式。
简单来说,VBR 的下一步才是加载操作系统
这里除了 VBR,还有 Boot Manager,比如说 Windows Boot Manager,GRUB 等等,就是计算机上装了多个操作系统,让用户选择启动哪一个的东西,也就是说从 MBR 过来,控制权不给 VBR 了,而是给启动管理器这么个程序
5 OS
VBR 指明了操作系统在哪,接下来就是把操作系统给加载起来,比如说 Linux 就会先把 kernel 加载起来,完了再到用户空间 init 进程,init 再加载各个进程模块,窗口,网络啥的,至此操作系统就起来了
6 UEFI
B 站大佬:谭玉刚的个人空间 讲 UEFI 的,先 mark 下,后面可以学习学习..
随着 64 位架构取代 32 位架构的出现,BIOS 开始显得过时了。英特尔着手创建 BIOS 后继产品的规范,该规范对必须以 20 位寻址的 16 位模式运行启动代码没有任何限制。此规范称为统一可扩展固件接口或 UEFI。尽管由英特尔开发,但自 2005 年起由统一 EFI 论坛管理。许多较新的 64 位系统都使用它,包括 Mac,这些系统也支持运行 Windows 的旧版 BIOS。
EFI 支持的一些功能包括:
保留了 BIOS 中的一些组件,包括电源管理(高级配置和电源接口,ACPI)和系统管理组件(例如,读取和设置日期)。
支持更大的磁盘 BIOS 仅支持每个磁盘四个分区,每个分区的容量高达 2.2TB。UEFI 支持的最大分区大小为 9.4ZB(9.4 × 10^21 字节)。
无需在 16 位(实)模式下启动,预引导执行环境可以直接访问所有系统内存。
UEFI 包括设备驱动程序,包括解释与体系结构无关的 EFI 字节代码 (EBC) 的能力。然而,操作系统使用它们自己的驱动程序,因此与 BIOS 一样,驱动程序通常仅用于引导过程。
旧的 BIOS 只能加载单个块,这需要多阶段启动过程。UEFI 有自己的命令解释器和完整的引导管理器。不再需要专用的引导加载程序。只要将可引导文件放入 UEFI 引导分区,该分区被格式化为 FAT 文件系统(旧 Windows 系统中的标准文件系统格式;几乎每个操作系统都知道如何处理的文件系统格式)。
固件是可扩展的,UEFI 的扩展可以加载到非易失性内存中。
7 Non-Intel Systems
上面的依据都是 Intel 的,向 Android (嵌入式) 和 MAC 就不一定一样了
许多嵌入式设备不会加载操作系统,而是已经将操作系统存储在非易失性存储器(如 Flash 或 ROM)中。例如,那些加载操作系统(例如基于 ARM 的 Android 手机)的设备将在设备开机时执行只读存储器(通常在 NOR 闪存中)中的代码。此引导代码嵌入在某些设备的 CPU ASIC 中,因此不需要板上单独的闪存芯片。当系统复位(包括上电)时,处理器处于监控 (SVC) 模式并且中断被禁用。在基于 ARM 的系统上,处理器从地址 0x00000000 开始执行。包含启动代码的闪存在复位时映射到地址 0x00000000。此代码执行各种初始化,包括在 DRAM 中设置异常向量表以及将应用程序代码从 ROM 复制到 DRAM(代码在 DRAM 中运行速度更快)。代码将 DRAM 重新映射到地址 0,从而隐藏了闪存(处理器有一个 REMAP 位来改变闪存的映射)。然后初始化存储系统。这涉及设置内存保护和设置系统堆栈。然后初始化 I/O 设备并将处理器更改为用户模式。引导固件检测可引导媒体并加载和运行第二阶段引导加载程序(如有必要)。第二阶段引导加载程序通常是用于大型系统的 GRUB 或用于嵌入式系统的 uBoot。第二阶段加载器加载操作系统并将控制权转移给它。