主页
文章
分类
系列
标签
简历
【Linux Kernel 0.12】启动流程
发布于: 2023-2-12   更新于: 2023-2-12   收录于: Linux Kernel
文章字数: 359   阅读时间: 2 分钟   阅读量:

从BIOS开始

为什么

首先运行的其实是BIOS(Basic Input Output System) 为什么? 取决于硬件的先决条件[3]:

  1. CPU只能运行加载到内存中的程序
  2. 内存中的程序在计算机掉电之后会全部消失

所以操作系统必须保存在不挥发存储介质中,在上电后由某中机制将其加载到内存中运行。BIOS横空出世。

BIOS如何启动?

一个新的疑问,BIOS自身又是如何启动的? 首先,BIOS同样是一段程序,所以同样无法逃脱之前的两个设定(CPU只能运行内存中的程序,内存掉电挥发),BIOS机制是如何应对的呢? 先做两点说明

  1. BIOS固化在计算机主机板上的一块很小的ROM芯片里。
  2. CPU硬件逻辑设计为加电瞬间强行将CS的值置为0xF000、IP的值置为0xFFF0,这样CS:IP就指向0xFFFF0这个地址位置

At initial power on, the BIOS is executed directly from ROM. The ROM chip is mapped to a fixed location in the processor’s memory space (this is typically a feature of the chipset). When the x86 processor comes out of reset, it immediately begins executing from 0xFFFFFFF0. However, executing directly from ROM is quite slow, so usually one of the first things the BIOS does is to copy and decompress the BIOS code into RAM, and it executes from there. Of course, the memory controller must be initialized first! The BIOS takes care of that beforehand. The memory map layout will vary from system to system. At power-on, the BIOS will query the attached PCI/PCIe devices, determine what resources are needed, and place them in the memory map at the optimal location. If everything is working properly, memory-mapped devices should not overlap with RAM. (Note that on a 64-bit system with >3GB of RAM, things get complicated because you need a “hole” in the middle of RAM for your 32-bit PCI/PCIe devices. Some early x64 BIOSes and chipsets had issues with this.)[4]

简而言之,在主板初始化的过程中,BIOS固件会被读取到内存中,BIOS固件被加载到内存后,需要进行内存映射。BIOS会将自身的代码和数据存放到特定的内存地址空间中。这是一个纯硬件完成的动作。

BIOS如何引导操作系统

主要说明BIOS如何加载操作系统,但BIOS的作用不仅仅如此

从0xFFFF0开始

第一条指令就是跳转指令

1
jmpf 0xf000:e05b 

Pasted image 20230704110835|800

在内存中建立中断向量表和中断服务程序

BIOS程序在内存最开始的位置(0x00000)用1 KB的内存空间(0x00000~0x003FF)构建中断向量表,在紧挨着它的位置用256字节的内存空间构建BIOS数据区(0x00400~0x004FF),并在大约57 KB以后的位置(0x0E05B)加载了8 KB左右的与中断向量表相应的若干中断服务程序[2] Pasted image 20230625182537|800

中断向量表中有256个中断向量,每个中断向量占4字节,其中两个字节是CS的值,两个字节是IP的值。每个中断向量都指向一个具体的中断服务程序。

为什么要建立中断机制? 因为需要利用中断机制从存储介质中读取系统内核到内存中

加载bootsect(第一部分内核代码)

对于Linux 0.12操作系统而言,计算机将分三批逐次加载操作系统的内核代码。第一批由BIOS中断int 0x19把第一扇区bootsect的内容加载到内存0x7c00处;第二批、第三批在bootsect的指挥下,分别把其后的4个扇区和随后的240个扇区的内容加载至内存。 计算机硬件体系结构的设计与BIOS联手操作,会让CPU接收到一个int 0x19中断。CPU接收到这个中断后,会立即在中断向量表中找到int 0x19中断向量[2]。相应的中断处理程序的作用就是把软盘第一扇区中的程序(512 B)加载到内存0x07C00处。

