mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3628 字
10 分钟
启动镜像:系统集成与自检
2021-12-03

在之前的章节中,我们逐步构建了操作系统的各个核心组件:从基础的 MBR 引导、保护模式设置、虚拟内存管理,到中断系统、内存管理、任务调度、同步机制、用户空间、设备驱动、文件系统,以及 Shell 和用户工具。本章将把所有这些组件整合成一个完整的、可启动的操作系统镜像。

经过 18 章的开发,我们有了引导程序、内核、调度器、文件系统、Shell……但它们还散落在各个目录中。本章要把所有组件集成为一个可启动的完整系统:自定义引导链(MBR→Loader→Kernel)、VHD 磁盘镜像打包、子系统初始化顺序、系统自检——这是整个系列的“毕业典礼”。

启动镜像概述#

集成所有组件的必要性#

在操作系统开发过程中,我们通常采用模块化的方式开发各个子系统。每个章节专注于实现特定的功能,如内存管理、任务调度、文件系统等。然而,这些分散的模块最终需要被整合成一个统一的系统,才能形成完整可用的操作系统。

启动镜像(Boot Image)的作用包括:

  1. 统一启动流程:将引导加载程序、内核、文件系统等所有必需组件打包到一个镜像文件中,确保系统能够从零开始完整启动
  2. 便于分发和测试:单个镜像文件可以方便地在不同环境中运行,便于测试和演示
  3. 系统集成验证:创建启动镜像的过程本身就是对系统集成的一次全面验证,可以发现各模块之间的接口问题
  4. 版本管理:每个镜像对应一个特定的系统版本,便于版本管理和发布

传统操作系统 vs 教学操作系统#

商业操作系统(如 Linux、Windows)通常使用复杂的引导加载程序(如 GRUB、LILO)和成熟的文件系统(如 ext4、NTFS)。我们的教学操作系统采用了更简洁但同样有效的设计:

  • 自定义引导链:MBR → Loader → 内核,而不是依赖 GRUB
  • 内存文件系统:SimpleFS 运行在内存中,简单高效
  • VHD 磁盘镜像:虚拟硬盘格式,便于 QEMU 直接启动
  • 集成自检:系统启动时自动进行功能验证

这种设计既保持了教育价值,又确保了系统的完整性和可靠性。

启动镜像的核心组成#

自定义引导链#

我们为什么不使用 GRUB 这样的成熟引导加载程序?原因有几个:

  1. 学习目的:自定义引导链能让你深入理解计算机启动的底层机制
  2. 完全控制:不依赖外部工具,从 MBR 到内核的每一步代码都是自己编写的
  3. 简化流程:避免了 GRUB 的复杂性,更适合教学环境
  4. 独立性:整个系统完全自包含,不依赖第三方软件

我们的引导链由三个阶段组成:

graph TD A[BIOS 启动] -->|加载扇区0| B[MBR<br/>512字节] B -->|跳转到0x7C00| C[MBR代码执行] C -->|读取扇区1-8| D[Loader<br/>8KB] D -->|跳转到0x8000| E[Loader代码执行] E -->|读取扇区9+| F[Kernel<br/>ELF格式] F -->|解析ELF| G[加载到内存] G -->|跳转到入口点| H[Kernel main函数]

MBR(Master Boot Record):位于磁盘第一个扇区(扇区0),大小为 512 字节。BIOS 启动后会将 MBR 加载到内存地址 0x7C00 并执行。MBR 的主要任务是:

  • 检查引导签名(0x55AA)
  • 从磁盘读取 Loader 程序
  • 将 Loader 加载到内存并跳转执行

Loader:位于扇区 1-8(共 8 个扇区,约 4KB)。Loader 完成更复杂的任务:

  • 进入保护模式
  • 设置 GDT(全局描述符表)
  • 启用分页机制
  • 加载 ELF 格式的内核
  • 跳转到内核入口点

Kernel:从扇区 9 开始存储,包含完整的操作系统内核。内核以 ELF 格式存储,Loader 负责解析 ELF 并加载到正确的内存位置。

VHD 磁盘镜像#

