在之前的章节中,我们逐步构建了操作系统的各个子系统:从基础的 MBR 引导、保护模式设置、虚拟内存管理,到中断系统、内存管理、任务调度、同步机制、用户空间、设备驱动、文件系统,以及 Shell 和用户工具。本章将把所有这些组件整合成一个完整的、可启动的操作系统镜像。
经过 18 章的开发,我们有了引导程序、内核、调度器、文件系统、Shell……但它们还散落在各个目录中。本章要把所有组件集成为一个可启动的完整系统:自定义引导链(MBR→Loader→Kernel)、VHD 磁盘镜像打包、子系统初始化顺序、系统自检,这是整个系列的 “毕业典礼”。
启动镜像概述
集成所有组件的必要性
在操作系统开发过程中,我们通常采用模块化的方式开发各个子系统。每个章节专注于实现特定的功能,如内存管理、任务调度、文件系统等。然而,这些分散的模块最终需要被整合成一个统一的系统,才能形成完整可用的操作系统。
启动镜像(Boot Image)的作用包括:
- 统一启动流程:将引导加载程序、内核、文件系统等所有必需组件打包到一个镜像文件中,确保系统能够从零开始完整启动
- 便于分发和测试:单个镜像文件可以方便地在不同环境中运行,便于测试和演示
- 系统集成验证:创建启动镜像的过程本身就是对系统集成的一次全面验证,可以发现各模块之间的接口问题
- 版本管理:每个镜像对应一个特定的系统版本,便于版本管理和发布
传统操作系统 vs 教学操作系统
商业操作系统(如 Linux、Windows)通常使用复杂的引导加载程序(如 GRUB、LILO)和成熟的文件系统(如 ext4、NTFS)。我们的教学操作系统采用了更简洁但同样有效的设计:
- 自定义引导链:MBR → Loader → 内核,而不是依赖 GRUB
- 内存文件系统:SimpleFS 运行在内存中,简单高效
- VHD 磁盘镜像:虚拟硬盘格式,便于 QEMU 直接启动
- 集成自检:系统启动时自动进行功能验证
这种设计既保持了教育价值,又确保了系统的完整性和可靠性。
启动镜像的核心组成
自定义引导链
我们为什么不使用 GRUB 这样的成熟引导加载程序?原因有几个:
- 学习目的:自定义引导链能让你深入理解计算机启动的底层机制
- 完全控制:不依赖外部工具,从 MBR 到内核的每一步代码都是自己编写的
- 简化流程:避免了 GRUB 的复杂性,更适合教学环境
- 独立性:整个系统完全自包含,不依赖第三方软件
我们的引导链由三个阶段组成:
MBR(Master Boot Record):位于磁盘第一个扇区(扇区 0),大小为 512 字节。BIOS 启动后会将 MBR 加载到内存地址 0x7C00 并执行。MBR 的主要任务是:
- 通过 E820 获取内存映射
- 从磁盘读取 Loader 程序
- 将 Loader 加载到内存并跳转执行
- 在扇区末尾写入 0x55AA 引导签名供 BIOS 检查
Loader:位于扇区 1-8(共 8 个扇区,4KB)。Loader 完成更复杂的任务:
- 进入保护模式
- 设置 GDT(全局描述符表)
- 启用分页机制
- 加载 ELF 格式的内核
- 跳转到内核入口点
Kernel:从扇区 9 开始存储,包含完整的操作系统内核。内核以 ELF 格式存储,Loader 负责解析 ELF 并加载到正确的内存位置。
VHD 磁盘镜像
VHD(Virtual Hard Disk)是虚拟磁盘的通用格式,具有以下优势:
- 标准格式:被 QEMU、VirtualBox 等主流虚拟机支持
- 固定大小:可以预先分配固定大小的磁盘空间
- 便于管理:单个文件包含完整的磁盘内容
- 可移植性:方便在不同机器之间传输
VHD 镜像的布局(按文件偏移,单位为字节):
| 区域 | 偏移 | 扇区 | 大小 | 说明 |
|---|---|---|---|---|
| MBR | 0x0000 | 0 | 512B | 引导扇区,BIOS 加载到 0x7C00 |
| Loader | 0x0200 | 1-8 | 4KB | 8 个扇区,加载到 0x8000 |
| Kernel | 0x1200 | 9+ | ELF 镜像 | 内核二进制,最高 1MB |
| VHD Footer | 文件末尾 | - | 512B | VPC 格式尾,标识镜像结束 |
VHD 文件由 qemu-img create -f vpc -o subformat=fixed 创建为固定大小镜像(注意必须显式指定 subformat=fixed,-f vpc 默认是动态磁盘,会把 footer 副本放在文件开头,dd 写 MBR 时会破坏它)。VPC 格式的标识 footer 在文件末尾而非开头,因此 MBR 直接从文件偏移 0 开始写入。
构建过程:
- 编译 MBR、Loader、Kernel,生成二进制文件
- 用
qemu-img create -f vpc创建固定大小的空 VHD 镜像 - 用
dd ... seek=0将 MBR 写入偏移 0x0000(扇区 0) - 用
dd ... seek=1将 Loader 写入偏移 0x0200(扇区 1) - 用
dd ... seek=9将 Kernel 写入偏移 0x1200(扇区 9) - (可选)写入文件系统数据
系统集成与初始化
操作系统从引导加载程序接管控制权后,需要按正确顺序初始化各个子系统:
- 依赖关系:某些子系统依赖于其他子系统(如任务调度依赖定时器)
- 资源分配:需要在早期初始化阶段分配关键资源(如内核堆)
- 错误处理:初始化失败时需要能够报告错误
- 性能优化:某些操作在启动阶段一次性完成,避免运行时开销
完整的初始化流程:
每个初始化步骤都有其特定的职责:
- 早期初始化:GDT、IDT、TSS 必须首先完成,它们为后续操作提供基础
- 内存管理:PMM 和 VMM 必须在需要动态内存分配之前初始化
- 设备驱动:硬件驱动需要在需要硬件功能之前初始化
- 文件系统:文件系统必须在用户进程使用文件之前挂载
- 任务和进程:这是系统启动的最后阶段,因为它们依赖于前面的所有子系统
系统自检(Self-Test)
系统自检在启动阶段自动验证关键功能是否正常工作:
- 快速诊断:在用户交互前发现明显的问题
- 教学价值:展示如何编写测试代码
- 增强信心:让开发者知道系统启动成功
- 文档作用:自检输出可以作为系统功能的简要说明
我们的自检包括三个基本测试:
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代码实现
文件结构
boot
kernel
arch/x86
drivers
fs
interrupt
lib
mem
sync
task
关键初始化流程
内核主函数 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) { if (schedule_needed()) { schedule_yield(); } __asm__ volatile("hlt"); }}MBR 引导代码(boot/mbr.S)
下面是 mbr.S 的真实代码节选(省略部分辅助函数)。MBR 运行在 16 位实模式,由 BIOS 加载到 0x7C00 执行。
; mbr.S:真实代码节选(NASM 语法,bits 16); 通过 %include 引入 bootmacros.inc 中的常量与宏
bits 16org 0x7c00
%include "../common/bootmacros.inc"
loader_base_addr equ 0x8000 ; loader 所处的段地址loader_sectors equ 8 ; 需要读取的扇区数E820_COUNT equ 0x8FF0 ; 存储E820条目数量的地址
; 初始化寄存器 mov ax, cs mov ds, ax mov ss, ax mov fs, ax mov ax, 0 mov es, ax mov sp, 0x7c00 ; 将 0x7c00 以下的区域作为栈 mov ax, 0xb800 mov gs, ax ; 显存段位置
call cls ; 清空屏幕 mov bx, hello_msg call print ; 输出启动信息
call get_memory_map ; 通过 E820 获取内存映射
; 读 loader(8 个扇区) mov dh, loader_sectors mov dl, 0x80 ; 第一个硬盘 mov bx, loader_base_addr call disk_load jmp 0:loader_base_addr ; 跳转至 loader(物理 0x8000)
hello_msg: db "HELLO, MBR!", 0
; --- disk_load 含 3 次重试机制(节选) ---disk_load: pusha push dx mov byte [.retry_count], 3.retry: mov ah, 0x02 ; int 0x13 功能:读扇区 mov al, dh ; 读取的扇区数 mov cl, 0x02 ; 从扇区 2 开始(扇区 1 是 MBR) mov ch, 0x00 ; 柱面 0 mov dh, 0x00 ; 磁头 0 int 0x13 jnc .disk_ok dec byte [.retry_count] ; 失败则重试 jz disk_error mov ah, 0x00 ; 重置磁盘驱动器后重试 int 0x13 jmp .retry.disk_ok: pop dx cmp al, dh ; 校验实际读取的扇区数 jne sectors_error popa ret.retry_count: db 0
; get_memory_map:通过 int 0x15, ax=0xE820 获取内存映射,存入 E820_BUFFER; (省略实现细节,最多 16 个条目)
; --- 扇区末尾的引导签名 --- times 510 - ($ - $$) db 0 db 0x55, 0xaa ; 写入引导签名,供 BIOS 检查解析:MBR 是系统启动的第一步。它的工作包括:
- 设置段寄存器和栈指针(栈位于 0x7C00 以下)
- 清屏并打印启动信息
- 调用
get_memory_map通过 E820 获取物理内存映射,供后续 PMM 使用 - 调用
disk_load读取 Loader(8 个扇区,含 3 次重试机制) - 用
jmp 0:loader_base_addr(即jmp 0:0x8000)跳转到 Loader - 在扇区末尾写入
0x55, 0xaa引导签名,BIOS 启动时会检查该签名是否存在
MBR 的限制是只有 512 字节,所以必须简洁。复杂的初始化交给 Loader 完成。
Loader 引导代码(boot/loader.S)
下面是 loader.S 的真实代码节选。Loader 加载到 0x8000,负责进入保护模式、启用分页(含内核高地址映射)、解析 ELF 加载内核,最终跳转到内核入口。
; loader.S:真实代码节选(NASM 语法); 通过 %include 引入 bootmacros.inc 中的 GDT 宏、分页常量、内核地址常量
[bits 16]org 0x8000
%include "../common/bootmacros.inc"
_start: jmp loader_start
; 用宏定义标准 GDT(null / 内核代码段 / 内核数据段 / 视频段)DEFINE_STANDARD_GDT
loader_start: cli xor ax, ax mov ds, ax mov es, ax mov fs, ax mov gs, ax SET_STACK_AT_MBR
call cls mov bx, MSG_REAL_MODE call print_bios
; 启用 A20 地址线(依次尝试 BIOS、键盘控制器、0x92 端口三种方法) ; ...(省略 A20 启用细节)
; 加载 GDT 并进入保护模式 lgdt [gdt_descriptor] mov eax, cr0 or eax, 1 ; 设置 PE 位 mov cr0, eax jmp SELECTOR_CODE32:init_pm ; 远跳刷新 CS
[bits 32]init_pm: mov ax, SELECTOR_DATA32 mov ds, ax mov es, ax mov fs, ax mov ss, ax mov ax, SELECTOR_VIDEO mov gs, ax SET_STACK_AT_PM ; 切换栈到 0x90000
call clear_screen ; 打印保护模式信息(省略)
call setup_page ; 建立页目录与页表,启用分页 ; 打印分页启用信息(省略)
call init_kernel ; 解析 ELF,加载内核段,返回入口地址于 eax
cli.hang: hlt jmp .hang
; --- setup_page:建立页目录与页表(节选) ---; 关键映射:; - 0xC0000000 的第一个 4MB:低 1MB 恒等映射 + 内核页表; - 0xC0400000 起:内核所有页表(254 个 PDE); - 0xC0800000 起:内核代码/数据物理起点 0x200000setup_page: ; 清零页目录(位于 0x101000) ; ... ; 第 0 个 PDE 与第 768 个 PDE 指向同一页表(低 1MB 恒等映射) mov eax, PAGE_DIR_PHYSICAL_ADDR - PAGE_SIZE or eax, PTE_USER | PTE_WRITE | PTE_PRESENT mov [PAGE_DIR_PHYSICAL_ADDR + 0], eax mov [PAGE_DIR_PHYSICAL_ADDR + 768 * 4], eax ; 第 769 个 PDE 指向内核页表区 ; 第 770-1023 共 254 个 PDE 指向内核后续页表 ; ... call enable_page ; 设置 CR3、置 CR0.PG、重新加载高地址 GDT ret
; --- init_kernel:解析 ELF 程序头,加载段,返回入口地址 ---init_kernel: call allocate_pages_for_kernel ; 为内核映像、内核段、内核栈建立映射 call load_hd_kernel_image ; 从磁盘读取内核映像到 KERNEL_BIN_LOAD_VIRTUAL_ADDR call do_load_kernel ; 遍历 PT_LOAD 段,memcpy 到 p_vaddr finit mov esp, KERNEL_STACK_TOP - 16 ; 内核栈顶 0xF0000000 mov ebp, esp jmp eax ; 跳转到 ELF 入口点 e_entry ret解析:Loader 的职责比 MBR 复杂得多:
- 加载 GDT:通过
DEFINE_STANDARD_GDT宏定义空描述符、内核代码段、内核数据段、视频段,是保护模式的基础 - 启用 A20 与保护模式:依次尝试三种方法启用 A20 地址线,然后设置 CR0 的 PE 位并远跳
- 启用分页:
setup_page建立页目录与页表,关键映射包括低 1MB 恒等映射、内核页表区(0xC0400000 起)、内核代码区(虚拟 0xC0800000 映射到物理 0x200000);enable_page置 CR0.PG 后重新加载高地址 GDT - 加载内核:
init_kernel先为内核映像、内核段、内核栈建立页表映射,再用load_hd_kernel_image从磁盘扇区 9 起读取内核映像,最后do_load_kernel遍历 ELF 程序头,将PT_LOAD段memcpy到各自的p_vaddr - 跳转执行:
do_load_kernel把 ELF 入口e_entry放入eax,init_kernel用jmp eax跳转到内核入口
内核链接在虚拟地址 0xC0800000(物理 0x200000),因此 Loader 必须先建立高地址映射,才能跳转到内核入口。
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 实现了基本的命令行界面:
- 命令解析:
parse_cmd()函数将输入行分解为参数数组 - 命令分发:使用
strcmp匹配命令名并调用相应的处理函数 - 文件操作:通过 VFS 接口调用文件系统功能
- 路径处理:支持绝对路径和相对路径
Shell 是一个内核任务,通过键盘输入获取命令,然后解析并执行。它直接调用内核文件系统 API,没有经过系统调用,这是教学简化。真正的 Shell 应作为用户态进程通过 int 0x80 与内核交互。
构建系统(Makefile)
CHAPTER := 19.boot-imageMODULES := mbr loader kernel
# MBR 模块mbr_SRC := boot/mbr.Smbr_OFFSET := 0
# Loader 模块loader_SRC := boot/loader.Sloader_OFFSET := 1
# Kernel 模块kernel_SRC := kernelkernel_LDFILE := kernel/link.ldkernel_OFFSET := 9
# 包含通用 Makefileinclude ../Makefile.inc解析:Makefile 定义了三个模块:
- mbr:从
boot/mbr.S编译,写入扇区 0 - loader:从
boot/loader.S编译,写入扇区 1 - kernel:从
kernel/目录编译,写入扇区 9 及之后
../Makefile.inc 提供了通用的构建逻辑,包括:
- 编译汇编和 C 源文件
- 链接内核
- 创建 VHD 镜像
- 将各个模块写入正确的扇区偏移
构建命令:
cd 19.boot-imagemake all # 构建所有模块并创建镜像make run # 在 QEMU 中运行make clean # 清理构建产物系统启动流程图
运行与验证
编译运行
# 进入章节目录cd 19.boot-image
# 清理旧构建make clean
# 构建镜像make all
# 在 QEMU 中运行make run
# 或者带 VNC 显示运行make run-vnc
# 或者带调试支持运行make run-vnc.debugVHD 镜像本身是标准虚拟磁盘格式,理论上可以 dd 写入 U 盘或移动硬盘,在真实 PC 上通过 BIOS 启动。本系列的镜像在 QEMU 上验证通过,真机启动受限于具体主板的 BIOS 兼容性和磁盘控制器支持,未在所有机型上逐一验证,建议以 QEMU 为主要运行环境。
预期输出
启动时会看到以下输出:
=================================== | 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.
/> helpCommands: help clear echo ls cat mkdir touch write pwd cd ps mem uptime version exit
/> ls home/ bin/ tmp/ readme.txt
/> cat readme.txtWelcome 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.txtHello MyOS!
/home/user> psTID NAME STATE3 shell RUNNING
/home/user> uptimeUptime: 0 seconds
/home/user> versionMyOS v1.0 - Complete Teaching OS
/home/user> exitSystem halted.功能验证清单
启动后,可以验证以下功能:
- 引导流程:系统从 MBR 成功启动,经过 Loader 到内核
- 内存管理:
mem命令显示 E820 内存映射信息 - 中断和定时器:
uptime命令显示系统运行时间,证明定时器工作正常 - 文件系统:
ls、cat、mkdir、touch、write等命令证明文件系统工作正常 - 任务调度:
ps命令显示当前任务状态 - Shell:可以交互式执行命令
- 键盘驱动:可以正常输入命令
- 自检功能:所有自检测试通过
踩坑记录
问题 1:系统启动后没有输出
可能原因:
- VGA 显示驱动初始化失败
- 中断未正确启用,导致后续初始化卡住
- 内核加载位置错误
解决方案:
- 检查
vga_clear()是否在初始化序列的最前面 - 确认
enable_interrupts()在idt_init()之后调用 - 检查内核链接脚本
kernel/link.ld中的入口地址是否正确 - 使用
make run-vnc.debug启动 QEMU 并连接 GDB 调试
问题 2:键盘输入无响应
可能原因:
- 键盘驱动未初始化
- 中断未启用
- 键盘缓冲区处理有问题
解决方案:
- 确认
keyboard_init()已被调用 - 检查
keyboard_has_char()和keyboard_getchar()函数实现 - 验证键盘中断处理程序是否正确注册到 IDT
- 添加调试输出,检查键盘扫描码是否被接收到
问题 3:文件系统命令失败
可能原因:
- SimpleFS 未正确初始化
- 文件系统未挂载
- VFS 接口调用错误
解决方案:
- 确认
simple_fs_init()已被调用 - 检查文件系统初始化时的错误返回值
- 验证文件操作(
fs_open、fs_read、fs_write)的返回值 - 添加调试输出,跟踪文件系统调用链
问题 4:构建失败或镜像不完整
可能原因:
- 模块偏移配置错误
- 链接脚本问题
- 编译器或链接器版本不兼容
解决方案:
- 检查 Makefile 中的
*_OFFSET配置是否正确 - 验证
kernel/link.ld中的内存布局 - 确认使用正确的交叉编译工具链(
i386-elf-gcc) - 使用
make view查看磁盘镜像的十六进制内容
至此,从第 01 章的系统加电到第 19 章的完整启动镜像,一个教学操作系统从零到一的旅程结束了。这个系统虽然简化,但涵盖了操作系统的基本概念:引导、内存管理、调度、同步、特权级隔离、文件系统、进程管理和用户交互。
参考
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时








