The Booting Process -- where dreams begain(基于x86架构的Linux 0.11 内核的启动过程)

 本篇旨在介绍基于x86架构的Linux 0.11 内核的启动过程。Data of an operating system must be loaded into the working memory during device start-up. This is made possible by a so-called bootloader, also known as a boot program or bootstrap loader.The process of booting an operating system involves transferring control through a series of small programs, each one more powerful than the one before it. The main programs in this process include BIOSBootloader, and the OS. The operating system is the final and the most powerful one.

一、启动第一步:加电

按下电源键,cpu启动,默认为16位实模式,CS:IP默认指向内存地址0xFFFF0。

Q1:什么是16位实模式呢?

这是相对于32位的扩展模式而言的,详情可参考intel手册,或我后续的博客(基本也是总结概括intel手册的内容)。

Q2:内存地址0xFFFF0中有什么?

CS:IP指向的内存地址,便是CPU将要执行的指令地址。0xFFFF0中有一条跳转指令,CPU执行该指令后,便跳到ROM-BIOS执行它的硬件系统检测和初始化程序。

二、执行BIOS

1.执行系统检测

*显卡信息--显示核心,显卡BIOS版本,厂商版权信息,显存容量等。

*CPU/硬盘/内存/光驱等信息。

*主板信息

......

2.建立中断向量表和中断服务程序

在内存地址0x00000 ~ 0x003FF处,构建中断向量表(1KB);

在内存地址0x00400 ~ 0x004FF处,构建BIOS数据区(256B);

在内存地址OX0E05B~0XFFFF处,加载相关中断服务历程(8KB,与上一段代码约距55KB)。这些中断服务历程的作用是把系统内核程序从硬盘加载到内存中。

3.调用int 0x19中断,加载内核引导程序bootsect

把软盘第一扇区的512B bootsect程序加载到内存地址0x7c00;当BIOS加载完bootsect,执行jmp 0x7c00,跳到bootsect中继续执行。

三、执行bootsect

1. 将自身bootsect 512字节从0x7c00复制到0x90000处,程序跳转到cs = 0x90000处继续执行。

将ds, es ,ss 都设置成与CS相同的位置0x9000;

2. 触发int 0x13中断,载入setup模块

将硬盘第二扇区开始的4个扇区,即是setup.s(2KB)对应的程序拷贝到0x90200之后,范围为0x90200 ~ 0x909FF;

3. 触发int 0x13中断,载入system模块

将从第六扇区开始的240个扇区(system模块,120KB)加载到内存的0x10000处。

4. 确定根设备号(rootdevice)

至此,根设备号是一个数字,该数字对应的是一个包含硬件信息的软盘,该软盘中含有格式化好的文件系统。

5. 执行跳转指令 jmpi 0, SETUPSEG,

跳转目的地0x90200执行setup代码

四、执行setup

1. 获取系统数据

利用BIOS提供中断服务程序从设备上提取内核运行所需的机器系统数据,并将这些系统数据加载到内存0x90000-0x901FD(510B)处,供内核相关程序使用(覆盖了已经完成使命的bootsect代码)。如表所示:

2.关中断,将system移到0x00000

将system从0x10000移到0x00000,将会覆盖BIOS在这区域的中断向量表和中断服务历程。因此在此之前必须关中断。转移后,system模块中代码的地址就等于实际的物理内存地址,便于对内核代码和数据的操作。

3. 设置临时全局描述符表和中断描述符表(极其重要,见后续博文)

  (1)GDT: 全局描述符表

  是存放段寄存器内容(段描述符)的数组。用来在保护模式下管理段描述符的数据结构,对操作系统自身的运行以及管理/调度进程有着重大意义。

  GDTR: GDT基地址寄存器,通过这个寄存器可以找到GDT首地址

  (2)IDT:中断描述符表

  保护模式下所有中断服务程序的入口地址,类似于实模式下的中断向量表  

  IDTR: 保存IDT的起始地址。由于已关中断,无需调用中断服务函数,所以目前IDTR设置为空。

4. 打开A20,实现32位寻址

打开A20,意味着CPU可以进行32位寻址,最大寻址空间为4GB。

5. 对8259A重新编程

将中断号重新编程,采用新的中断.

6. CR0寄存器PE位置1,处理器变为保护模式

在保护模式下,所有对内存的访问都需通过GDT或者LDT。因此在进入保护模式前需置好GDT.

