L2 开始揭开钢琴的盖子
1. 计算机打开电源时执行的第一条指令
通常是IP指针(或PC指针)指向的内容,由硬件设计者决定。
以 x86 计算机为例:
- x86 PC 刚开机时,CPU处于 实模式,寻址方式为 CS:IP
- 开机时,CS = 0xFFFF,IP = 0x0000
- 从 ROM BIOS 映射区 寻址,地址为 0xFFFF0
- 检查 RAM、键盘、显示器、磁盘、……
- 将磁盘的 0 磁道 0 扇区(即操作系统的引导扇区)的 512 字节的内容 读入内存 0x7c00 处
- 设置 CS = 0x07c0,IP = 0x0000
地址 |
内容 |
0xFFFFFFFF ~ ? |
ROM BIOS |
|
…… |
0x100000 ~ 0xF0000 |
[入口] ROM BIOS 映射区
|
|
…… |
? ~ 0x7c00 |
留给 0磁道 0 扇区
|
|
…… |
? ~ 0x00000000 |
…… |
2. 0x7c00处存放的代码
磁盘的引导扇区(0 磁道 0 扇区)的 512 字节的内容会被读到内存地址 0x7c00 处。其中包括启动设备的信息,存放着开机后执行的第一段可被控制的程序。
操作系统由此开始。
2.1 引导扇区代码:bootsect.s
三个重要字段:
BOOTSEG = 0x7c0
INITSEG = 0x9000
SETUPSEG = 0x9020
bootsect.s:
.global begtext, begdata, begbss, endtext, enddata, endbss
.text // 文本段
begtext:
.data // 数据段
begdata:
.bss // 未初始化数据段
begbss:
entry start // 关键字 entry 告诉链接器 "程序入口"
start:
mov ax, #BOOTSEG mov ds, ax
mov ax, #INITSEG mov es, ax
mov cx, #256
sub si, si sub di, di
rep movw // 重复移动 256 个字
jmpi go, INITSEG // 段间跳转
得到两个地址 ds:si = 0x7c00 和 es:di = 0x90000。然后将 0x7c00 处的 256 个字移动到 0x90000 处,腾出内存。
go: mov ax, cs // cs = 0x9000
mov ds, ax mov es, ax mov ss, ax mov sp, #0xff00
load_setup: // 载入setup模块
mov dx, #0x0000 mov cx, #0x0002 mov bx, #0x0200
mov ax, #0x0200+SETUPLEN int 0x13 // BIOS 中断
jnc ok_load_setup
mov dx, #0x0000
mov ax, #0x0000 // 复位
int 0x13
j load_setup // 重读
int 0x13 代表读磁盘扇区的中断;
ah = 0x02 - 读磁盘,al = 扇区数量(SETUPLEN = 4),
ch = 柱面号,cl = 开始扇区,
dh = 磁头号,dl = 驱动器号,
es:bx = 内存地址。
即从第二个扇区开始读 4 个扇区,这 4 个扇区就是 setup 模块所在的扇区,读到内存地址 0x90200 处。
Ok_load_setup: // 载入 setup 模块
mov dl,#0x00 mov ax,#0x0800 // ah = 8
int 0x13 movch,#0x00 mov sectors,cx
mov ah,#0x03 xor bh,bh int 0x10 // 读光标
mov cx,#24 mov bx,#0x0007 // 7 是显示属性
mov bp,#msg1 mov ax,#1301 int 0x10 // 显示字符
mov ax,#SYSSEG // SYSSEG = 0x1000
mov es,ax
call read_it // 读入 system 模块
jmpi 0,SETUPSEG
……
sectors: .word 0 // 磁道扇区数
msg1: .byte 13,10
.ascii "Loading system..."
.byte 13,10,13,10
int 0x10 代表显示 logo,即显示 msg1 中的内容。同时,cx = 24 代表输出的字符数量。
最后 jmpi 0,SETUPSEG,即跳转至 setup 对应的内存地址执行。
read_it: mov ax,es cmp ax,#ENDSEG jb ok1_read
ret
ok1_read:
mov ax,sectors
sub ax,sread // sread 是当前已读扇区数,ax 是未读扇区数
call read_track // 读磁道
读取 system 模块的相关代码。
3. 小结
打开电源,首先执行 boot(引导扇区的内容),随后 boot 会完成 setup 的读取工作以及 system 的读取工作。