5.从零开始写LINUX内核--从实模式到保护模式的过渡实现

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 程序的过程
  • 硬盘参数读取结果
  • 保护模式切换时的中断控制器配置日志

五、常见问题解决

  1. 保护模式切换后无响应

    • 检查 GDT 基地址计算:gdt_48 中基地址需转换为线性地址(SETUPSEG * 16 + gdt
    • 验证 lgdt 指令前 ds 寄存器是否正确设置为 SETUPSEG
    • 确认远跳转指令的段选择子(0x0008)对应 GDT 中的代码段
  2. 硬件信息收集异常

    • 内存大小读取失败:部分 BIOS 不支持 int 0x15 功能 0x88,可改用功能 0xE820 兼容
    • 硬盘参数为空:在虚拟机中需配置至少 1 个硬盘设备
  3. A20 地址线启用失败

    • 检查 8042 控制器交互流程,确保 empty_8042 函数正确等待控制器空闲
    • 替换延迟指令(.word 0x00eb, 0x00eb)为多个 nop 指令(兼容部分硬件)

总结

相关推荐
wAIxiSeu22 分钟前
Github开源项目推荐
开源·github
188号安全攻城狮25 分钟前
【PWN】HappyNewYearCTF_8_ret2csu
linux·汇编·安全·网络安全·系统安全
Tansmjs42 分钟前
使用Python自动收发邮件
jvm·数据库·python
m0_5613596744 分钟前
用Python监控系统日志并发送警报
jvm·数据库·python
Dxy12393102161 小时前
MySQL INSERT ... ON DUPLICATE KEY UPDATE 与非主键唯一字段
数据库·mysql
zhousenshan1 小时前
springboot事务管理几种方式
数据库
开源能源管理系统1 小时前
MyEMS开源能源管理系统赋能化纤织造产业绿色转型
开源·能源·能源管理系统·零碳工厂
Yana.nice2 小时前
openssl将证书从p7b转换为crt格式
java·linux
zhangfeng11332 小时前
ModelScope(魔搭社区)介绍与模型微调全指南 中国版Hugging Face GPU租借平台 一站式开源模型社区与服务平台
人工智能·开源
布局呆星2 小时前
SQLite数据库的介绍与使用
数据库·python