VHD(Virtual Hard Disk)是虚拟磁盘的通用格式,具有以下优势:

  1. 标准格式:被 QEMU、VirtualBox 等主流虚拟机支持
  2. 固定大小:可以预先分配固定大小的磁盘空间
  3. 便于管理:单个文件包含完整的磁盘内容
  4. 可移植性:方便在不同机器之间传输

VHD 镜像的布局:

VHD 镜像 (disk.vhd)
┌─────────────────────────┐ 偏移 0x0000
│ VHD Header │ 512 字节(VHD 格式头)
├─────────────────────────┤ 偏移 0x0200
│ MBR (扇区 0) │ 512 字节
├─────────────────────────┤ 偏移 0x0400
│ Loader (扇区 1-8) │ 4096 字节
├─────────────────────────┤ 偏移 0x1400
│ Kernel (扇区 9+) │ 内核 ELF 镜像
├─────────────────────────┤
│ (文件系统数据) │ SimpleFS 数据区
└─────────────────────────┘

构建过程:

  1. 编译 MBR、Loader、Kernel,生成二进制文件
  2. 创建空的 VHD 镜像文件
  3. 将 MBR 写入偏移 0x0200
  4. 将 Loader 写入偏移 0x0400
  5. 将 Kernel 写入偏移 0x1400
  6. (可选)写入文件系统数据

系统集成与初始化#

操作系统从引导加载程序接管控制权后,需要按正确顺序初始化各个子系统:

  1. 依赖关系:某些子系统依赖于其他子系统(如任务调度依赖定时器)
  2. 资源分配:需要在早期初始化阶段分配关键资源(如内核堆)
  3. 错误处理:初始化失败时需要能够报告错误
  4. 性能优化:某些操作在启动阶段一次性完成,避免运行时开销

完整的初始化流程:

flowchart TD A[Kernel 入口] --> B[VGA 显示初始化] B --> C[GDT 全局描述符表] C --> D[TSS 任务状态段] D --> E[IDT 中断描述符表] E --> F[启用中断] F --> G[定时器初始化] G --> H[Boot 信息解析] H --> I[PMM 物理内存管理] I --> J[VMM 虚拟内存管理] J --> K[KHeap 内核堆] K --> L[设备框架初始化] L --> M[键盘驱动] M --> N[ATA 硬盘驱动] N --> O[文件系统初始化] O --> P[SimpleFS 挂载] P --> Q[任务子系统] Q --> R[进程子系统] R --> S[系统调用初始化] S --> T[系统自检] T --> U[创建文件系统内容] U --> V[启动 Shell 任务] V --> W[进入调度器]

每个初始化步骤都有其特定的职责:

  • 早期初始化:GDT、IDT、TSS 必须首先完成,它们为后续操作提供基础
  • 内存管理:PMM 和 VMM 必须在需要动态内存分配之前初始化
  • 设备驱动:硬件驱动需要在需要硬件功能之前初始化
  • 文件系统:文件系统必须在用户进程使用文件之前挂载
  • 任务和进程:这是系统启动的最后阶段,因为它们依赖于前面的所有子系统

系统自检(Self-Test)#

系统自检在启动阶段自动验证关键功能是否正常工作:

  1. 快速诊断:在用户交互前发现明显的问题
  2. 教学价值:展示如何编写测试代码
  3. 增强信心:让开发者知道系统启动成功
  4. 文档作用:自检输出可以作为系统功能的简要说明

我们的自检包括三个基本测试:

static int self_test(void)
{
int pass = 0;
// 测试1:内存分配
void *p = kmalloc(256);
if (p) { pass++; kfree(p); }
vga_printf(" [%s] Memory allocation\n", p ? "PASS" : "FAIL");
// 测试2:中断启用
uint32_t fl; __asm__ volatile("pushfl; popl %0":"=r"(fl));
if (fl & 0x200) pass++;
vga_printf(" [%s] Interrupts\n", (fl & 0x200) ? "PASS" : "FAIL");
// 测试3:定时器工作
uint32_t t1 = getTick();
for (volatile int i = 0; i < 50000; i++);
if (getTick() >= t1) pass++;
vga_printf(" [%s] Timer\n", getTick() >= t1 ? "PASS" : "FAIL");
return pass;
}
  • 内存分配测试:验证内核堆能够正常分配和释放内存
  • 中断测试:验证中断标志位(IF)已启用,意味着中断系统工作正常
  • 定时器测试:验证定时器中断在增加 tick 计数

