在之前的章节中,我们逐步构建了操作系统的各个核心组件:从基础的 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 的主要任务是:
- 检查引导签名(0x55AA)
- 从磁盘读取 Loader 程序
- 将 Loader 加载到内存并跳转执行
Loader:位于扇区 1-8(共 8 个扇区,约 4KB)。Loader 完成更复杂的任务:
- 进入保护模式
- 设置 GDT(全局描述符表)
- 启用分页机制
- 加载 ELF 格式的内核
- 跳转到内核入口点
Kernel:从扇区 9 开始存储,包含完整的操作系统内核。内核以 ELF 格式存储,Loader 负责解析 ELF 并加载到正确的内存位置。
VHD 磁盘镜像
VHD(Virtual Hard Disk)是虚拟磁盘的通用格式,具有以下优势:
- 标准格式:被 QEMU、VirtualBox 等主流虚拟机支持
- 固定大小:可以预先分配固定大小的磁盘空间
- 便于管理:单个文件包含完整的磁盘内容
- 可移植性:方便在不同机器之间传输
VHD 镜像的布局:
VHD 镜像 (disk.vhd)┌─────────────────────────┐ 偏移 0x0000│ VHD Header │ 512 字节(VHD 格式头)├─────────────────────────┤ 偏移 0x0200│ MBR (扇区 0) │ 512 字节├─────────────────────────┤ 偏移 0x0400│ Loader (扇区 1-8) │ 4096 字节├─────────────────────────┤ 偏移 0x1400│ Kernel (扇区 9+) │ 内核 ELF 镜像├─────────────────────────┤│ (文件系统数据) │ SimpleFS 数据区└─────────────────────────┘构建过程:
- 编译 MBR、Loader、Kernel,生成二进制文件
- 创建空的 VHD 镜像文件
- 将 MBR 写入偏移 0x0200
- 将 Loader 写入偏移 0x0400
- 将 Kernel 写入偏移 0x1400
- (可选)写入文件系统数据
系统集成与初始化
操作系统从引导加载程序接管控制权后,需要按正确顺序初始化各个子系统:
- 依赖关系:某些子系统依赖于其他子系统(如任务调度依赖定时器)
- 资源分配:需要在早期初始化阶段分配关键资源(如内核堆)
- 错误处理:初始化失败时需要能够报告错误
- 性能优化:某些操作在启动阶段一次性完成,避免运行时开销
完整的初始化流程:
每个初始化步骤都有其特定的职责:
- 早期初始化: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代码实现
文件结构
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_stringprint_done: ret
; 填充剩余空间到 510 字节times 510-($-$$) db 0
; 引导签名dw 0xAA55解析:MBR 是系统启动的第一步。它非常简单,只做三件事:
- 设置段寄存器和栈指针
- 调用 BIOS 中断读取 Loader(8 个扇区)
- 跳转到 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 0x08DATA_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 复杂得多:
- 加载 GDT:全局描述符表定义了内存段的属性,是保护模式的基础
- 启用保护模式:设置 CR0 寄存器的 PE(Protection Enable)位
- 设置分页:创建页目录和页表,启用虚拟内存
- 加载内核:读取内核 ELF 文件并解析(示例中简化了)
- 跳转执行:跳转到内核入口点,进入 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 实现了基本的命令行界面:
- 命令解析:
parse_cmd()函数将输入行分解为参数数组 - 命令分发:使用
strcmp匹配命令名并调用相应的处理函数 - 文件操作:通过 VFS 接口调用文件系统功能
- 路径处理:支持绝对路径和相对路径
Shell 是一个独立运行的任务,通过键盘输入获取命令,然后解析并执行。这展示了用户态进程如何与内核交互。
构建系统(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.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.
/> helpCommands: 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.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_init()是否在初始化序列的最前面 - 确认
enable_interrupts()在idt_init()之后调用 - 检查内核链接脚本
kernel.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查看磁盘镜像的十六进制内容
小结
从 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:命令解析、文件操作、内置命令
后续扩展方向
虽然本教程已经结束,但操作系统开发是一个永无止境的领域。你可以继续探索:
- 网络支持:实现 TCP/IP 协议栈、网络驱动(以太网)
- 图形界面:开发 GUI 窗口系统、图形驱动(VESA)
- 多核支持:实现 SMP(对称多处理)、多核调度
- 安全特性:添加用户权限、文件权限、内存保护
- 驱动框架:支持 PCI 设备、USB 设备、音频设备
- 高级文件系统:支持 ext4、FAT32 等成熟文件系统
- 应用程序:开发更多用户工具(编辑器、编译器等)
学习成果
通过这 19 章的学习,你已经:
- 理解了计算机启动的完整流程
- 掌握了操作系统的核心原理
- 实现了从零开始的操作系统开发
- 体验了系统集成的复杂性
- 获得了深入底层编程的经验
这些知识不仅对操作系统开发有用,对理解计算机系统的各个方面都有很大帮助。
参考
支持与分享
如果这篇文章对你有帮助,欢迎支持作者或分享给更多人
部分信息可能已经过时






