Part 2
目录
Part 2
学习理论知识
反汇编
扇区
BIOS 启动过程总结
Boot loader 启动过程总结
A20 gate
读 boot/boot.S 和 boot/boot.c 源码
- - boot/boot.S
- - boot/mian.c
学习理论知识
反汇编
把机器语言转换为汇编语言代码
扇区
对于 PC 来说, 软盘, 硬盘都可以被划分为一个个大小为 512 字节的区域, 叫做扇区. 一个扇区是一次磁盘操作的最小粒度. 每一次读取或者写入操作都必须是一个或多个扇区. 如果一个磁盘是可以被用来启动操作系统的, 就把这个磁盘的第一个扇区叫做启动扇区. 当 BIOS 找到一个可以启动的软盘或硬盘后, 它就会把这 512 字节的启动扇区加载到内存地址 0x7c00~0x7dff 这个区域内.
BIOS 启动过程总结
当计算机加电后, 一般不直接执行操作系统, 而是执行系统初始化软件完成基本 IO 初始化和引导加载功能. 简单地说, 系统初始化软件就是在操作系统内核运行之前运行的一段小软件. 通过这段小软件, 我们可以初始化硬件设备, 建立系统的内存空间映射图, 从而将系统的软硬件环境带到一个合适的状态, 以便为最终调用操作系统内核准备好正确的环境. 最终引导加载程序把操作系统内核映像加载到 RAM 中, 并将系统控制权传递给它.
对于绝大多数计算机系统而言, 操作系统和应用软件是存放在磁盘 (硬盘 / 软盘), 光盘, EPROM,ROM,Flash 等可在掉电后继续保存数据的存储介质上. 计算机启动后, CPU 一开始会到一个特定的地址开始执行指令, 这个特定的地址存放了系统初始化软件, 负责完成计算机基本的 IO 初始化, 这是系统加电后运行的第一段软件代码. 对于 Intel 80386 的体系结构而言, PC 机中的系统初始化软件由 BIOS (Basic Input Output System, 即基本输入 / 输出系统, 其本质是一个固化在主板 Flash/CMOS 上的软件) 和位于软盘 / 硬盘引导扇区中的 OS Boot Loader(在 ucore 中的 bootasm.S 和 bootmain.c)一起组成. BIOS 实际上是被固化在计算机 ROM(只读存储器)芯片上的一个特殊的软件, 为上层软件提供最底层的, 最直接的硬件控制与支持. 更形象地说, BIOS 就是 PC 计算机硬件与上层软件程序之间的一个 "桥梁", 负责访问和控制硬件.
以 Intel 80386 为例, 计算机加电后, CPU 从物理地址 0xFFFFFFF0(由初始化的 CS:EIP 确定, 此时 CS 和 IP 的值分别是 0xF000 和 0xFFF0))开始执行. 在 0xFFFFFFF0 这里只是存放了一条跳转指令, 通过跳转指令跳到 BIOS 例行程序起始点. BIOS 做完计算机硬件自检和初始化后, 会选择一个启动设备 (例如软盘, 硬盘, 光盘等), 并且读取该设备的第一扇区(即主引导扇区或启动扇区) 到内存一个特定的地址 0x7c00 处, 然后 CPU 控制权会转移到那个地址继续执行. 至此 BIOS 的初始化工作做完了, 进一步的工作交给了 bootloader.
计算机加电后, 首先处于 实模式 , 经过 boot loader 转换后切换到 32-bit 保护模式
Boot loader 启动过程总结
BIOS 将通过读取硬盘主引导扇区到内存, 并转跳到对应内存中的位置执行 bootloader.bootloader 完成的工作包括:
切换到保护模式, 启用分段机制
读磁盘中 ELF 执行文件格式的操作系统到内存
显示字符串信息
把控制权交给操作系统
对应实现文件../boot/boot.S 和 ../boot/main.c
A20 gate
8088/8086 只有 20 位地址线, 按理它的寻址空间是 2^20, 应该是 1024KB, 但 PC 机的寻址结构是 segment:offset, 所以 segment:offset 所能表达的寻址空间最大应为 0ffff0h + 0ffffh = 10ffefh(大约 1088kB)
当你用 segment:offset 的方式企图寻址 100000h 这个地址时, 由于没有实际的第 21 位地址线, 你实际寻址的内存是 00000h 的位置, 如果你企图寻址 100001h 这个地址时, 你实际得到的内容是地址 00001h 上的内容
这个事对实际使用几乎没有任何影响, 但是后来就不行了, 出现了 80286, 地址线达到了 24 位, 使 segment:offset 寻址 100000h--10ffefh 这将近 64K 的存储器成为可能, 为了保持向下兼容, 于是出现了 A20 Gate
扩展内存: 1M 以上的内存寻址空间
这里面绝大部分内存区域只能在保护模式下才能寻址到,
但有一部分既可以在保护模式下, 也可以在实模式下寻址, 这就是我们前面提到过的地址 100000h--10ffefh 之间的这块内存, 为了表明其特殊性, 我们把这块有趣的内存区叫做 "高端内存".
(如果当初 IBM 把上位内存区的东西放在低端, 就没有这么多麻烦了)
ROM 和 RAM 的地址重叠
实际的内存条上地址都是连续的, 采用技术手段把这段地址空间空出来给 ROM 用, 比浪费这 384K 内存的成本还要高 所以采用 ROM 和 RAM 的地址重叠
实际上, 往往 ROM 并不能完全覆盖整个 384K 区域, 这样就会有一些地址没有被 ROM 占用, 那么这部分地址上的 RAM 仍然是可以使用的.
ROM Shadowing:
RAM 和 ROM 的性能是有很大差异的, RAM 的存取速度要远远大于 ROM, 而且 RAM 可以 32 位存取, ROM 通常只能 16 位
当机器加电后, 先让 ROM 有效, RAM 无效, 然后读出 ROM 内容, 再让 ROM 无效, RAM 有效, 把读出的 ROM 内容放到相同地址的 RAM 中, 并把相应位置的 RAM 设定为只读, 这样就把 ROM 搬到了 RAM 中, 地址完全一样, 只是性能比使用 ROM 要高些, 这块 RAM 就好像 ROM 的 Shadow 一样.
A20 gate:
出现 80286 以后, 为了保持和 8086 的兼容, 需要使用第 21 根地址总线在设计上在第 21 条地址线 (也就是 A20) 上做了一个开关, 当这个开关打开时, 这条地址线和其它地址线一样可以使用, 当这个开关关闭时, 第 21 条地址线 (A20) 恒为 0
A20 gate 在什么时候需要打开
在实模式下要访问高端内存区, 这个开关必须打开;
在保护模式下, 由于使用 32 位地址线, 如果 A20 恒等于 0, 那么系统只能访问奇数兆的内存, 即只能访问 0--1M,2-3M,4-5M......, 这显然是不行的, 所以在保护模式下, 这个开关也必须打开
PC 如何实现 A20 gate:
用 8042 芯片(控制键盘的单独的单片机), 但与键盘毫无关系
参考资料:
读 boot/boot.S 和 boot/boot.c 源码
- boot/boot.S
该文件的目的:
- start CPU, switch to 32-bit protected mode(启动 CPU 并且最终转到 32-bit 保护模式)
- BIOS loads code from first sector of the hard disk into memory at physical addr 07xc00
- executing in real mode (%cs=0, %ip=7c00)
步骤:
初始化重要的 segment registers, 全部初始化为 0
16 位指令下, 屏蔽中断, 初始化段寄存器
开启 A20 gate, 停止取模运算, 将高位的空间也可访问
利用 Bootstrap GDT 转换到 protected mode
跳转到 32-bit 模式下的下一个指令
然后在 32-bit 保护模式下, 设置保护模式的寄存器
设置 stack pointer 然后调用 main.c 执行 main.c 里面的 bootmain 函数
关于开启 A20 gate 的代码部分解析:
- # Enable A20:
- # For backwards compatibility with the earliest PCs, physical
- # address line 20 is tied low, so that addresses higher than
- # 1MB wrap around to zero by default. This code undoes this.
- seta20.1:
- inb $0x64,%al # Wait for not busy
- testb $0x2,%al
- jnz seta20.1
- movb $0xd1,%al # 0xd1 -> port 0x64
- outb %al,$0x64
- seta20.2:
- inb $0x64,%al # Wait for not busy
- testb $0x2,%al
- jnz seta20.2
- movb $0xdf,%al # 0xdf -> port 0x60
- outb %al,$0x60
这部分指令就是在准备把 CPU 的工作模式从实模式转换为保护模式. 我们可以看到其中的指令包括 inb,outb 这样的 IO 端口命令. 所以这些指令都是在对外部设备进行操作. 根据下面的链接:
http://bochs.sourceforge.net/techspec/PORTS.LST
我们可以查看到, 0x64 端口属于键盘控制器 804x, 名称是控制器读取状态寄存器. 下面是它各个位的含义.
所以 16~18 号指令是在不断的检测 bit1.bit1 的值代表输入缓冲区是否满了, 也就是说 CPU 传送给控制器的数据, 控制器是否已经取走了, 如果 CPU 想向控制器传送新的数据的话, 必须先保证这一位为 0. 所以这三条指令会一直等待这一位变为 0, 才能继续向后运行.
当 0x64 端口准备好读入数据后, 现在就可以写入数据了, 所以 19~20 这两条指令是把 0xd1 这条数据写入到 0x64 端口中. 当向 0x64 端口写入数据时, 则代表向键盘控制器 804x 发送指令. 这个指令将会被送给 0x60 端口.
通过图中可见, D1 指令代表下一次写入 0x60 端口的数据将被写入给 804x 控制器的输出端口. 可以理解为下一个写入 0x60 端口的数据是一个控制指令.
然后 21~24 号指令又开始再次等待, 等待刚刚写入的指令 D1, 是否已经被读取了.
如果指令被读取了, 25~26 号指令会向控制器输入新的指令, 0xdf. 通过查询我们看到 0xDF 指令的含义如下
这个指令的含义可以从图中看到, 使能 A20 线, 代表可以进入保护模式了.
boot.S & main.c 代码分析链接:
- https://www.cnblogs.com/fatsheep9146/p/5115086.html
- - boot/mian.c
boot.S & main.c 存在磁盘第一个扇区
第二个扇区开始保存 kernel
内核需为 ELF 格式
Boot up steps:
CPU 启动后, 加载 BIOS 进入内存并执行它
BIOS 初始化设备, 一系列中断准备, 读取第一个扇区的 boot device 到内存并跳到该处
从 boot.S 开始控制, 它建立保护模式 + 1 个 stack, 以便 C 代码可以跑, 然后调用 bootmain()函数
bootmain()函数读取内核并跳到内核
segment 和 sector 的关系: 一个 segment 包含多个 sector
- readsect(void *dst, uint32_t offset)
- readseg(uchar *pa, uint count, uint offset)
它的功能从注释上来理解是, 把距离内核起始地址 offset 个偏移量存储单元作为起始, 将它和它之后的 count 字节的数据读出送入以 pa 为起始地址的内存物理地址处.
来源: https://www.cnblogs.com/cindycindy/p/mit_6_828_Lab01_Booting_a_PC_Part2.html