[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,我就知道肯定会有这个疑问的

参考:

Booting an Operating System (rutgers.edu)

Why Booting is called “Booting “ | Desktop Reality

操作系统的启动为什么被称之为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根,寻址范围为2^20^B=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执行过程如下:

  1. 开机自检(POST),如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止

  2. 检测显卡(芯片)的BIOS并执行其代码以初始化视频硬件

  3. 检测任何其他设备BIOS并调用它们的初始化函数

  4. 显示BIOS启动画面

  5. 执行简短的内存测试(确定系统中有多少内存)

  6. 设置内存和驱动参数

  7. 配置即插即用设备(传统上是PCI总线设备)

  8. 分配资源(DMA通道和IRQ)

  9. 识别引导设备,此时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
2
3
第一阶段引导加载程序(≤ 440字节)
磁盘签名(4字节)
磁盘分区表,用于标识磁盘的不同区域(每个分区16字节 × 4个分区)

一个扇区的硬盘主引导记录MBR由4个部分组成。

字节内容分类
0000H~0088H主引导程序主引导程序
0089H~00E1H出错信息数据区
00E2H~01BDH全为0
01BEH~01CDH分区项1分区表
01CEH~01DDH分区项2
01DEH~01EDH分区项3
01EEH~01FDH分区项4
01FEH0x55结束标志
01FFH0xAA

占用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字节本分区的总扇区数。

最后的四个字节(“主分区的扇区总数”),决定了这个主分区的长度。也就是说,一个主分区的扇区总数最多不超过2^32^。

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

BIOS完了以后就到了硬盘的某个分区了,在进硬盘分区之前,先来看一个疑问。

为什么是0x7c00?这玩意在Intel手册没搜到,所以应当和CPU啥的没关系。觉知此事要Google:Why BIOS loads MBR into 0x7C00 in x86 ? - Glamenv-Septzen.net(en)

“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
2
3
4
5
6
7
8
9
10
11
12
13
+--------------------- 0x0
| Interrupts vectors
+--------------------- 0x400
| BIOS data area
+--------------------- 0x5??
| OS load area
+--------------------- 0x7C00
| Boot sector
+--------------------- 0x7E00
| Boot data/stack
+--------------------- 0x7FFF
| (not used)
+--------------------- (...)

相对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站大佬:谭玉刚的个人空间_哔哩哔哩_bilibili讲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就不一定一样了

许多嵌入式设备不会加载操作系统,而是已经将操作系统存储在非易失性存储器(如闪存或ROM)中。例如,那些加载操作系统(例如基于ARM的Android手机)的设备将在设备开机时执行只读存储器(通常在NOR闪存中)中的代码。此引导代码嵌入在某些设备的CPU ASIC中,因此不需要板上单独的闪存芯片。当系统复位(包括上电)时,处理器处于监控(SVC)模式并且中断被禁用。在基于ARM的系统上,处理器从地址0x00000000开始执行。包含启动代码的闪存在复位时映射到地址0x00000000。此代码执行各种初始化,包括在DRAM中设置异常向量表以及将应用程序代码从ROM复制到DRAM(代码在DRAM中运行速度更快)。代码将DRAM重新映射到地址0,从而隐藏了闪存(处理器有一个REMAP位来改变闪存的映射)。然后初始化存储系统。这涉及设置内存保护和设置系统堆栈。然后初始化I/O设备并将处理器更改为用户模式。引导固件检测可引导媒体并加载和运行第二阶段引导加载程序(如有必要)。第二阶段引导加载程序通常是用于大型系统的GRUB或用于嵌入式系统的uBoot。第二阶段加载器加载操作系统并将控制权转移给它。