自检输出示例:

Self-test:
[PASS] Memory allocation
[PASS] Interrupts
[PASS] Timer
Result: 3/3 passed

代码实现#

文件结构#

19.boot-image/
├── boot/
│ ├── mbr.S # MBR 引导代码
│ └── loader.S # Loader 引导代码
├── kernel/
│ ├── arch/x86/
│ │ ├── context_switch.S # 上下文切换汇编
│ │ ├── gdt.S # GDT 设置
│ │ ├── tss.c # TSS 管理
│ │ └── usermode.S # 用户模式切换
│ ├── drivers/
│ │ ├── ata.c # ATA 硬盘驱动
│ │ ├── device.c # 设备框架
│ │ ├── keyboard.c # 键盘驱动
│ │ └── vga.c # VGA 显示驱动
│ ├── fs/
│ │ ├── disk_fs.c # 磁盘文件系统接口
│ │ ├── fs.c # 虚拟文件系统
│ │ ├── pipe.c # 管道实现
│ │ └── simple_fs.c # SimpleFS 实现
│ ├── interrupt/
│ │ ├── interrupt.S # 中断处理汇编
│ │ ├── interrupt.c # 中断处理
│ │ ├── syscall.c # 系统调用
│ │ └── timer.c # 定时器
│ ├── lib/
│ │ ├── bitmap.c # 位图操作
│ │ ├── hash_table.c # 哈希表
│ │ ├── id_pool.c # ID 分配池
│ │ ├── linked_list.c # 链表
│ │ ├── math.c # 数学函数
│ │ ├── ordered_array.c # 有序数组
│ │ ├── rand.c # 随机数
│ │ └── string.c # 字符串操作
│ ├── mem/
│ │ ├── boot_info.c # 启动信息解析
│ │ ├── gdt.c # GDT 管理
│ │ ├── kheap.c # 内核堆
│ │ ├── pmm.c # 物理内存管理
│ │ └── vmm.c # 虚拟内存管理
│ ├── sync/
│ │ ├── lock.S # 锁原语汇编
│ │ ├── mutex.c # 互斥锁
│ │ ├── semaphore.c # 信号量
│ │ ├── spinlock.c # 自旋锁
│ │ └── yieldlock.c # 让出锁
│ └── task/
│ ├── exec.c # exec 系统调用
│ ├── fork.c # fork 系统调用
│ ├── process.c # 进程管理
│ ├── runqueue.c # 运行队列
│ ├── scheduler.c # 调度器
│ ├── signal.c # 信号处理
│ ├── task.c # 任务管理
│ └── tss.c # TSS 管理
├── kernel.c # 内核主函数(含 Shell)
├── kernel.ld # 内核链接脚本
└── Makefile # 构建配置

关键初始化流程#

内核主函数 main() 完成所有初始化:

void main()
{
vga_clear();
vga_printf(" ===================================\n");
vga_printf(" | MyOS - Teaching OS v1.0 |\n");
vga_printf(" | Chapter 19: Final Boot Image |\n");
vga_printf(" ===================================\n\n");
vga_printf("Initializing kernel subsystems...\n");
// 硬件基础设施
vga_printf(" GDT..."); gdt_init(); vga_printf("OK\n");
vga_printf(" TSS..."); tss_init(); vga_printf("OK\n");
vga_printf(" IDT..."); idt_init(); vga_printf("OK\n");
vga_printf(" Interrupts..."); enable_interrupts(); vga_printf("OK\n");
vga_printf(" Timer..."); init_timer(TIMER_FREQUENCY); vga_printf("OK\n");
// 内存管理
vga_printf(" Memory...");
boot_info_init();
pmm_init(&boot_info);
vmm_init();
init_kheap();
vga_printf("OK\n");
// 设备驱动
vga_printf(" Devices...");
device_framework_init();
keyboard_init();
ata_init();
vga_printf("OK\n");
// 文件系统
vga_printf(" Filesystem...");
fs_init();
simple_fs_init();
vga_printf("OK\n");
// 任务和进程管理
vga_printf(" Tasks...");
task_subsystem_init();
process_subsystem_init();
syscall_init();
vga_printf("OK\n");
// 系统自检
vga_printf("\nSelf-test:\n");
int pass = self_test();
vga_printf(" Result: %d/3 passed\n", pass);
// 预创建文件系统内容
fs_mkdir("/home", FS_PERM_READ | FS_PERM_WRITE);
fs_mkdir("/bin", FS_PERM_READ | FS_PERM_EXEC);
fs_mkdir("/tmp", FS_PERM_READ | FS_PERM_WRITE);
int fd = fs_open("/readme.txt", FS_OPEN_WRITE | FS_OPEN_CREATE);
if (fd >= 0) {
const char *txt = "Welcome to MyOS!\nA complete teaching operating system.\n\nFeatures:\n- Preemptive multitasking\n- Virtual memory\n- File system\n- Device drivers\n- System calls\n- Interactive shell\n";
fs_write(fd, txt, strlen(txt));
fs_close(fd);
}
// 启动 Shell
vga_printf("\nStarting shell...\n");
task_create_kernel("shell", shell_task, NULL, 5);
// 进入调度器,永不返回
schedule();
while (1);
}

