操作系统真象还原第三章
3.1 地址、section、vstart 浅尝辄止
3.1.1 什么是地址
地址描述各种符号在源程序中的位置,是源代码中各符号偏移文件开头的距离。
编译器的工作就是给各符号编址。
3.1.2 什么是 section
编译器提供的关键字 section 只是为了让程序员在逻辑上将程序划分成几个部分。
3.1.3 什么是 vstart
section 用 vstart= 来修饰后,可以被赋予一个虚拟起始地址。
mbr 用 vstart=0x7c00
来修饰的原因,是开发人员知道 mbr 要被加载器(BIOS)加载到物理地址 0x7c00,mbr 中后续的物理地址都是 0x7c00+。
3.2 CPU 的实模式
3.2.1 CPU 的工作原理
CPU 大体上可以划分为 3 个部分:
- 控制单元。由指令寄存器 IR、指令译码器 ID、操作控制器 OC 组成。
- 存储单元。CPU 内部的 L1、L2缓存及寄存器。
- 运算单元。负责算术运算(加减乘除)和逻辑运算(比较、移位)。
3.2.2 实模式下的寄存器
CPU 中的寄存器大致上分为两大类:
- 对程序员不可见的寄存器。比如全局描述符表寄存器 GDTR、中断描述符表寄存器 IDTR、局部描述符表寄存器 LDTR、任务寄存器 TR、控制寄存器 CR0~3、指令指针寄存器 IP、标志寄存器 flags、调试寄存器 DR0~7。
- 对程序员可见的寄存器。比如段寄存器、通用寄存器。
实模式下,默认用到的寄存器都是 16 位宽的。
CPU 用“段基址:段内偏移地址”的形式访问内存。
段基址在实模式下要乘以 16,在保护模式下只是个选择子,但其作用就是指定一片内存的起始地址。
IP 寄存器是不可见寄存器,CS 寄存器是可见寄存器。
无论是实模式还是保护模式,通用寄存器有 8 个:
- AX
- BX
- CX
- DX
- SI
- DI
- BP
- SP
通用寄存器:
寄存器 | 助记名称 | 功能描述 |
---|---|---|
ax | 累加器 | 常用于算术运算、逻辑运算、保存与外设输入输出的数据 |
bx | 基址寄存器 | 常用于存储内存地址 |
cx | 计数器 | 常用于循环指令中的循环次数 |
dx | 数据寄存器 | 通常情况下只用于保存外设控制器的端口号地址 |
si | 源变址寄存器 | 常用于字符串操作中的数据源地址 |
di | 目的变址寄存器 | 常用于字符串操作中的数据目的地址 |
sp | 栈指针寄存器 | 段基址SS用来指向栈顶,push和pop操作会修改sp的值 |
bp | 基址指针 | 可通过SS:bp的方式把栈当作普通的数据段来访问 |
3.2.3 实模式下内存分段由来
实模式的“实”体现在:程序中用到的地址都是真实的物理地址,“段基址:段内偏移”产生的逻辑地址就是物理地址。
3.2.4 实模式下 CPU 内存寻址方式
寻址方式:
- 寄存器寻址。直接从寄存器拿数据。
- 立即数寻址。立即数就是常数。
- 内存寻址。操作数在内存中。
- 直接寻址。将操作数中给出的数字作为内存地址。
- 基址寻址。在操作数中用 bx 寄存器或 bp 寄存器作为地址的起始,地址的变化以它为基础。
- 变址寻址。和基址寻址类似,只是寄存器由 bx、bp 换成了 si 和 di。默认段寄存器是 dx。
- 基址变址寻址。基址变址和变址寻址的组合。
3.2.5 栈到底是什么玩意儿
栈和数据段、代码段一样,是个内存中的区域。
3.2.6 实模式下的 ret
ret 指令的功能是在栈顶弹出 2 字节的内容来替换 IP 寄存器。
retf 是从栈顶取得 4 字节,栈顶处的 2 字节用来替换 IP 寄存器,另外的 2 字节用来替换 CS 寄存器。
call 和 ret 是一对配合。call far 和 retf 是一对配合。
3.2.7 实模式下的 call
call 指令所调用目标函数和当前代码段是同一个段,即在同一个 64KB 的空间内。
xxd
命令用于逐字节查看文件。
- 16 位实模式相对近调用。call 指令的操作数是目标地址相对于 call 指令的地址偏移量。
- 16 位实模式间接绝对近调用。目标函数的地址不以立即数的形式出现,且是绝对地址。
- 16 位实模式直接绝对远调用。目标函数的地址是立即数,和当前指令不在同一个段中。
- 16 位实模式间接绝对远调用。目标函数的地址不是立即数,和当前指令不在同一个段中。
3.2.8 实模式下的 jmp
jmp
转移指令只需要更新 CS:IP 寄存器或只更新 IP 寄存器就好了,不需要保存它们的值。
- 16 位实模式相对短转移。操作数是个相对增量。
- 16 位实模式相对近转移。比起第一种方式,操作数由 8 位变成 16 位。
- 16 位实模式间接绝对近转移。目标地址是绝对地址,存在寄存器或内存中。
- 16 位实模式直接绝对远转移。跨段转移。
- 16 位实模式间接绝对远转移。段基址和段内偏移存在寄存器或内存中。
3.2.9 标志寄存器 flags
实模式下标志寄存器是 16 位的 flags,在 32 位保护模式下,扩展了标志寄存器,成为 32 位的 eflags。
3.2.10 有条件转移
有条件转移不是简单的一个指令,它是一个指令族,简称 jxx
。如果条件满足,jxx
将会跳转到指定的位置去执行,否则继续顺序地执行下一条指令。
目标地址只能是段内偏移地址。
进行条件转移,所谓的条件就是判断上一条指令地结果是否满足某方面或某些方面,能够影响标志位的指令才能被其后的条件指令用作条件。
3.2.11 实模式小结
在实模式下,用户程序和操作系统是同一特权的程序,因为实模式下没有特权级,所以用户程序可以执行一些具有破坏性的指令。
3.3 让我们直接对显示器说点什么吧
3.3.1 CPU 如何与外设通信 – IO 接口
IO 接口是连接 CPU 与外部设备的逻辑控制部件,分为硬件和软件两部分:
- 硬件部分的工作是协调 CPU 和外设。如数据缓冲和数据格式转换。
- 软件部分的工作是控制接口电路工作的驱动程序以及完成内部数据传输所需要的程序。
南桥用于连接 pci、pci-express、AGP 等低速设备,北桥用于连接高速设备,如内存。
IO 接口中的寄存器称为端口。IO 接口通过端口与 CPU 通信。
in
指令用于从端口中读取数据:
in al, dx
in ax, dx
其中 al
和 ax
用来存储从端口获取的数据,dx
是指端口号。
out
指令用于往端口中写数据:
out dx, al
out dx, ax
out 立即数, al
out 立即数, ax
out
指令中的目的操作数是端口号。
in
指令中,端口号只能用 dx
寄存器。out
指令中,可以选用 dx
寄存器或立即数充当端口号。
in
指令用 al
寄存器存储 8 位宽度的数据,用 ax
寄存器存储 16 位宽度的数据。
3.3.2 显卡概述
中断向量表只在实模式下存在,保护模式下没有中断向量表。
pci 总线是共享并行架构。PCI Express 总线是串行架构,简称 pcie。
3.3.3 显存、显卡、显示器
无论是哪种显卡,提供的可编程接口都是:IO 端口和显存。
显卡的工作就是不断读取显存,随后将其内容发送到显示器。
显存地址分布:
起始 | 结束 | 大小 | 用途 |
---|---|---|---|
C0000 | C7FFF | 32KB | 显式适配器 BIOS |
B8000 | BFFFF | 32KB | 用于文本模式显式适配器 |
B0000 | B7FFF | 32KB | 用于黑白显式适配器 |
A0000 | AFFFF | 64KB | 用于彩色显式适配器 |
显卡也有自己的 BIOS。
显卡的文本模式分为多种模式,用“行数*列数”来表示,如“80*25”,“40*25”,“80*43” 或者 “80*50”。
显卡在加电后,默认模式是“80*25”。
屏幕上的每个字符都是由 2 个字节来表示的:
- 低字节是字符的 ASCII 码。
- 高字节是字符属性元信息。低 4 位是字符前景色,高 4 位是字符背景色。第 4 位用来控制亮度,第 7 位用来控制字符是否闪烁。
3.3.4 改进 MBR,直接操作显卡
3.4 bochs 调试方法
bochs 是一个开源 x86 虚拟机软件,在它的实现中定义了各种数据结构来模拟硬件。
bochs 支持硬件级别的调试:
- 调试时可以查看页表、gdt、idt 等数据结构
- 可以查看栈中数据
- 可以返汇编任意内存
- 实模式、保护模式互相变换时提醒
- 中断发生时提醒
nm
命令用来列出可执行文件的符号表及其地址。纯二进制文件不支持,支持 elf 文件,纯二进制文件中不包含符号表。
3.5 硬盘介绍
3.5.1 硬盘发展简史
3.5.2 硬盘工作原理
将磁盘整个盘面划分为多个同心环,以圆心画扇型,扇型与每个同心环相交的弧状区域作为最基本的数据存储单元。这个同心环就称为磁道,而同心环上的弧状区域是扇型的一部分,称为扇区。不同盘面的磁道组成的管状区域就称为柱面。
扇区的编号从1开始。盘面和磁道的编号从0开始。扇区有自己的“头部”,包含扇区自身的信息:磁头号、磁道号和扇区号。
针对硬盘的 IO 接口时硬盘控制器。
3.5.3 硬盘控制器端口
端口分为两组:
- Command Block registers。用于向硬盘驱动器写入命令字或者从硬盘控制器获得硬盘状态
- Control Block registers。用于控制硬盘工作状态
端口时按照通道给出的,要想操作某通道上的某块硬盘,需要单独指定。
data 寄存器的作用是读取或写入数据,宽度是16位(其它硬盘控制器寄存器都是8位)。在读硬盘时,硬盘准备好数据后,硬盘控制器将其放在内部的缓冲区中,不断读此寄存器便是读出缓冲区中的全部数据。在写硬盘时,把数据输送到此端口,数据便被存入缓冲区里,硬盘控制器发现这个缓冲区中有数据了,便将此处的数据写入相应的扇区中。
Error 和 Feature 寄存器都是 8 位宽度。
Sector count 寄存器用来指定待读取或待写入的扇区数。硬盘每完成一个扇区,就会将此寄存器的值减1,所以如果中间失败了,此寄存器中的值便是尚未完成的扇区。最大值是255,若指定为0,表示要操作256个扇区。
逻辑块地址(Logical Block Address),扇区从 0 开始依次递增编号,不用考虑扇区所在的物理结构。
LBA 有两种:
- LBA28。用 28 位比特来描述一个扇区的地址,最大支持128GB
- LBA48。用 48 位比特来描述一个扇区的地址,最大支持128PB
LBA 寄存器有 LBA low、LBA mid、LBA high 三个。LBA low 寄存器用来存储 28 位地址的第 0 ~ 7 位,LBA mid 寄存器用来存储第 8 ~ 15 位,LBA high 寄存器存储第 16 ~ 23 位。
device 寄存器的低 4 位用来存储 LBA 地址的第 24 ~ 27 位。第 4 位用来指定通道上的主盘或从盘,0 代表主盘,1 代表从盘。第 6 位用来设置是否启用 LBA 方式,1 代表启用 LBA 模式,0 代表启用 CHS 模式。第 5 和第 7 位固定为 1,称为 MBS 位。
在读硬盘时,Status 寄存器给出硬盘的状态信息。第 0 位是 ERR 位,如果此位为 1,表示命令出错。第 3 位是 data request 位,如果此位为 1,表示硬盘已经把数据准备好了,主机可以把数据读出来。第 6 位是 DRDY,表示硬盘就绪,可以执行一些命令。第 7 位是 BSY 位,表示硬盘是否繁忙,如果为 1 表示硬盘正忙,此寄存器中其他位都无效。在写硬盘时,Command 寄存器用来存储让硬盘执行的命令,只要把命令写进此寄存器,硬盘就开始工作了。
将要使用的命令:
- identify:0xEC,硬盘识别
- read sector:0x20,读扇区
- write sector:0x30,写扇区
3.5.4 常用的硬盘操作方法
command 寄存器最后写,一旦 command 寄存器被写入,硬盘就开始干活。
建议操作步骤:
- 先选择通道,往该通道的 sector count 寄存器写入待操作的扇区数
- 往该通道上的三个 LBA 寄存器写入扇区起始地址的低 24 位
- 往 device 寄存器中写入 LBA 地址的 24 ~ 27 位,并置第 6 位 1,使其为 LBA 模式,设置第 4 位,选择操作的硬盘(主盘或从盘)
- 往该通道上的 command 寄存器写入操作命令
- 读取该通道上的 status 寄存器,判断硬盘工作是否完成
- 如果以上步骤是读硬盘,进入下一个步骤。否则完工
- 将硬盘数据读出
常用的数据传送方式:
- 无条件传送。数据源设备随时准备好数据,CPU随时拿
- 查询传送。传输前需要先去检测设备的状态
- 中断传送。数据源准备好后,发中断通知 CPU 来拿数据
- 直接存储器存取方式(DMA)。需要 DMA 控制器
- I/O 处理机传送方式。一种专用于处理 I/O 的处理器
3.6 让 MBR 使用硬盘
MBR 负责从硬盘上把 loader 加载到内存,并将接力棒交给它。
loader 中要定义一些数据结构(如 GDT 全局描述符表),这些数据结构将来的内核还是要用的,所以 loader 记载到内存后不能被覆盖。
代码:github仓库,tag: chapter3。
去掉了书中代码 mbr.S 中不必要的打印字符部分以及添加了更多注释。