bootsect.S

分配内存

为了加载setup,bootsect首先就需要对后续操作所涉及的内存位置进行设置,包括将要加载的setup程序的扇区数(SETUPLEN)以及被加载到的位置(SETUPSEG);启动扇区被BIOS加载的位置(BOOTSEG)及将要移动到的新位置(INITSEG);内核(kernel)被加载的位置(SYSSEG)、内核的末尾位置(ENDSEG)及根文件系统设备号(ROOT_DEV)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
SETUPLEN = 4				! nr of setup-sectors
BOOTSEG  = 0x07c0			! original address of boot-sector
INITSEG  = DEF_INITSEG			! we move boot here - out of the way
SETUPSEG = DEF_SETUPSEG			! setup starts here
SYSSEG   = DEF_SYSSEG			! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE		! where to stop loading

! ROOT_DEV & SWAP_DEV are now written by "build".
ROOT_DEV = 0
SWAP_DEV = 0

Pasted image 20230625184245|800

复制bootsect

bootsect启动程序将它自身(全部的512 B内容)从内存0x07C00(BOOTSEG)处复制至内存0x90000(INITSEG)处。 Pasted image 20230625184446|800

加载setup(第二部分内核代码)

借助BIOS提供的int 0x13中断向量所指向的中断服务程序(也就是磁盘服务程序)加载setup到内存中,一共4个扇区

加载system模块(第三部分内核代码)

任然利用BIOS 提供的int 0x13中断服务,将system模块加载到内核,一共240个扇区

确定根设备号

Pasted image 20230625231340|800

setup.s

设置内核参数

setup程序做的第一件事情就是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据,其中包括光标位置、显示页面等数据,并分别从中断向量0x41和0x46向量值所指的内存地址处获取硬盘参数表1、硬盘参数表2,把它们存放在0x9000:0x0080和0x9000:0x0090处。BIOS提取的机器系统数据将覆盖bootsect程序所在部分区域。 Pasted image 20230625231517|800

移动system

将system模块从0x10000~0x8fff(当时认为内核系统模块system的长度不会超过此值:512KB) 整块向下移动到内存绝对地址0x00000处

进入保护模式

加载中断描述符表寄存器(idtr)和全局描述符表寄存器(gdtr),开启A20地址线,重新设置两个中断控制芯⽚8259A,将硬件中断号重新设置为0x20~0x2f。最后设置CPU的控制寄存器CR0(也称机器状态字),从而进⼊32位保护模式运行。

当前内存的映像 Pasted image 20230627150829|800

head.S

程序自身的代码在程序自身所在的内存空间创建了内核分页机制,即在0x000000的位置创建了页目录表、页表、缓冲区、GDT、IDT,并将head程序已经执行过的代码所占内存空间覆盖。这意味着head程序自己将自己废弃,main函数即将开始执行 Pasted image 20230625231756|800

重新设置IDT

首先是加载各个数据段寄存器,重新设置中断描述符表IDT,共256项,并使各个表项均指向一个只报错误的哑中断子程序ignore_int

重新设置GDT

在设置好中断描述符表之后,本程序又重新设置了全局段描述符表gdt

设置管理内存的分页处理机制

将页目录表放在绝对物理地址0开始处(也是本程序所处的物理内存位置,因此这段程序将被覆盖掉),紧随后面放置共可寻址16MB内存的4个页表,并分别设置它们的表项。

当前内存的映像 Pasted image 20230627151222|800


[1] 《Linux内核完全剖析:基于0.12内核》赵炯著

[2] 《Linux 内核设计的艺术(第2版)》新设计团队

[3] 《Linux内核源代码情景分析》毛德操,胡希明等

[4] x86 - Who loads the BIOS and the memory map during boot-up - Stack Overflow

[5] 8086CPU中14个寄存器的详解_基址寄存器