MBR 引导代码(boot/mbr.S)#

; MBR 位于磁盘第一个扇区,512 字节
; BIOS 加载到 0x7C00 并执行
[BITS 16]
[ORG 0x7C00]
start:
cli ; 关闭中断
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00 ; 设置栈指针
; 读取 Loader(扇区 1-8)
mov ah, 0x02 ; BIOS 读取磁盘功能
mov al, 8 ; 读取 8 个扇区
mov ch, 0 ; 柱面 0
mov cl, 2 ; 从扇区 2 开始(扇区 1 = cl=1, ch=0)
mov dh, 0 ; 磁头 0
mov dl, 0x80 ; 第一个硬盘
mov bx, 0x8000 ; 加载到 0x8000
mov es, bx
mov bx, 0x0000
int 0x13 ; BIOS 磁盘中断
jc disk_error ; 如果出错则跳转
; 跳转到 Loader
jmp 0x8000:0x0000
disk_error:
mov si, error_msg
call print_string
hlt
jmp $
error_msg db 'Disk read error!', 0
print_string:
lodsb
or al, al
jz print_done
mov ah, 0x0E
mov bh, 0x00
int 0x10
jmp print_string
print_done:
ret
; 填充剩余空间到 510 字节
times 510-($-$$) db 0
; 引导签名
dw 0xAA55

解析:MBR 是系统启动的第一步。它非常简单,只做三件事:

  1. 设置段寄存器和栈指针
  2. 调用 BIOS 中断读取 Loader(8 个扇区)
  3. 跳转到 Loader 执行

MBR 的限制是只有 512 字节,所以必须简洁。实际的复杂初始化由 Loader 完成。

Loader 引导代码(boot/loader.S)#

; Loader 加载到 0x8000,约 8KB
; 负责进入保护模式、加载内核
[BITS 16]
[ORG 0x8000]
start:
cli
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x8000
; 加载 GDT
lgdt [gdt_desc]
; 启用保护模式(设置 CR0 的 PE 位)
mov eax, cr0
or eax, 1
mov cr0, eax
; 远跳转到 32 位代码
jmp CODE_SEG:init_pm
[BITS 32]
init_pm:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
; 设置页目录和页表
mov eax, page_dir
mov cr3, eax
; 启用分页(设置 CR0 的 PG 位)
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
; 读取内核(从扇区 9 开始)
; 假设内核已加载到正确位置
; 这里简化处理,实际需要读取并解析 ELF
; 跳转到内核入口
jmp CODE_SEG:0x100000 ; 假设内核加载到 1MB
; GDT 定义
gdt:
dq 0 ; 空描述符
dd 0x0000FFFF ; 代码段
dd 0x00CF9A00
dd 0x0000FFFF ; 数据段
dd 0x00CF9200
gdt_desc:
dw gdt_desc - gdt - 1
dd gdt
CODE_SEG equ 0x08
DATA_SEG equ 0x10
; 页目录(简单映射)
page_dir:
dd 0x103 ; 第一个页表地址 + 可读写/用户/存在
times 1023 dd 0
; 页表(映射前 4MB)
page_table:
dd 0x00000083 ; 0x00000000 - 0x003FFFFF
dd 0x00400083 ; 0x00400000 - 0x007FFFFF
dd 0x00800083 ; 0x00800000 - 0x00BFFFFF
dd 0x00C00083 ; 0x00C00000 - 0x00FFFFFF
times 4092 dd 0