7. jmpi 0, 8,跳转到systm的头部head模块执行

0是段内偏移
8是gdt表中的偏移,每个元素占8B,刚好是第一个元素,内核代码段.该元素中获取内核读代码段的基址位为0x0,

因此跳到了 0x0000000处,systm的头部head模块执行执行。

五、执行head模块(保护模式之下)

此段代码处于物理地址0开始的地方。
1. 设置各个数据段寄存器
 
2. 重新设置中断描述符表IDT
计256个表项,每个表项均指向一个只报错误的哑中断子程序--ignore_int。每个中断描述符表项占 8 Byte。

 

 

 P  是段存在标志;

DPL  是描述符的优先级。

中断门描述符中段选择符设置为 0x0008,表示该哑中断处理子程序在内核代码中。

偏移值被设置为 ignore_int  中断处理子程序在 head.s 程序中的偏移值。由于 head.s 程序被移动到从内存地址 0 开始处, 因此该偏移值也就是中断处理子程序 在内核代码段中的偏移值。

由于内核代码段一直存在于内存中, 并且特权级为 0,即 P=1 , DPL=00。因此中断门描述符的字节 5 和字节 4 的值应该是 0x8E00。

3. 重新设置全局段描述符表gdt

新设置的 GDT 表与原来在 setup.s 程序中设置的 GDT 表描述符除了在段限长上有些区别以外(原为 8MB,现为 16MB),其他内容完全一样。当然我们也可以在 setup.s 程序中就把描述符的段限长直接设置成 16MB, 然后直接把原 GDT 表移动到内存适当位置处。因此这里重新设置 GDT 的主要原因是为了把 gdt 表放在内存内核代码比较合理的地方。前面设置的 GDT 表处于内存 0x902XX 处。这个地方将在内核初始化后用作内存高速缓冲区的一部分。

 

4. 接着使用物理地址 0  1MB 开始处的字节内容相比较的方法,检测 A20 地址线是否已真的开启

如果没有开启,则在访问高于 1MB 物理内存地址时 CPU 实际只会循环访问(IP MOD  1Mb)地址处的内容,也即与访问 0 地址开始对应字节的内容都相同。如果检测下来发现没有开启,则进入死循环。

 5. 然后程序测试 PC 机是否含有数学协处理器芯片(80287 、80387 或其兼容芯片), 并在控制寄存器 CR0 中设置相应的标志位。

6. 接着设置管理内存的分页处理机制

将页目录表放在绝对物理地址 0 开始处(也是本程序所处的物理内存位置,因此这段程序将被覆盖掉),紧随后面放置共可寻址 16MB  内存的 4 个页表,并分别设置它们的表项。页目录表项和页表项格式见图 6- 10 所示。其中 P 是页面存在于内存标志; R/W 是读写标 志; U/S 是用户/超级用户标志; A 是页面已访问标志; D 是页面内容已修改标志;最左边 20 比特是表项对应页面在物理内存中页面地址的高 20 比特位。

这里每个表项的属性标志都被设置成 0x07  (P=1 、U/S=1 、R/W=1),表示该页存在、用户可读写。 这样设置内核页表属性的原因是:CPU 的分段机制和分页管理都有保护方法。分页机制中页目录表和页表项中设置的保护标志(U/S 、R/W)需要与段描述符中的特权级(PL)保护机制一起组合使用。但段描述符中的 PL 起主要作用。 CPU 会首先检查段保护, 然后再检查页保护。如果当前特权级 CPL < 3 (例 如 0),则说明 CPU 正在以超级用户(Supervisor)身份运行。  此时所有页面都能访问,并可随意进行 内存读写操作。如果 CPL = 3,则说明 CPU  正在以用户(User)身份运行。此时只有属于 User 的页面  (U/S=1) 可以访问, 并且只有标记为可读写的页面(W/R= 1) 是可写的。而此时属于超级用户的页面  (U/S=0)则既不可写、也不可以读。由于内核代码有些特别之处,即其中包含有任务 0 和任务1的代码和数据。因此这里把页面属性设置为 0x7 就可保证这两个任务代码不仅可以在用户态下执行,而且又不能随意访问内核资源。

7.最后,head.s 程序利用返回指令将预先放置在堆栈中的/init/main.c 程序的入口地址弹出, 去运行main() 程序。

 

 

 

 

 

 

 

 

posted @   大桉树  阅读(64)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示