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 指令(兼容部分硬件)

总结

相关推荐
青鱼入云25 分钟前
mysql查询中的filesort是指什么
数据库·mysql
开航母的李大28 分钟前
Navicat 全量&增量数据库迁移
数据库·oracle
十五年专注C++开发32 分钟前
CMake进阶: externalproject_add用于在构建阶段下载、配置、构建和安装外部项目
linux·c++·windows·cmake·自动化构建
Skylar_.1 小时前
嵌入式 - Linux软件编程:进程
java·linux·服务器
rannn_1111 小时前
【Linux学习|黑马笔记|Day4】IP地址、主机名、网络请求、下载、端口、进程管理、主机状态监控、环境变量、文件的上传和下载、压缩和解压
linux·笔记·后端·学习
長琹1 小时前
9、C 语言内存管理知识点总结
linux·c语言
野生柚子2 小时前
记录学习K8s 集群中OOM Killer的决策基准及执行流程
linux·运维
NocoBase3 小时前
GitHub 上 Star 数量前 18 的开源 AI Agent 项目
人工智能·开源·openai
TLucas3 小时前
在CentOS 7上将PostgreSQL数据库从默认路径迁移到自定义目录
linux·运维·postgresql·centos