解析:Loader 的职责比 MBR 复杂得多:

  1. 加载 GDT:全局描述符表定义了内存段的属性,是保护模式的基础
  2. 启用保护模式:设置 CR0 寄存器的 PE(Protection Enable)位
  3. 设置分页:创建页目录和页表,启用虚拟内存
  4. 加载内核:读取内核 ELF 文件并解析(示例中简化了)
  5. 跳转执行:跳转到内核入口点,进入 32 位保护模式

Loader 运行在物理地址空间,但设置了简单的页表映射,使得 0-4MB 的虚拟地址映射到相同的物理地址。

Shell 命令解析#

static int parse_cmd(char *line, char *argv[])
{
int argc = 0;
char *p = line;
while (*p && argc < MAX_ARGS - 1) {
while (*p == ' ') p++; // 跳过空格
if (!*p) break;
argv[argc++] = p; // 记录参数起始位置
while (*p && *p != ' ') p++; // 找到参数结束
if (*p) *p++ = '\0'; // 用空字符替换空格
}
argv[argc] = NULL; // 参数数组以 NULL 结尾
return argc;
}
static void shell_cmd(char *line)
{
char *argv[MAX_ARGS];
int argc = parse_cmd(line, argv);
if (argc == 0) return;
// 命令分发
if (strcmp(argv[0], "help") == 0) {
vga_printf("Commands: help clear echo ls cat mkdir touch write pwd cd\n");
vga_printf(" ps mem uptime version pipe signal diskls exit\n");
} else if (strcmp(argv[0], "ls") == 0) {
const char *p = argc > 1 ? argv[1] : cwd;
int d = fs_opendir(p);
if (d < 0) {
vga_printf("ls: cannot open '%s'\n", p);
return;
}
dirent_t *de;
while ((de = fs_readdir(d))) {
vga_printf(" %s%s\n", de->name,
de->type == FILE_TYPE_DIRECTORY ? "/" : "");
}
fs_closedir(d);
} else if (strcmp(argv[0], "cat") == 0) {
if (argc < 2) {
vga_printf("Usage: cat <file>\n");
return;
}
char path[128];
if (argv[1][0] == '/') {
strncpy(path, argv[1], 127);
} else {
sprintf(path, "%s/%s",
strcmp(cwd, "/") == 0 ? "" : cwd,
argv[1]);
}
int f = fs_open(path, FS_OPEN_READ);
if (f < 0) {
vga_printf("cat: not found\n");
return;
}
char b[128];
ssize_t n;
while ((n = fs_read(f, b, 127)) > 0) {
b[n] = 0;
vga_printf("%s", b);
}
fs_close(f);
}
// ... 更多命令 ...
}

解析:Shell 实现了基本的命令行界面:

  1. 命令解析parse_cmd() 函数将输入行分解为参数数组
  2. 命令分发:使用 strcmp 匹配命令名并调用相应的处理函数
  3. 文件操作:通过 VFS 接口调用文件系统功能
  4. 路径处理:支持绝对路径和相对路径

Shell 是一个独立运行的任务,通过键盘输入获取命令,然后解析并执行。这展示了用户态进程如何与内核交互。

构建系统(Makefile)#

CHAPTER := 19.boot-image
MODULES := mbr loader kernel
# MBR 模块
mbr_SRC := boot/mbr.S
mbr_OFFSET := 0
# Loader 模块
loader_SRC := boot/loader.S
loader_OFFSET := 1
# Kernel 模块
kernel_SRC := kernel
kernel_LDFILE := kernel/link.ld
kernel_OFFSET := 9
# 包含通用 Makefile
include ../Makefile.inc

解析:Makefile 定义了三个模块:

  1. mbr:从 boot/mbr.S 编译,写入扇区 0
  2. loader:从 boot/loader.S 编译,写入扇区 1
  3. kernel:从 kernel/ 目录编译,写入扇区 9 及之后

