操作系统学习13 保护模式下加载运行C程序
- 一、本章说明
-
- [1. 前置知识](#1. 前置知识)
- [2. 本章目标](#2. 本章目标)
- [3. 目录结构](#3. 目录结构)
- [4. 准备工具](#4. 准备工具)
- [5. 内存布局](#5. 内存布局)
- [6. 与 lesson11 / lesson12 的差异](#6. 与 lesson11 / lesson12 的差异)
- [二、平坦模式 GDT](#二、平坦模式 GDT)
-
- 什么是平坦模型(之前章节已经介绍,这里再复习下)
- [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. 前置知识
请先阅读:
- lesson11 --- x86-C 调用汇编程序与传参:引导程序读盘、加载内核、C 与汇编互调
- lesson12 --- 进入保护模式:GDT、GDTR、
CR0.PE、32 位远跳转
本章将两者合并:在实模式下读盘加载内核,再切换到 32 位保护模式,运行自己的 C 程序。
2. 本章目标
完成本章后,我们将能够:
- 在引导扇区内完成 读盘 + 进保护模式 的完整流程
- 使用 平坦模式 GDT(代码/数据段均映射 0~4G),简化 32 位 C 内核寻址
- 用 32 位 GCC (
-m32 -ffreestanding)编译内核,链接地址仍为0xC200 - 在保护模式下 直接写 VGA 显存 (
0xB8000),不再调用 BIOSINT 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.bin、kernel.bin、os.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_screen、init_keyboard、print_char、read_char 全部改为裸机实现。本章键盘采用 端口轮询 ,未使用 IDT / PIC / 键盘中断(IRQ1) (概念见 第四节)。
二、平坦模式 GDT
lesson12 为显存单独建了一个基址 0xB8000 的数据段描述符,并用 GS 访问。本章采用更常见的 平坦模型(Flat Model)。
什么是平坦模型(之前章节已经介绍,这里再复习下)
保护模式下,CPU 不再用「段值 × 16 + 偏移」算物理地址,而是:
线性地址 = 段基址(来自 GDT 描述符)+ 32 位偏移
每个段寄存器(CS、DS 等)存的是 选择子 ,指向 GDT 里的一项 段描述符。描述符里规定了该段的基址、界限和属性(可读/可写/可执行、16 位还是 32 位等)。
平坦模型 的做法是:让代码段、数据段 共享同一套「从 0 开始、覆盖整个 4G 线性空间」的映射------
| 描述符 | 基址 | 界限 | G 位 | 实际覆盖范围 |
|---|---|---|---|---|
| 代码段 | 0 | 0xFFFFF | 1(4K 粒度) | 0 ~ 4G−1 |
| 数据段 | 0 | 0xFFFFF | 1 | 0 ~ 4G−1 |
界限字段存的是 页帧数 − 1 :0xFFFFF 配合 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 ,界限 = 0xFFFFF ,G = 1 (
DA_4K)→ 每段覆盖 4G 线性空间 - 代码段选择子
SelectorCode = 8,数据段SelectorData = 16 - 线性地址 = 段基址 + 偏移,故
0xB8000、0xC200均可直接用 32 位偏移访问 - 进保护模式后:
DS/ES/SS/FS/GS均加载数据段选择子,CS由远跳转加载代码段选择子
三、引导程序流程
1. 实模式阶段(与 lesson11 相同)
- 设置
SS:SP = 0:0x7C00 - 从软盘扇区 2 起循环读入,目标
ES:BX从0x8200:0递增 - 读盘成功后打印
Loading NotOneOS (32-bit PM)... - 调用
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_screen、init_keyboard、read_char、print_char、print_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 0x0D,print_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_scancode 在 0x64 bit0 就绪时读 0x60;若 bit3 表示鼠标数据则丢弃重读。
scancode_map 为 128 字节的 Set 1 查找表,按下码作下标查 ASCII。仅覆盖主键盘可打印字符;不支持 Shift 大写。
修饰键须映射为 0。 左 Shift(0x2A)、右 Shift(0x36)等若仍对应可打印字符,按 Shift 时会误回显------例如出现 \。应把这些条目写成 0,read_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_screen 对 0xB8000 的写入,或在 read_char 处设断(查看 ../target/kernel.map 中符号地址)。
七、本章小结
- lesson11 解决了「加载并运行 C 程序」;lesson12 解决了「进入保护模式」;本章把两条路径串成一条完整启动链
- 保护模式下 BIOS 不可用,显示与输入必须自己实现(显存、8042 端口轮询)
- 平坦 GDT 让 C 代码可以像访问物理地址一样使用
0xC200、0xB8000等线性地址 - 键盘采用 轮询 + Set 1 扫描码表 ,软件光标保证回显位置正确;内核从 16 位实模式编译 升级为 32 位保护模式编译
下一章可在此基础上增加:IDT + PIC 键盘中断 (替代轮询)、Shift 大写、或开启分页(页表)。本章刻意保持简单,中断机制见 [第四节「IDT 与 PIC」](#第四节「IDT 与 PIC」)。
源码地址: