操作系统学习13 保护模式下加载运行C程序

操作系统学习13 保护模式下加载运行C程序

  • 一、本章说明
    • [1. 前置知识](#1. 前置知识)
    • [2. 本章目标](#2. 本章目标)
    • [3. 目录结构](#3. 目录结构)
    • [4. 准备工具](#4. 准备工具)
    • [5. 内存布局](#5. 内存布局)
    • [6. 与 lesson11 / lesson12 的差异](#6. 与 lesson11 / lesson12 的差异)
  • [二、平坦模式 GDT](#二、平坦模式 GDT)
  • 三、引导程序流程
    • [1. 实模式阶段(与 lesson11 相同)](#1. 实模式阶段(与 lesson11 相同))
    • [2. 切换保护模式(与 lesson12 相同)](#2. 切换保护模式(与 lesson12 相同))
    • [3. 32 位入口](#3. 32 位入口)
  • [四、32 位内核与 utilities](#四、32 位内核与 utilities)
    • [1. kernel.c](#1. kernel.c)
    • [2. 显存输出:直接写与调 BIOS 的区别](#2. 显存输出:直接写与调 BIOS 的区别)
    • [3. utilities.s --- 显存输出与软件光标](#3. utilities.s — 显存输出与软件光标)
    • [4. 8042 端口轮询原理](#4. 8042 端口轮询原理)
      • [IDT 与 PIC 是什么](#IDT 与 PIC 是什么)
    • [5. utilities.s --- 键盘轮询实现](#5. utilities.s — 键盘轮询实现)
    • [6. 编译选项变化](#6. 编译选项变化)
  • [五、Makefile 与写盘](#五、Makefile 与写盘)
  • 六、编译与运行
  • 七、本章小结

一、本章说明

1. 前置知识

请先阅读:

本章将两者合并:在实模式下读盘加载内核,再切换到 32 位保护模式,运行自己的 C 程序

2. 本章目标

完成本章后,我们将能够:

  • 在引导扇区内完成 读盘 + 进保护模式 的完整流程
  • 使用 平坦模式 GDT(代码/数据段均映射 0~4G),简化 32 位 C 内核寻址
  • 32 位 GCC-m32 -ffreestanding)编译内核,链接地址仍为 0xC200
  • 在保护模式下 直接写 VGA 显存0xB8000),不再调用 BIOS INT 10h
  • 通过 PS/2 键盘端口轮询init_keyboard + read_char)实现简易控制台回显
  • 软件光标_)替代 VGA 硬件光标,避免光标位置错乱

3. 目录结构

复制代码
lesson13/
├── Makefile
├── boot/
│   ├── loader.s        # BPB + 读盘 + 进保护模式 + 跳转内核
│   └── pm.inc          # 段描述符宏(含 DA_4K 平坦段)
├── kernel/
│   ├── kernel.c        # 32 位 C 入口:欢迎语 + 提示符 + 键盘回显
│   ├── utilities.s     # 32 位显存输出、软件光标与键盘轮询
│   └── utilities.h
└── README.md

编译产物输出到 ../target/loader.binkernel.binos.img)。

4. 准备工具

工具 用途
nasm 汇编引导程序与 utilities
i686-elf-gcc 编译 32 位 C 内核(-m32 -ffreestanding
i686-elf-ld 链接 flat binary
dd 写入 os.img
make 一键编译、安装、运行
Bochs 运行与调试

工具已放在开源仓库tools/env/ 目录。

5. 内存布局

位置 物理地址 说明
引导扇区 0x7C00 BIOS 加载入口,loader.bin
读盘缓冲区 0x8200 INT 13h 读入起点(与 lesson11 相同)
内核入口 0xC200 kernel.bin 链接地址,保护模式下 JMP 目标
内核栈 ESP = 0x90000 进保护模式后由引导程序设置
VGA 显存 0xB8000 文本模式,每字符 2 字节 [ASCII][属性]
GDT 引导扇区内 平坦代码段 + 平坦数据段

#mermaid-svg-PQ4T3bdZWrH3v8Su{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PQ4T3bdZWrH3v8Su .error-icon{fill:#552222;}#mermaid-svg-PQ4T3bdZWrH3v8Su .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PQ4T3bdZWrH3v8Su .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .marker.cross{stroke:#333333;}#mermaid-svg-PQ4T3bdZWrH3v8Su svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PQ4T3bdZWrH3v8Su p{margin:0;}#mermaid-svg-PQ4T3bdZWrH3v8Su .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .cluster-label text{fill:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .cluster-label span{color:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .cluster-label span p{background-color:transparent;}#mermaid-svg-PQ4T3bdZWrH3v8Su .label text,#mermaid-svg-PQ4T3bdZWrH3v8Su span{fill:#333;color:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .node rect,#mermaid-svg-PQ4T3bdZWrH3v8Su .node circle,#mermaid-svg-PQ4T3bdZWrH3v8Su .node ellipse,#mermaid-svg-PQ4T3bdZWrH3v8Su .node polygon,#mermaid-svg-PQ4T3bdZWrH3v8Su .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .rough-node .label text,#mermaid-svg-PQ4T3bdZWrH3v8Su .node .label text,#mermaid-svg-PQ4T3bdZWrH3v8Su .image-shape .label,#mermaid-svg-PQ4T3bdZWrH3v8Su .icon-shape .label{text-anchor:middle;}#mermaid-svg-PQ4T3bdZWrH3v8Su .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .rough-node .label,#mermaid-svg-PQ4T3bdZWrH3v8Su .node .label,#mermaid-svg-PQ4T3bdZWrH3v8Su .image-shape .label,#mermaid-svg-PQ4T3bdZWrH3v8Su .icon-shape .label{text-align:center;}#mermaid-svg-PQ4T3bdZWrH3v8Su .node.clickable{cursor:pointer;}#mermaid-svg-PQ4T3bdZWrH3v8Su .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .arrowheadPath{fill:#333333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PQ4T3bdZWrH3v8Su .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PQ4T3bdZWrH3v8Su .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PQ4T3bdZWrH3v8Su .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PQ4T3bdZWrH3v8Su .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .cluster text{fill:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su .cluster span{color:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PQ4T3bdZWrH3v8Su .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PQ4T3bdZWrH3v8Su rect.text{fill:none;stroke-width:0;}#mermaid-svg-PQ4T3bdZWrH3v8Su .icon-shape,#mermaid-svg-PQ4T3bdZWrH3v8Su .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PQ4T3bdZWrH3v8Su .icon-shape p,#mermaid-svg-PQ4T3bdZWrH3v8Su .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PQ4T3bdZWrH3v8Su .icon-shape .label rect,#mermaid-svg-PQ4T3bdZWrH3v8Su .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PQ4T3bdZWrH3v8Su .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PQ4T3bdZWrH3v8Su .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PQ4T3bdZWrH3v8Su :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} INT 13h 读盘
loader.s 实模式
内核映像在 0xC200
lgdt / CR0.PE / 远跳转
32位: 设 DS/SS/ESP
JMP 0xC200
kernel.c: clear_screen → init_keyboard
print_str 欢迎语 + > 提示符
read_char → print_char 循环

6. 与 lesson11 / lesson12 的差异

项目 lesson11 lesson12 lesson13
运行模式 16 位实模式 32 位保护模式 先实模式读盘,再进保护模式
内核编译 -m16 无 C 内核 -m32 -ffreestanding
显示 BIOS INT 10h 直接写显存 直接写显存
键盘 BIOS INT 16h init_keyboard + 端口 0x60/0x64 轮询
GDT 代码段 + 显存段 平坦代码段 + 平坦数据段
跳转内核 JMP 0xC200(实模式) JMP 0xC200(保护模式)

关键变化: 进入保护模式后,不能再调用 BIOS 中断 (BIOS 只在实模式有效)。C 内核依赖的 clear_screeninit_keyboardprint_charread_char 全部改为裸机实现。本章键盘采用 端口轮询 ,未使用 IDT / PIC / 键盘中断(IRQ1) (概念见 第四节)。


二、平坦模式 GDT

lesson12 为显存单独建了一个基址 0xB8000 的数据段描述符,并用 GS 访问。本章采用更常见的 平坦模型(Flat Model)

什么是平坦模型(之前章节已经介绍,这里再复习下)

保护模式下,CPU 不再用「段值 × 16 + 偏移」算物理地址,而是:

线性地址 = 段基址(来自 GDT 描述符)+ 32 位偏移

每个段寄存器(CSDS 等)存的是 选择子 ,指向 GDT 里的一项 段描述符。描述符里规定了该段的基址、界限和属性(可读/可写/可执行、16 位还是 32 位等)。

平坦模型 的做法是:让代码段、数据段 共享同一套「从 0 开始、覆盖整个 4G 线性空间」的映射------

描述符 基址 界限 G 位 实际覆盖范围
代码段 0 0xFFFFF 1(4K 粒度) 0 ~ 4G−1
数据段 0 0xFFFFF 1 0 ~ 4G−1

界限字段存的是 页帧数 − 10xFFFFF 配合 G=1 表示 (0xFFFFF + 1) × 4K = 4G。于是任意 32 位偏移 offset 都在段内合法,线性地址就等于 offset
#mermaid-svg-gxKLe0Xg03gUy3Ss{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gxKLe0Xg03gUy3Ss .error-icon{fill:#552222;}#mermaid-svg-gxKLe0Xg03gUy3Ss .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gxKLe0Xg03gUy3Ss .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .marker.cross{stroke:#333333;}#mermaid-svg-gxKLe0Xg03gUy3Ss svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gxKLe0Xg03gUy3Ss p{margin:0;}#mermaid-svg-gxKLe0Xg03gUy3Ss .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .cluster-label text{fill:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .cluster-label span{color:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .cluster-label span p{background-color:transparent;}#mermaid-svg-gxKLe0Xg03gUy3Ss .label text,#mermaid-svg-gxKLe0Xg03gUy3Ss span{fill:#333;color:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .node rect,#mermaid-svg-gxKLe0Xg03gUy3Ss .node circle,#mermaid-svg-gxKLe0Xg03gUy3Ss .node ellipse,#mermaid-svg-gxKLe0Xg03gUy3Ss .node polygon,#mermaid-svg-gxKLe0Xg03gUy3Ss .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .rough-node .label text,#mermaid-svg-gxKLe0Xg03gUy3Ss .node .label text,#mermaid-svg-gxKLe0Xg03gUy3Ss .image-shape .label,#mermaid-svg-gxKLe0Xg03gUy3Ss .icon-shape .label{text-anchor:middle;}#mermaid-svg-gxKLe0Xg03gUy3Ss .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .rough-node .label,#mermaid-svg-gxKLe0Xg03gUy3Ss .node .label,#mermaid-svg-gxKLe0Xg03gUy3Ss .image-shape .label,#mermaid-svg-gxKLe0Xg03gUy3Ss .icon-shape .label{text-align:center;}#mermaid-svg-gxKLe0Xg03gUy3Ss .node.clickable{cursor:pointer;}#mermaid-svg-gxKLe0Xg03gUy3Ss .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .arrowheadPath{fill:#333333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gxKLe0Xg03gUy3Ss .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gxKLe0Xg03gUy3Ss .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gxKLe0Xg03gUy3Ss .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gxKLe0Xg03gUy3Ss .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .cluster text{fill:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss .cluster span{color:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gxKLe0Xg03gUy3Ss .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gxKLe0Xg03gUy3Ss rect.text{fill:none;stroke-width:0;}#mermaid-svg-gxKLe0Xg03gUy3Ss .icon-shape,#mermaid-svg-gxKLe0Xg03gUy3Ss .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gxKLe0Xg03gUy3Ss .icon-shape p,#mermaid-svg-gxKLe0Xg03gUy3Ss .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gxKLe0Xg03gUy3Ss .icon-shape .label rect,#mermaid-svg-gxKLe0Xg03gUy3Ss .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gxKLe0Xg03gUy3Ss .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gxKLe0Xg03gUy3Ss .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gxKLe0Xg03gUy3Ss :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lesson12 分段方式
GS 基址 0xB8000
GS:0 → 0xB8000
CS 基址 引导段
CS:偏移 → 代码
平坦模型
32 位偏移 0xB8000
线性地址 0xB8000
32 位偏移 0xC200
线性地址 0xC200

与 lesson12 的区别: lesson12 代码段基址随引导扇区动态填写,显存段基址固定 0xB8000,访问 VGA 要 MOV AX, [GS:EDI] 这类 段:偏移 写法。平坦模型DS 基址也是 0,可直接 MOV EAX, 0xB8000 / MOV [EAX], BL,C 编译出的指针也是裸 32 位地址,无需为显存单独设段。

与实模式的区别: 实模式段最大 64K,段与段可能重叠;平坦模型段上限 4G,代码/数据段重叠同一地址空间------这正是现代 32 位 OS 启动阶段的常见布局(分页开启前仍用平坦段,开启页表后再用虚拟地址)。

GDT 定义

nasm 复制代码
LABEL_GDT:
   Descriptor    0,       0,                  0
LABEL_DESC_CODE:
   Descriptor    0,       0xFFFFF,            DA_C + DA_32 + DA_4K
LABEL_DESC_DATA:
   Descriptor    0,       0xFFFFF,            DA_DRW + DA_32 + DA_4K
  • 基址 = 0界限 = 0xFFFFFG = 1DA_4K)→ 每段覆盖 4G 线性空间
  • 代码段选择子 SelectorCode = 8,数据段 SelectorData = 16
  • 线性地址 = 段基址 + 偏移,故 0xB80000xC200 均可直接用 32 位偏移访问
  • 进保护模式后:DS/ES/SS/FS/GS 均加载数据段选择子,CS 由远跳转加载代码段选择子

三、引导程序流程

1. 实模式阶段(与 lesson11 相同)

  1. 设置 SS:SP = 0:0x7C00
  2. 从软盘扇区 2 起循环读入,目标 ES:BX0x8200:0 递增
  3. 读盘成功后打印 Loading NotOneOS (32-bit PM)...
  4. 调用 enter_pm 而非直接 JMP 0xC200

2. 切换保护模式(与 lesson12 相同)

nasm 复制代码
enter_pm:
   MOV      AX, CS
   MOV      DS, AX
   ; 计算 GDT 物理地址填入 GdtPtr
   LGDT     [GdtPtr]
   CLI
   IN       AL, 92h          ; 开 A20
   OR       AL, 00000010b
   OUT      92h, AL
   MOV      EAX, CR0
   OR       EAX, 1           ; CR0.PE = 1
   MOV      CR0, EAX
   JMP      DWORD SelectorCode:pm_start

3. 32 位入口

nasm 复制代码
[BITS 32]
pm_start:
   MOV      AX, SelectorData
   MOV      DS, AX
   MOV      ES, AX
   MOV      SS, AX
   MOV      FS, AX
   MOV      GS, AX
   MOV      ESP, 0x90000
   JMP      0xC200           ; 跳转到 C 内核

注意: 平坦模式下 CS 基址为 0,JMP 0xC200 即跳到线性地址 0xC200 的内核入口。


四、32 位内核与 utilities

1. kernel.c

与 lesson11 类似,但运行于保护模式,且需先初始化键盘:

c 复制代码
void main(void)
{
    clear_screen();
    init_keyboard();
    char *str = "NotOneOS 32-bit Protected Mode.\r\nWelcome..\r\n";
    int len = strlen(str);
    print_str(str, len);
    print_str("> ", 2);

    char a;
    fin:
        a = read_char();
        if (a != 0)
            print_char(a);
        goto fin;
}

utilities.h 导出:clear_screeninit_keyboardread_charprint_charprint_str

2. 显存输出:直接写与调 BIOS 的区别

lesson11 在 16 位实模式 下通过 INT 10h 输出字符,例如 AH=0x0E(电传/Teletype)逐字显示、AH=0x00 AL=0x03 清屏。BIOS 视频服务替你维护光标、处理 \r/\n、必要时滚屏,并更新 VGA 硬件光标寄存器。

进入 32 位保护模式 后,这条路径不可用:BIOS 中断服务例程按 实模式 编写,依赖实模式段寄存器与中断环境;在保护模式下执行 INT 10h 时,CPU 会按 IDT (中断描述符表)取门描述符,而本章尚未建立可用的 IDT,轻则无效、重则 triple fault(IDT 说明见 第四节)。

因此本章改为 直接写 VGA 文本模式显存 0xB8000

对比项 lesson11:INT 10h lesson13:直接写 0xB8000
运行模式 16 位实模式 32 位保护模式
输出方式 INT 10h,BIOS 代劳 MOV [0xB8000+偏移], 字符/属性
显存布局 由 BIOS 抽象,调用者不必关心 80×25,每格 2 字节:[ASCII][属性]
光标 BIOS 内部维护 自行维护 cursor_x / cursor_y
回车换行 AH=0x0E 可自动处理(lesson11 还在 \r 后再发 \n print_char 自行解析 0x0D / 0x0A
滚屏 BIOS 自动 本章简化为到达末行后停在最后一行
硬件光标 BIOS 同步更新 关闭硬件光标,用 _ 软件光标

偏移计算: 字符 (x, y) 在显存中的字节偏移为 (y × 80 + x) × 2。属性字节常用 0x07(灰字黑底),与 lesson11 中 BX=15 的 INT 10h 配色接近。
#mermaid-svg-9TbxW6RS3vm0rC31{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9TbxW6RS3vm0rC31 .error-icon{fill:#552222;}#mermaid-svg-9TbxW6RS3vm0rC31 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9TbxW6RS3vm0rC31 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9TbxW6RS3vm0rC31 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9TbxW6RS3vm0rC31 .marker.cross{stroke:#333333;}#mermaid-svg-9TbxW6RS3vm0rC31 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9TbxW6RS3vm0rC31 p{margin:0;}#mermaid-svg-9TbxW6RS3vm0rC31 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 .cluster-label text{fill:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 .cluster-label span{color:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 .cluster-label span p{background-color:transparent;}#mermaid-svg-9TbxW6RS3vm0rC31 .label text,#mermaid-svg-9TbxW6RS3vm0rC31 span{fill:#333;color:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 .node rect,#mermaid-svg-9TbxW6RS3vm0rC31 .node circle,#mermaid-svg-9TbxW6RS3vm0rC31 .node ellipse,#mermaid-svg-9TbxW6RS3vm0rC31 .node polygon,#mermaid-svg-9TbxW6RS3vm0rC31 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9TbxW6RS3vm0rC31 .rough-node .label text,#mermaid-svg-9TbxW6RS3vm0rC31 .node .label text,#mermaid-svg-9TbxW6RS3vm0rC31 .image-shape .label,#mermaid-svg-9TbxW6RS3vm0rC31 .icon-shape .label{text-anchor:middle;}#mermaid-svg-9TbxW6RS3vm0rC31 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9TbxW6RS3vm0rC31 .rough-node .label,#mermaid-svg-9TbxW6RS3vm0rC31 .node .label,#mermaid-svg-9TbxW6RS3vm0rC31 .image-shape .label,#mermaid-svg-9TbxW6RS3vm0rC31 .icon-shape .label{text-align:center;}#mermaid-svg-9TbxW6RS3vm0rC31 .node.clickable{cursor:pointer;}#mermaid-svg-9TbxW6RS3vm0rC31 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9TbxW6RS3vm0rC31 .arrowheadPath{fill:#333333;}#mermaid-svg-9TbxW6RS3vm0rC31 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9TbxW6RS3vm0rC31 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9TbxW6RS3vm0rC31 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9TbxW6RS3vm0rC31 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9TbxW6RS3vm0rC31 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9TbxW6RS3vm0rC31 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9TbxW6RS3vm0rC31 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9TbxW6RS3vm0rC31 .cluster text{fill:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 .cluster span{color:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9TbxW6RS3vm0rC31 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9TbxW6RS3vm0rC31 rect.text{fill:none;stroke-width:0;}#mermaid-svg-9TbxW6RS3vm0rC31 .icon-shape,#mermaid-svg-9TbxW6RS3vm0rC31 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9TbxW6RS3vm0rC31 .icon-shape p,#mermaid-svg-9TbxW6RS3vm0rC31 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9TbxW6RS3vm0rC31 .icon-shape .label rect,#mermaid-svg-9TbxW6RS3vm0rC31 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9TbxW6RS3vm0rC31 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9TbxW6RS3vm0rC31 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9TbxW6RS3vm0rC31 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} lesson13 保护模式
平坦段寻址
print_char
0xB8000 + 偏移
cursor_x/y + 软件光标
lesson11 实模式
INT 10h AH=0x0E
print_char
BIOS 视频服务
写 0xB8000 + 更新光标

本质区别: 调 BIOS 是「请求固件服务」;直写显存是「按硬件手册操作帧缓冲」。后者代码更多,但不依赖实模式,是保护模式 / 内核自绘控制台的基础。

3. utilities.s --- 显存输出与软件光标

不再调用 INT 10h,直接写 0xB8000。用 cursor_x / cursor_y 维护逻辑光标,关闭 VGA 硬件光标disable_hw_cursor),在当前位置绘制 _ 作为软件光标(cursor_draw / cursor_restore)。

nasm 复制代码
VIDEO_MEM equ 0xB8000

clear_screen:
    ; 整屏填 0x0720,重置 cursor_x/y
    CALL disable_hw_cursor
    CALL cursor_draw

print_char:
    ; 偏移 = (y * 80 + x) * 2
    ; 字符先存 BL,再 MOV [EAX], BL(避免用 EAX 算址后覆盖 AL)
    ; 0x0D:列归零(行不变);0x0A:列归零并换行

注意: 回车键扫描码 0x1C 映射为 ASCII 0x0Dprint_char 收到后只回到当前行 行首,不会自动换行;字符串里的 \r\n 仍由 print_str 逐字处理。

4. 8042 端口轮询原理

PC 的 8042 芯片 (或兼容逻辑)连接 PS/2 键盘。CPU 不直接读键盘,而是经 8042 的两个 I/O 端口交换数据:

端口 方向 作用
0x64 状态寄存器(是否有数据可读、输入缓冲是否满等)
0x64 命令寄存器 (如 0xAE 启用键盘端口)
0x60 读入 扫描码(或控制器应答)
0x60 向键盘发送 命令 (如 0xF4 开启扫描)

状态寄存器 0x64 常用位:

名称 含义
0 OBF Output Buffer Full:为 1 表示 0x60 有数据可读
1 IBF Input Buffer Full:为 1 表示勿向 0x64/0x60
3 AUX 为 1 表示数据来自鼠标/aux,读键盘时应丢弃

按键到字符的数据路径:
CPU 轮询 8042 控制器 PS/2 键盘 CPU 轮询 8042 控制器 PS/2 键盘 #mermaid-svg-tEgVK9fMMxetmhU1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tEgVK9fMMxetmhU1 .error-icon{fill:#552222;}#mermaid-svg-tEgVK9fMMxetmhU1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tEgVK9fMMxetmhU1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tEgVK9fMMxetmhU1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tEgVK9fMMxetmhU1 .marker.cross{stroke:#333333;}#mermaid-svg-tEgVK9fMMxetmhU1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tEgVK9fMMxetmhU1 p{margin:0;}#mermaid-svg-tEgVK9fMMxetmhU1 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEgVK9fMMxetmhU1 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tEgVK9fMMxetmhU1 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tEgVK9fMMxetmhU1 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tEgVK9fMMxetmhU1 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tEgVK9fMMxetmhU1 .sequenceNumber{fill:white;}#mermaid-svg-tEgVK9fMMxetmhU1 #sequencenumber{fill:#333;}#mermaid-svg-tEgVK9fMMxetmhU1 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tEgVK9fMMxetmhU1 .messageText{fill:#333;stroke:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEgVK9fMMxetmhU1 .labelText,#mermaid-svg-tEgVK9fMMxetmhU1 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .loopText,#mermaid-svg-tEgVK9fMMxetmhU1 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tEgVK9fMMxetmhU1 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tEgVK9fMMxetmhU1 .noteText,#mermaid-svg-tEgVK9fMMxetmhU1 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tEgVK9fMMxetmhU1 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEgVK9fMMxetmhU1 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEgVK9fMMxetmhU1 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tEgVK9fMMxetmhU1 .actorPopupMenu{position:absolute;}#mermaid-svg-tEgVK9fMMxetmhU1 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tEgVK9fMMxetmhU1 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tEgVK9fMMxetmhU1 .actor-man circle,#mermaid-svg-tEgVK9fMMxetmhU1 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tEgVK9fMMxetmhU1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop read_kbd_scancode 按下/释放,发送扫描码 置 OBF=1 IN 0x64 读状态 OBF=1 时 IN 0x60 取扫描码 scancode_map 查 ASCII

Set 1 扫描码约定: 按下码 bit7=0 (make code),释放码 bit7=1 (break code,通常 = 按下码 | 0x80)。本章 read_char 先等按下、再等释放,避免一次按键被重复处理。

与 lesson11 INT 16h 的对比:

对比项 INT 16h(BIOS) 8042 端口轮询(本章)
可用模式 实模式 保护模式可用
读键方式 BIOS 阻塞等待 循环读 0x64/0x60
返回值 已转换的 ASCII(含 Shift 等) 原始扫描码 → 自行查 scancode_map
CPU 占用 等待时 BIOS 内部轮询/中断 当前线程空转在 .wait 循环
复杂度 调用简单 init_keyboard、查表、处理修饰键

初始化两步: OUT 0x64, 0xAE 打开键盘端口;OUT 0x60, 0xF4 让键盘开始扫描。写命令前须 wait_8042(等 IBF=0),写后 drain_kbd 清空残留字节,避免误读旧数据。

IDT 与 PIC 是什么

本章用 轮询 读键盘,没有建 IDT 、也没有配置 PIC 。二者是保护模式下处理 硬件中断 的基础设施,与 lesson12 已学过的 GDT 地位类似------GDT 管「段从哪访问」,IDT 管「中断/异常来了跳去哪」。

IDT(Interrupt Descriptor Table,中断描述符表)

  • 保护模式下,CPU 遇到 异常 (除零、页故障等)或 中断 (硬件 IRQ、INT n 指令)时,用 中断向量号 (0~255)作为下标,到 IDT 里取一项 门描述符
  • 门描述符里写着处理程序的 代码段选择子 + 偏移 ;CPU 自动切换去执行该 中断处理程序(ISR)
  • LIDT 指令加载 IDTR(类似 LGDT 加载 GDT)。没有合法 IDT 时,任何中断/异常都无法正确分发,通常导致 triple fault。
  • 向量 0~31 保留给 CPU 异常;32 起常留给外部设备(具体编号由 PIC 或 APIC 决定)。

PIC(Programmable Interrupt Controller,可编程中断控制器)

  • PC 上外设(键盘、时钟、磁盘等)不能直接「打断」CPU,而是向 8259A PIC (或后来的 APIC)发 IRQ 请求线
  • PIC 把多条 IRQ 汇总 成一条信号送给 CPU;CPU 响应后,PIC 再告知是 哪一号 IRQ
  • 经典布局:键盘占 IRQ1 。BIOS 在实模式下已把 IRQ 映射到某个中断向量;OS 进入保护模式后通常要做 PIC 重映射(例如把 IRQ0~15 改到向量 32~47),避免与 CPU 异常号冲突。
  • 还需 屏蔽/开放 某条 IRQ(OUT 到 PIC 的 0x21/0xA1 等端口),并用 STI 允许 CPU 响应中断。

键盘中断 vs 本章轮询

方式 数据路径 本章
轮询 CPU 空转循环读 0x64,有数据再读 0x60 ✅ 采用
IRQ1 中断 按键 → 8042 → PIC → CPU 跳 ISR → ISR 读 0x60 ❌ 未实现

#mermaid-svg-a76QXStHqB1mZZ4g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-a76QXStHqB1mZZ4g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-a76QXStHqB1mZZ4g .error-icon{fill:#552222;}#mermaid-svg-a76QXStHqB1mZZ4g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-a76QXStHqB1mZZ4g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-a76QXStHqB1mZZ4g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-a76QXStHqB1mZZ4g .marker.cross{stroke:#333333;}#mermaid-svg-a76QXStHqB1mZZ4g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-a76QXStHqB1mZZ4g p{margin:0;}#mermaid-svg-a76QXStHqB1mZZ4g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-a76QXStHqB1mZZ4g .cluster-label text{fill:#333;}#mermaid-svg-a76QXStHqB1mZZ4g .cluster-label span{color:#333;}#mermaid-svg-a76QXStHqB1mZZ4g .cluster-label span p{background-color:transparent;}#mermaid-svg-a76QXStHqB1mZZ4g .label text,#mermaid-svg-a76QXStHqB1mZZ4g span{fill:#333;color:#333;}#mermaid-svg-a76QXStHqB1mZZ4g .node rect,#mermaid-svg-a76QXStHqB1mZZ4g .node circle,#mermaid-svg-a76QXStHqB1mZZ4g .node ellipse,#mermaid-svg-a76QXStHqB1mZZ4g .node polygon,#mermaid-svg-a76QXStHqB1mZZ4g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-a76QXStHqB1mZZ4g .rough-node .label text,#mermaid-svg-a76QXStHqB1mZZ4g .node .label text,#mermaid-svg-a76QXStHqB1mZZ4g .image-shape .label,#mermaid-svg-a76QXStHqB1mZZ4g .icon-shape .label{text-anchor:middle;}#mermaid-svg-a76QXStHqB1mZZ4g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-a76QXStHqB1mZZ4g .rough-node .label,#mermaid-svg-a76QXStHqB1mZZ4g .node .label,#mermaid-svg-a76QXStHqB1mZZ4g .image-shape .label,#mermaid-svg-a76QXStHqB1mZZ4g .icon-shape .label{text-align:center;}#mermaid-svg-a76QXStHqB1mZZ4g .node.clickable{cursor:pointer;}#mermaid-svg-a76QXStHqB1mZZ4g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-a76QXStHqB1mZZ4g .arrowheadPath{fill:#333333;}#mermaid-svg-a76QXStHqB1mZZ4g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-a76QXStHqB1mZZ4g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-a76QXStHqB1mZZ4g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a76QXStHqB1mZZ4g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-a76QXStHqB1mZZ4g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a76QXStHqB1mZZ4g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-a76QXStHqB1mZZ4g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-a76QXStHqB1mZZ4g .cluster text{fill:#333;}#mermaid-svg-a76QXStHqB1mZZ4g .cluster span{color:#333;}#mermaid-svg-a76QXStHqB1mZZ4g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-a76QXStHqB1mZZ4g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-a76QXStHqB1mZZ4g rect.text{fill:none;stroke-width:0;}#mermaid-svg-a76QXStHqB1mZZ4g .icon-shape,#mermaid-svg-a76QXStHqB1mZZ4g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-a76QXStHqB1mZZ4g .icon-shape p,#mermaid-svg-a76QXStHqB1mZZ4g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-a76QXStHqB1mZZ4g .icon-shape .label rect,#mermaid-svg-a76QXStHqB1mZZ4g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-a76QXStHqB1mZZ4g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-a76QXStHqB1mZZ4g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-a76QXStHqB1mZZ4g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 中断方式 下一章
按键
8042
PIC IRQ1
CPU 查 IDT
键盘中断处理程序
本章:轮询
IN 0x64


read_char 循环
OBF=1?
IN 0x60 取扫描码

中断方案需 LIDT、PIC 重映射、编写 ISR、开 STI,概念与代码量都更大。轮询虽浪费 CPU,但逻辑直观,足够完成回显演示;下一章可改为 IRQ 驱动,并顺带支持 Shift 大写等。

5. utilities.s --- 键盘轮询实现

保护模式下无法使用 INT 16h。本章不建 IDT,也不开 STI,改为轮询 8042 控制器端口 0x64 / 0x60

初始化init_keyboard):

nasm 复制代码
init_keyboard:
    ; wait_8042 → OUT 0x64, 0xAE   ; 启用键盘端口
    ; drain_kbd 清空缓冲
    ; OUT 0x60, 0xF4(经 kbd_cmd) ; 开启扫描
    ; drain_kbd

读键read_char):

nasm 复制代码
read_char:
.mk_wait:
    CALL read_kbd_scancode      ; 等按下(bit7=0)
    TEST AL, 0x80
    JNZ  .mk_wait
    MOV  BL, AL
.br_wait:
    CALL read_kbd_scancode      ; 等释放(bit7=1)
    TEST AL, 0x80
    JZ   .br_wait
    ; 0x1C → 0x0D;其余查 scancode_map(128 字节 Set 1 表)

read_kbd_scancode0x64 bit0 就绪时读 0x60;若 bit3 表示鼠标数据则丢弃重读。

scancode_map 为 128 字节的 Set 1 查找表,按下码作下标查 ASCII。仅覆盖主键盘可打印字符;不支持 Shift 大写

修饰键须映射为 0。 左 Shift(0x2A)、右 Shift(0x36)等若仍对应可打印字符,按 Shift 时会误回显------例如出现 \。应把这些条目写成 0read_char 返回 0 时 kernel.c 不回显。

表项必须逐字节对齐。 反斜杠应写 DB 0x5C,不能写 DB '\\':NASM 会把 '\\' 汇编成 2 字节 ,从该行起整表偏移 1,典型现象是按 b 却显示 v。写完后可对照 Set 1 扫描码表抽查几键。

6. 编译选项变化

makefile 复制代码
# lesson11(实模式)
CCFLAGS = -march=i386 -m16 -ffreestanding

# lesson13(保护模式 32 位)
CCFLAGS = -m32 -ffreestanding -fno-pic -fno-builtin -nostdlib -O2

链接地址与 lesson11 相同:

makefile 复制代码
kernel.bin: kernel.o utilities.o
	$(LD) -m elf_i386 -Ttext 0xC200 --oformat binary \
	      -o $(TARGET)$@ $(TARGET)kernel.o $(TARGET)utilities.o

五、Makefile 与写盘

makefile 复制代码
loader.o: boot/loader.s boot/pm.inc
	$(NASM) -I./ -f elf32 -o $(TARGET)loader.o boot/loader.s

loader.bin: loader.o
	$(LD) -m elf_i386 -Ttext 0x7c00 --oformat binary \
	      -o $(TARGET)loader.bin $(TARGET)loader.o

install:
	$(DD) if=$(TARGET)loader.bin of=$(TARGET)os.img conv=notrunc
	$(DD) if=$(TARGET)kernel.bin of=$(TARGET)os.img conv=notrunc bs=512 seek=33

seek=33 与 lesson11 一致:内核写入数据区起始扇区(0-based 扇区 33)。


六、编译与运行

lesson13/ 目录下:

bash 复制代码
make          # 生成 loader.bin、kernel.bin
make install  # 写入 ../target/os.img
make run      # 启动 Bochs
make debug    # Bochs 调试模式

验证清单:

  • 屏幕短暂显示 Loading NotOneOS (32-bit PM)...
  • 清屏后输出 NotOneOS 32-bit Protected Mode.Welcome..> 提示符
  • 光标显示为 _,随输入移动
  • 按键可回显字母、数字;回车回到当前行行首
  • 无 triple fault(Bochs 不重启)

运行注意: Bochs 启动后需 点击显示窗口 使其获得键盘焦点,否则按键无反应。

调试可在内核入口设断点(地址以链接结果为准):

bash 复制代码
make debug
复制代码
b 0xC200
c

单步跟踪 clear_screen0xB8000 的写入,或在 read_char 处设断(查看 ../target/kernel.map 中符号地址)。


七、本章小结

  • lesson11 解决了「加载并运行 C 程序」;lesson12 解决了「进入保护模式」;本章把两条路径串成一条完整启动链
  • 保护模式下 BIOS 不可用,显示与输入必须自己实现(显存、8042 端口轮询)
  • 平坦 GDT 让 C 代码可以像访问物理地址一样使用 0xC2000xB8000 等线性地址
  • 键盘采用 轮询 + Set 1 扫描码表 ,软件光标保证回显位置正确;内核从 16 位实模式编译 升级为 32 位保护模式编译

下一章可在此基础上增加:IDT + PIC 键盘中断 (替代轮询)、Shift 大写、或开启分页(页表)。本章刻意保持简单,中断机制见 [第四节「IDT 与 PIC」](#第四节「IDT 与 PIC」)。

源码地址:

https://gitee.com/xundh/learn-os