../Makefile.inc 提供了通用的构建逻辑,包括:

  • 编译汇编和 C 源文件
  • 链接内核
  • 创建 VHD 镜像
  • 将各个模块写入正确的扇区偏移

构建命令:

cd 19.boot-image
make all # 构建所有模块并创建镜像
make run # 在 QEMU 中运行
make clean # 清理构建产物

系统启动流程图#

sequenceDiagram participant BIOS participant MBR participant Loader participant Kernel participant Shell BIOS->>MBR: 加载扇区 0 到 0x7C00 BIOS->>MBR: 跳转执行 MBR->>MBR: 设置段寄存器和栈 MBR->>MBR: 读取扇区 1-8 MBR->>Loader: 跳转到 0x8000 Loader->>Loader: 加载 GDT Loader->>Loader: 启用保护模式 Loader->>Loader: 设置页表 Loader->>Loader: 启用分页 Loader->>Loader: 读取内核 Loader->>Kernel: 跳转到内核入口 Kernel->>Kernel: 初始化 GDT/TSS/IDT Kernel->>Kernel: 启用中断 Kernel->>Kernel: 初始化定时器 Kernel->>Kernel: 初始化内存管理 Kernel->>Kernel: 初始化设备驱动 Kernel->>Kernel: 初始化文件系统 Kernel->>Kernel: 初始化任务和进程 Kernel->>Kernel: 执行系统自检 Kernel->>Kernel: 预创建文件系统内容 Kernel->>Shell: 创建 Shell 任务 Kernel->>Kernel: 启动调度器 Shell->>Shell: 显示提示符 Shell->>Shell: 等待键盘输入 Shell->>Shell: 解析命令 Shell->>Kernel: 执行系统调用

运行与验证#

编译运行#

# 进入章节目录
cd 19.boot-image
# 清理旧构建
make clean
# 构建镜像
make all
# 在 QEMU 中运行
make run
# 或者带 VNC 显示运行
make run-vnc
# 或者带调试支持运行
make run-vnc.debug

预期输出#

启动时会看到以下输出:

===================================
| MyOS - Teaching OS v1.0 |
| Chapter 19: Final Boot Image |
===================================
Initializing kernel subsystems...
GDT...OK
TSS...OK
IDT...OK
Interrupts...OK
Timer...OK
Memory...OK
Devices...OK
Filesystem...OK
Tasks...OK
Self-test:
[PASS] Memory allocation
[PASS] Interrupts
[PASS] Timer
Result: 3/3 passed
Starting shell...
MyOS Shell v1.0 - Type 'help' for commands.
/> help
Commands: help clear echo ls cat mkdir touch write pwd cd
ps mem uptime version pipe signal diskls exit
/> ls
home/
bin/
tmp/
readme.txt
/> cat readme.txt
Welcome to MyOS!
A complete teaching operating system.
Features:
- Preemptive multitasking
- Virtual memory
- File system
- Device drivers
- System calls
- Interactive shell
/> pwd
/
/> mkdir /home/user
/> cd /home/user
/home/user> touch test.txt
/home/user> write test.txt Hello MyOS!
/home/user> cat test.txt
Hello MyOS!
/home/user> ps
TID NAME STATE
3 shell RUNNING
/home/user> uptime
Uptime: 0 seconds
/home/user> version
MyOS v1.0 - Complete Teaching OS
/home/user> exit
System halted.

功能验证清单#

启动后,可以验证以下功能:

  1. 引导流程:系统从 MBR 成功启动,经过 Loader 到内核
  2. 内存管理mem 命令显示 E820 内存映射信息
  3. 中断和定时器uptime 命令显示系统运行时间,证明定时器工作正常
  4. 文件系统lscatmkdirtouchwrite 等命令证明文件系统工作正常
  5. 任务调度ps 命令显示当前任务状态
  6. Shell:可以交互式执行命令
  7. 键盘驱动:可以正常输入命令
  8. 自检功能:所有自检测试通过

踩坑记录#

问题1:系统启动后没有输出#

可能原因

  • VGA 显示驱动初始化失败
  • 中断未正确启用,导致后续初始化卡住
  • 内核加载位置错误

