引言
- 目标:是重写boot文件夹下面的引导文件,加入一些个人信息。
- 语法:由于使用两个语法风格的汇编需要两个汇编器,有些麻烦,直接全都用
GNU
的as(gas)
进行编译。使用AT&T
语法的汇编语言程序。- 接下来先拜读同济大学赵炯博士的《Linux内核0.11完全注释(修正版V3.0)》(以后简称《注释》)的第6章,做一些笔记。
-
boot
目录文件:包含 bootsect.s、setup.s、head.s 三个汇编文件。 -
bootsect.s
与setup.s
:实模式 16 位代码,Linux0.11使用Intel
语法,用as86
和ld86
编译。原因是
- 早期 GNU as 不支持实模式 16 位代码
- 直到 1994 年以后发布的 GNU as 汇编器才开始支持编译 16 位代码的.code16 伪指令。
-
head.s
:保护模式 32 位代码,用GNU
的as
(AT&T 语法)编译。 -
内核 2.4.X 起,bootsect.s 和 setup.s 才完全用 as 编译。
总体功能
先总体说明一下 Linux 操作系统启动部分的主要执行流程。
- 启动初始ROM BIOS:PC 开机,80x86 CPU 进入实模式,从地址
0xFFFF0
(ROM - BIOS)执行代码。BIOS 执行系统检测,在物理地址0
初始化中断向量,将启动设备(软驱或硬盘)的第一个扇区(512 字节,磁盘引导扇区)bootsect.s读入内存0x7C00
处并跳转执行。
启动初始化
PC 开机
▼
80x86 CPU 进入实模式
▼
从地址 0xFFFF0(ROM - BIOS)执行代码
▼
BIOS 执行系统检测:检测对象:对 CPU、内存、硬盘、显卡、键盘等主要硬件组件进行检查,确认其是否存在故障或能否正常连接。
▼
在物理地址 0 初始化中断向量:0x400字节1kb的中断向量表
▼
将启动设备(软驱或硬盘)的第一个扇区(512 字节,磁盘引导扇区)bootsect.s读入内存0x7C00(31KB)处
▼
跳转至内存 0x7C00 处执行
这里提到了扇区和对应的代码文件,先说明一下其对应关系:
- Linux 0.11 内核在 1.44MB 磁盘上所占扇区的分布情况。1.44MB 磁盘共有 2880 个扇区,一个扇区512字节
- 引导程序代码
bootsect
占用第 1 个扇区setup
模块占用随后的 4 个扇区- Linux 0.11 内核
system
模块大约占随后的 240 个扇区。- 还剩下 2630 多个扇区未被使用。这些剩余的未用空间可被利用来存放一个基本的根文件系统,从而可以创建出使用单张磁盘就能让系统运转起来的集成盘来。
-
bootsect.s:它将由上一步
BIOS
读入到内存绝对地址0x7C00(31KB)
处-
当它被执行时就会把自己移动到内存绝对地址
0x90000(576KB)
处,本身是一个扇区大小512
字节,所以尾部就是0x90200
-
把代码(
boot/setup.s
),四个扇区大小2k
,读入到bootsect
的尾部,内存0x90200
处 -
内核的其他部分(
system 模块
)则被读入到从内存地址0x10000(64KB)
开始处:为什么是0x10000(64KB)
?因为当时 system 模块的长度不会超过 0x80000 字节大小(即 512KB),是为了不覆盖到
bootsect
和setup
。
-
-
setup.s:是一个操作系统加载程序,下面解释这个文件做了什么事情
-
首先、前文说
BIOS
在物理地址0的地方有个1k的中断向量表,这里先利用ROM BIOS
中断读取机器系统数据,放到0x90000
覆盖掉了 bootsect 程序 -
然后、将 system 模块从
0x10000-0x8ffff
(当时认为内核系统模块 system 的长度不会超过512KB)整块向下移动到内存绝对地址0x00000
处。为什么一开始bootsect不直接放到物理地址0处呢?因为 setup 代码开始还需要利用 ROM BIOS 中的中断调用来获取机器的一些参数(例如显示卡模式、硬盘参数表等)。
当
BIOS
初始化时会在物理内存开始处放置一个大小为0x400
字节(1KB)
的中断向量表,因此需要在使用完BIOS
的中断调用后才能将这个区域覆盖掉。
-
-
最后进入 32 位保护模式运行,并跳转到
head.s
运行。下面这些操作都是为了进入保护模式做准备-
寄存器加载:加载中断描述符表寄存器(
idtr
)和全局描述符表寄存器(gdtr
)。进入保护模式,寻址方式发生改变,需要段选择符、段描述符,这里进行最初的设置,后面在
head.s
里面会进行进一步设置。 -
A20 地址线:开启 A20 地址线。
80386 有 32 根地址线,可寻址 (2^{32} = 4 GB) 的内存空间。
A20 地址线(地址总线的第 20 位,从 0 开始计数)与 80386 的关系如下:
- 实模式兼容性:80386 的实模式为兼容 8086/8088 设计。若 A20 关闭,访问超过 (1MB)(0x100000)的地址时,会像 8086 一样 “回绕”(如 0x100000 映射到 0x000000),因为此时第 20 位地址无效(始终为 0),模拟旧处理器的地址模式。
- 保护模式内存访问:在保护模式下,A20 开启是访问 (1MB) 以上全部内存的前提。若 A20 关闭,内存访问会被限制在 (0-1MB-1)、2MB-(3MB-1)) 等奇偶分段,无法连续寻址;A20 开启后,32 根地址线可完全发挥作用,实现 (4GB) 地址空间的连续访问。
简而言之,A20 地址线控制着 80386 对 (1MB) 以上内存的访问方式,是实模式兼容旧架构与保护模式充分利用内存的关键控制项。
-
中断重设:重新设置中断控制芯片 8259A,将硬件中断号设为
0x20 - 0x2f
。 -
设置 CPU 控制寄存器
CR0
,进入保护模式。
- 当这个setup程序完成的时候,系统模块 system 被移动到物理地址 0x0000 开始处,BIOS的中断向量表被system覆盖了。上面一个bootsect程序也已经被机器的系统数据覆盖了
-
-
head.s:Head.s 代码的主要作用是初步初始化中断描述符表中的 256 项门描述符,检查 A20 地址线是否已经打开,测试系统是否含有数学协处理器。然后初始化内存页目录表,为内存的分页管理作好准备工作。最后跳转到 system 模块中的初始化程序 init/main.c 中继续执行。
- 其中值得关注的是:分页机制设置一级页表和二级页表时,是从物理地址0开始的也就是把自身程序给覆盖了。
那么最后,执行完head.s后,已经正式完成了内存页目录和页表的设置,并重新设置了内核实际使用的中断描述符表 idt 和全局描述符表 gdt。另外还为软盘驱动程序开辟了 1KB 字节的缓冲区。
接下来具体写一下这三个文件