AT&T 语法增强版 Setup 程序开发:从实模式到保护模式的过渡实现(Ubuntu 20.04 全流程)
在操作系统启动流程中,Setup 程序承担着从实模式向保护模式过渡的关键任务。本文基于前期引导扇区与基础 Setup 程序框架,开发增强版 Setup 程序,实现硬件信息收集、GDT 配置及保护模式切换功能,完整覆盖从 BIOS 到内核加载前的初始化流程。
一、开发环境与工具链复用
1. 工具链依赖
沿用引导扇区开发的工具链(版本验证通过),确保环境一致性:
bash
# 工具链清单(同前期配置,无需重复安装)
sudo apt update && sudo apt install -y \
binutils gcc qemu-system-x86 qemu-utils \
util-linux xxd vim nano
2. 环境验证
通过前期编写的 check_boot_env.sh
脚本验证环境,确保 16 位汇编支持与工具完整性:
bash
chmod +x check_boot_env.sh
./check_boot_env.sh
显示 [SUCCESS] 开发环境就绪
即可进入开发。
二、增强版 Setup 程序设计与实现
增强版 Setup 程序需完成以下核心任务:
- 收集硬件信息(内存大小、显示参数、磁盘信息)
- 配置中断控制器与键盘控制器
- 构建全局描述符表(GDT)
- 从实模式切换到 32 位保护模式
1. 代码实现(setup.s)
asm
.code16 ; 初始运行于16位实模式
.text
.global _start_setup
/* 常量定义 */
INITSEG = 0x9000 ; 硬件信息存储段地址
SETUPSEG = 0x9020 ; Setup程序自身段地址
_start_setup:
/* 初始化段寄存器(CS=SETUPSEG,同步DS/ES) */
movw %cs, %ax
movw %ax, %ds ; 数据段 = 代码段
movw %ax, %es ; 附加段 = 代码段
/* 打印启动信息:"setup is running [enhanced]" */
movw $setup_msg, %ax
movw %ax, %bp ; ES:BP 指向字符串
movw $0x1301, %ax ; AH=0x13(字符串显示), AL=0x01(带属性+光标移动)
movw $0x000C, %bx ; BH=0(页0), BL=0x0C(红底白字)
movw $24, %cx ; 字符串长度
movb $3, %dh ; 行号3
movb $0, %dl ; 列号0
int $0x10 ; 调用BIOS显示中断
/* 阶段1:收集硬件信息并存储到INITSEG段 */
call collect_hardware_info
/* 阶段2:准备保护模式环境 */
call prepare_protected_mode
/* 阶段3:切换到保护模式并跳转 */
call enter_protected_mode
hang:
jmp hang ; 异常情况下的死循环
/* 硬件信息收集函数 */
collect_hardware_info:
/* 1. 收集光标位置(BIOS中断0x10, 功能0x03) */
movb $0x03, %ah
xorw %bh, %bh ; 页0
int $0x10
movw $INITSEG, %ax
movw %ax, %ds
movw %dx, (0) ; 存储光标位置到0x9000:0000
/* 2. 收集扩展内存大小(BIOS中断0x15, 功能0x88) */
movb $0x88, %ah
int $0x15
movw %ax, (2) ; 存储到0x9000:0002(KB)
/* 3. 收集显示模式信息(BIOS中断0x10, 功能0x0F) */
movb $0x0F, %ah
int $0x10
movw %bx, (4) ; 存储显示页/颜色模式到0x9000:0004
movw %ax, (6) ; 存储列数到0x9000:0006
/* 4. 收集硬盘参数(从BIOS数据区复制) */
; 复制第一硬盘参数(0x41*4=0x104)
movw $0x0000, %ax
movw %ax, %ds
ldsw (0x104), %si ; SI = 硬盘参数表地址
movw $INITSEG, %ax
movw %ax, %es
movw $0x0080, %di ; 目标地址:0x9000:0080
movw $0x10, %cx ; 复制16字节
rep
movsb
; 复制第二硬盘参数(0x46*4=0x118)
movw $0x0000, %ax
movw %ax, %ds
ldsw (0x118), %si
movw $INITSEG, %ax
movw %ax, %es
movw $0x0090, %di ; 目标地址:0x9000:0090
movw $0x10, %cx
rep
movsb
ret
/* 保护模式环境准备函数 */
prepare_protected_mode:
cli ; 关闭中断(避免切换期间中断干扰)
/* 移动内核代码到0x0000:0000(假设内核在0x1000段) */
movw $0x1000, %ax ; 源段起始地址
do_move:
movw %ax, %es ; ES = 目标段(初始为0x1000)
addw $0x1000, %ax ; 源段 = 目标段 + 0x1000
cmpw $0x9000, %ax ; 移动到0x9000段为止
jz end_move
movw %ax, %ds ; DS = 源段
xorw %si, %si ; SI=0(源偏移)
xorw %di, %di ; DI=0(目标偏移)
movw $0x8000, %cx ; 复制0x8000字(64KB)
rep
movsw ; 批量移动
jmp do_move
end_move:
/* 配置8042键盘控制器(允许保护模式下键盘中断) */
call empty_8042 ; 等待控制器空闲
movb $0xD1, %al ; 命令:写输出端口
outb %al, $0x64
call empty_8042
movb $0xDF, %al ; 允许A20地址线
outb %al, $0x60
call empty_8042
/* 配置8259中断控制器(屏蔽所有中断,后续由内核启用) */
movb $0x11, %al ; 初始化命令字ICW1
outb %al, $0x20 ; 主8259
outb %al, $0xA0 ; 从8259
.word 0x00eb, 0x00eb ; 短延迟(替代nop)
movb $0x20, %al ; 主8259向量基地址0x20
outb %al, $0x21
.word 0x00eb, 0x00eb
movb $0x04, %al ; 主8259级联标识
outb %al, $0x21
.word 0x00eb, 0x00eb
movb $0x01, %al ; 8086模式
outb %al, $0x21
.word 0x00eb, 0x00eb
outb %al, $0xA1 ; 从8259配置(同上)
.word 0x00eb, 0x00eb
movb $0xFF, %al ; 屏蔽所有中断
outb %al, $0x21
outb %al, $0xA1
ret
/* 进入保护模式函数 */
enter_protected_mode:
/* 加载GDT(全局描述符表) */
movw $SETUPSEG, %ax
movw %ax, %ds
lgdt gdt_48 ; 加载GDT描述符
/* 设置CR0寄存器PE位(保护模式使能) */
movl %cr0, %eax
orl $0x00000001, %eax ; 第0位=1
movl %eax, %cr0
/* 远跳转到保护模式代码(清空流水线) */
.byte 0x66, 0xea ; 32位远跳转前缀
.long 0x00000000 ; 偏移地址0
.word 0x0008 ; 段选择子(GDT代码段)
/* 8042控制器空闲等待函数 */
empty_8042:
.word 0x00eb, 0x00eb ; 延迟
inb $0x64, %al ; 读取状态寄存器
testb $0x02, %al ; 检查输入缓冲区是否为空
jnz empty_8042 ; 非空则继续等待
ret
/* 全局描述符表(GDT)定义 */
gdt:
/* 空描述符(必须存在) */
.word 0, 0, 0, 0
/* 代码段描述符:基地址0,限长0xFFFFF,可读可执行,特权级0 */
.word 0xFFFF ; 限长(低16位)
.word 0x0000 ; 基地址(低16位)
.byte 0x00 ; 基地址(中8位)
.byte 0b10011010 ; 访问位:P=1, DPL=0, 代码段, 可执行, 可读
.byte 0b11001111 ; 标志位:G=1(4KB粒度), D=1(32位), 限长高4位
.byte 0x00 ; 基地址(高8位)
/* 数据段描述符:基地址0,限长0xFFFFF,可读可写,特权级0 */
.word 0xFFFF
.word 0x0000
.byte 0x00
.byte 0b10010010 ; 访问位:P=1, DPL=0, 数据段, 可写
.byte 0b11001111
.byte 0x00
/* 视频段描述符:基地址0xB8000(VGA显存),限长0x07FF */
.word 0x07FF
.word 0x8000
.byte 0x0B
.byte 0b10010010 ; 数据段,可写
.byte 0b00000000 ; 16位模式兼容
.byte 0x00
gdt_end:
/* GDT描述符(供lgdt指令使用) */
gdt_48:
.word gdt_end - gdt - 1 ; GDT限长
.word gdt + SETUPSEG * 16, 0 ; GDT基地址(段地址转换为线性地址)
/* 字符串定义 */
setup_msg:
.ascii "setup is running [enhanced]"
/* 填充至2048字节(4个扇区) */
.fill 2048 - (.-_start_setup), 1, 0
三、镜像构建与验证流程
1. 修改 Makefile 适配增强版 Setup
沿用前期 Makefile 框架,无需修改(已兼容 setup.s 编译):
makefile
# 工具链定义(同前期)
AS := as --32
LD := ld -m elf_i386
LDFLAGS := -Ttext 0x0 -s --oformat binary
all: linux.img
linux.img: build bootsect setup
./build > linux.img
bootsect: bootsect.o
$(LD) $(LDFLAGS) -o $@ $<
bootsect.o: bootsect.s
$(AS) -o $@ $<
setup: setup.o
$(LD) $(LDFLAGS) -e _start_setup -o $@ $<
setup.o: setup.s
$(AS) -o $@ $<
build: build.c
gcc -o $@ $<
clean:
rm -f *.o bootsect setup build linux.img
run:
qemu-system-i386 -fda linux.img -boot a -vga std -debugcon stdio
2. 编译与生成镜像
bash
# 清理旧文件并重新编译
make clean && make
生成 linux.img
镜像文件,包含引导扇区(512 字节)和增强版 Setup 程序(2048 字节)。
3. 关键验证步骤
bash
# 1. 验证Setup程序大小(必须为2048字节)
ls -l setup | awk '{print "setup大小:" $5 "字节(应等于2048)"}'
# 2. 查看GDT表定义(确保无语法错误)
objdump -s -j .text setup.o | grep -A 20 "gdt:"
# 3. 验证引导扇区标志
xxd -s 510 -l 2 bootsect # 应显示0000200: aa55
四、QEMU 运行与调试配置
1. 基础运行命令
bash
make run # 或直接执行:qemu-system-i386 -fda linux.img -boot a -vga std
成功运行后,QEMU 窗口将显示:
- 第 1 行:
Setup has been loaded
(引导扇区输出) - 第 3 行:
setup is running [enhanced]
(增强版 Setup 输出) - 随后进入保护模式切换流程(屏幕可能短暂黑屏,属正常现象)
2. 调试模式运行
通过 QEMU 调试输出观察硬件信息收集过程:
bash
qemu-system-i386 -fda linux.img -boot a -vga std -debugcon stdio
调试信息将显示:
- BIOS 加载 Setup 程序的过程
- 硬盘参数读取结果
- 保护模式切换时的中断控制器配置日志
五、常见问题解决
-
保护模式切换后无响应
- 检查 GDT 基地址计算:
gdt_48
中基地址需转换为线性地址(SETUPSEG * 16 + gdt
) - 验证
lgdt
指令前ds
寄存器是否正确设置为SETUPSEG
- 确认远跳转指令的段选择子(
0x0008
)对应 GDT 中的代码段
- 检查 GDT 基地址计算:
-
硬件信息收集异常
- 内存大小读取失败:部分 BIOS 不支持
int 0x15
功能0x88
,可改用功能0xE820
兼容 - 硬盘参数为空:在虚拟机中需配置至少 1 个硬盘设备
- 内存大小读取失败:部分 BIOS 不支持
-
A20 地址线启用失败
- 检查 8042 控制器交互流程,确保
empty_8042
函数正确等待控制器空闲 - 替换延迟指令(
.word 0x00eb, 0x00eb
)为多个nop
指令(兼容部分硬件)
- 检查 8042 控制器交互流程,确保