解决方案

  1. 检查 vga_init() 是否在初始化序列的最前面
  2. 确认 enable_interrupts()idt_init() 之后调用
  3. 检查内核链接脚本 kernel.ld 中的入口地址是否正确
  4. 使用 make run-vnc.debug 启动 QEMU 并连接 GDB 调试

问题2:键盘输入无响应#

可能原因

  • 键盘驱动未初始化
  • 中断未启用
  • 键盘缓冲区处理有问题

解决方案

  1. 确认 keyboard_init() 已被调用
  2. 检查 keyboard_has_char()keyboard_getchar() 函数实现
  3. 验证键盘中断处理程序是否正确注册到 IDT
  4. 添加调试输出,检查键盘扫描码是否被接收到

问题3:文件系统命令失败#

可能原因

  • SimpleFS 未正确初始化
  • 文件系统未挂载
  • VFS 接口调用错误

解决方案

  1. 确认 simple_fs_init() 已被调用
  2. 检查文件系统初始化时的错误返回值
  3. 验证文件操作(fs_openfs_readfs_write)的返回值
  4. 添加调试输出,跟踪文件系统调用链

问题4:构建失败或镜像不完整#

可能原因

  • 模块偏移配置错误
  • 链接脚本问题
  • 编译器或链接器版本不兼容

解决方案

  1. 检查 Makefile 中的 *_OFFSET 配置是否正确
  2. 验证 kernel/link.ld 中的内存布局
  3. 确认使用正确的交叉编译工具链(i386-elf-gcc
  4. 使用 make view 查看磁盘镜像的十六进制内容

小结#

从 MBR 到 Loader 到 Kernel 的自定义引导链完全控制了启动流程,VHD 磁盘镜像将所有组件打包为可分发的虚拟磁盘,子系统按正确顺序初始化(GDT→IDT→PMM→VMM→调度器→文件系统→Shell)确保了依赖关系,系统自检验证了每个子系统正常工作。

至此,从第 01 章的系统加电到第 19 章的完整启动镜像,一个教学操作系统从零到一的旅程结束了。这个系统虽然简化,但涵盖了操作系统的核心概念:引导、内存管理、调度、同步、特权级隔离、文件系统、进程管理和用户交互。 4. 系统自检:自动验证关键功能 5. 集成 Shell:提供交互式用户界面

完整功能列表#

恭喜你!现在你拥有一个完整的操作系统,包含:

  • 引导系统:自定义 MBR + Loader 引导链
  • 内存管理:物理内存管理(PMM)、虚拟内存管理(VMM)、内核堆(KHeap)
  • 中断系统:IDT、定时器、键盘中断
  • 任务调度:抢占式多任务、Round-Robin 调度
  • 同步机制:自旋锁、互斥锁、信号量、让出锁
  • 进程管理:fork、exec、进程树
  • 用户空间:TSS、Ring 3 切换、系统调用
  • 设备驱动:VGA、键盘、ATA 硬盘
  • 文件系统:VFS、SimpleFS、管道
  • 交互式 Shell:命令解析、文件操作、内置命令

后续扩展方向#

虽然本教程已经结束,但操作系统开发是一个永无止境的领域。你可以继续探索:

  1. 网络支持:实现 TCP/IP 协议栈、网络驱动(以太网)
  2. 图形界面:开发 GUI 窗口系统、图形驱动(VESA)
  3. 多核支持:实现 SMP(对称多处理)、多核调度
  4. 安全特性:添加用户权限、文件权限、内存保护
  5. 驱动框架:支持 PCI 设备、USB 设备、音频设备
  6. 高级文件系统:支持 ext4、FAT32 等成熟文件系统
  7. 应用程序:开发更多用户工具(编辑器、编译器等)

学习成果#

通过这 19 章的学习,你已经:

  • 理解了计算机启动的完整流程
  • 掌握了操作系统的核心原理
  • 实现了从零开始的操作系统开发
  • 体验了系统集成的复杂性
  • 获得了深入底层编程的经验

这些知识不仅对操作系统开发有用,对理解计算机系统的各个方面都有很大帮助。

参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

启动镜像:系统集成与自检
https://blog.souloss.com/posts/os/boot-image-integration/
作者
Souloss
发布于
2021-12-03
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时