当前位置: 首页 > news >正文

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, %axmovw    %ax, %ds        ; 数据段 = 代码段movw    %ax, %es        ; 附加段 = 代码段/* 打印启动信息:"setup is running [enhanced]" */movw    $setup_msg, %axmovw    %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         ; 行号3movb    $0, %dl         ; 列号0int     $0x10           ; 调用BIOS显示中断/* 阶段1:收集硬件信息并存储到INITSEG段 */call    collect_hardware_info/* 阶段2:准备保护模式环境 */call    prepare_protected_mode/* 阶段3:切换到保护模式并跳转 */call    enter_protected_modehang:jmp     hang            ; 异常情况下的死循环/* 硬件信息收集函数 */
collect_hardware_info:/* 1. 收集光标位置(BIOS中断0x10, 功能0x03) */movb    $0x03, %ahxorw    %bh, %bh        ; 页0int     $0x10movw    $INITSEG, %axmovw    %ax, %dsmovw    %dx, (0)        ; 存储光标位置到0x9000:0000/* 2. 收集扩展内存大小(BIOS中断0x15, 功能0x88) */movb    $0x88, %ahint     $0x15movw    %ax, (2)        ; 存储到0x9000:0002(KB)/* 3. 收集显示模式信息(BIOS中断0x10, 功能0x0F) */movb    $0x0F, %ahint     $0x10movw    %bx, (4)        ; 存储显示页/颜色模式到0x9000:0004movw    %ax, (6)        ; 存储列数到0x9000:0006/* 4. 收集硬盘参数(从BIOS数据区复制) */; 复制第一硬盘参数(0x41*4=0x104)movw    $0x0000, %axmovw    %ax, %dsldsw    (0x104), %si    ; SI = 硬盘参数表地址movw    $INITSEG, %axmovw    %ax, %esmovw    $0x0080, %di    ; 目标地址:0x9000:0080movw    $0x10, %cx      ; 复制16字节repmovsb; 复制第二硬盘参数(0x46*4=0x118)movw    $0x0000, %axmovw    %ax, %dsldsw    (0x118), %simovw    $INITSEG, %axmovw    %ax, %esmovw    $0x0090, %di    ; 目标地址:0x9000:0090movw    $0x10, %cxrepmovsbret/* 保护模式环境准备函数 */
prepare_protected_mode:cli                     ; 关闭中断(避免切换期间中断干扰)/* 移动内核代码到0x0000:0000(假设内核在0x1000段) */movw    $0x1000, %ax    ; 源段起始地址
do_move:movw    %ax, %es        ; ES = 目标段(初始为0x1000)addw    $0x1000, %ax    ; 源段 = 目标段 + 0x1000cmpw    $0x9000, %ax    ; 移动到0x9000段为止jz      end_movemovw    %ax, %ds        ; DS = 源段xorw    %si, %si        ; SI=0(源偏移)xorw    %di, %di        ; DI=0(目标偏移)movw    $0x8000, %cx    ; 复制0x8000字(64KB)repmovsw                    ; 批量移动jmp     do_move
end_move:/* 配置8042键盘控制器(允许保护模式下键盘中断) */call    empty_8042      ; 等待控制器空闲movb    $0xD1, %al      ; 命令:写输出端口outb    %al, $0x64call    empty_8042movb    $0xDF, %al      ; 允许A20地址线outb    %al, $0x60call    empty_8042/* 配置8259中断控制器(屏蔽所有中断,后续由内核启用) */movb    $0x11, %al      ; 初始化命令字ICW1outb    %al, $0x20      ; 主8259outb    %al, $0xA0      ; 从8259.word   0x00eb, 0x00eb  ; 短延迟(替代nop)movb    $0x20, %al      ; 主8259向量基地址0x20outb    %al, $0x21.word   0x00eb, 0x00ebmovb    $0x04, %al      ; 主8259级联标识outb    %al, $0x21.word   0x00eb, 0x00ebmovb    $0x01, %al      ; 8086模式outb    %al, $0x21.word   0x00eb, 0x00eboutb    %al, $0xA1      ; 从8259配置(同上).word   0x00eb, 0x00ebmovb    $0xFF, %al      ; 屏蔽所有中断outb    %al, $0x21outb    %al, $0xA1ret/* 进入保护模式函数 */
enter_protected_mode:/* 加载GDT(全局描述符表) */movw    $SETUPSEG, %axmovw    %ax, %dslgdt    gdt_48          ; 加载GDT描述符/* 设置CR0寄存器PE位(保护模式使能) */movl    %cr0, %eaxorl     $0x00000001, %eax  ; 第0位=1movl    %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   0x00gdt_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 binaryall: linux.imglinux.img: build bootsect setup./build > linux.imgbootsect: 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.cgcc -o $@ $<clean:rm -f *.o bootsect setup build linux.imgrun: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 指令(兼容部分硬件)

总结

http://www.dtcms.com/a/331401.html

相关文章:

  • 嵌入式LINUX——————网络2
  • 晶台光耦在工业控制领域的应用
  • 集成koa2+ts+typeorm记录
  • 14 ABP Framework 文档管理
  • java开发,匹配指定内容设置不同字体颜色
  • 嵌入式C/C++面试大全
  • 传统自然语言处理任务入口
  • css预编译器实现星空背景图
  • XJar 加密 jar 包
  • Vscode的wsl环境开发ESP32S3的一些问题总结
  • 《贵州棒球百科》体育赛事排名·棒球1号位
  • 建造者模式C++
  • 串口通信中,实现串口接收函数时,避免数据丢失或被覆盖的方法
  • 20250814在荣品RD-RK3588开发板的Android13下解决卡迪的LCD屏在开机的时候brightness最暗【背光的pwm信号的极性反了】
  • 机器学习核心概念与实践笔记
  • 安卓设备通过USB,连接继电器,再通过继电器开关闸机
  • 前端包管理工具
  • 【FreeRTOS】任务管理:创建与删除任务,任务优先级与阻塞
  • 计算机网络---传输控制协议Transmission Control Protocol(TCP)
  • Redis的 ​​散列(Hash)​​ 和 ​​列表(List)​​ 数据结构操作详解
  • 力扣-64.最小路径和
  • 【AI推理部署教程】使用 vLLM 运行智谱 GLM-4.5V 视觉语言模型推理服务
  • 电商双 11 美妆数据分析总结(补充)
  • 入门概述(面试常问)
  • 中久数创——笔试题
  • Android构建工具版本兼容性对照表
  • Git 中切换到指定 tag
  • 会议系统核心流程详解:创建、加入与消息交互
  • 卫星通信链路预算之七:上行载噪比计算
  • MySQL-dble分库分表方案