当“爆肝!自研内核、自研引擎、自研架构的操作系统”这样的标题出现在技术社区时,围观的人很多,真正能讲清楚的人很少。一个操作系统到底什么才算“自研”,是从引导扇区开始写,还是改一个 Linux 发行版的品牌信息就算?自研内核和自研引擎又分别落在系统的哪一层?本文不打算替任何项目“验明正身”,而是把这条路拆开,从内核职责、架构选型、引导流程、最小可运行代码到调试排错,完整走一遍。你可以跟着示例在 QEMU 里跑出第一个自制内核,再理解为什么“自研架构”比“自研内核”更难,以及真正生产级操作系统还需要什么。
1. 先拆解“自研内核、自研引擎、自研架构”到底意味着什么
开发者和产品宣传里常见的“自研”,含义差别很大。如果不先划清边界,很容易把“基于开源代码二次开发”和“从零实现”混为一谈。下面把标题里的三个关键词放到操作系统工程语境里重新定义。
1.1 自研的边界:内核、驱动、引擎和工具链要分开算
“自研内核”最严格的定义,是指内核核心代码的引导、中断、内存管理、进程调度、系统调用、设备驱动等内容都由开发团队自己编写,而不是直接复用 Linux、BSD 等现成内核源码。这里有一个现实:即使是最纯粹的自研内核,也几乎不可能绕开编译器、链接器、引导协议和硬件规范。GCC、NASM、GRUB、Multiboot 规范都是外部的。你不能因为用了 GCC 就说编译器不是自研,但也不能因为用了 GCC 就否认内核逻辑是自研。
“自研引擎”在操作系统语境里通常不是指游戏引擎,而是指系统内的关键基础设施,比如窗口合成器、图形渲染栈、运行时、脚本引擎或 GUI 框架。一个自制内核如果只能在 VGA 文本模式下打印字符串,也可以认为它有一个“自研的最小显示引擎”;如果做到了 framebuffer 绘制、字体渲染和窗口管理,那才是真正意义上的图形引擎。
“自研架构”则指操作系统内部模块的划分方式和协作机制,也就是内核设计层面的事。同样的功能,可以用宏内核把所有模块放进内核态,也可以用微内核把大部分服务搬到用户态。架构设计决定了系统的性能、稳定性、安全性和后续维护难度,这也是“自研架构”最有技术含量、最容易被忽视的部分。
1.2 内核到底负责什么
操作系统内核向下管理硬件,向上为用户程序提供服务。核心职责可以归纳为六个方面:
- 进程与线程管理:创建、调度、销毁任务,切换上下文。
- 内存管理:虚拟地址映射、物理页分配、进程隔离。
- 中断与异常处理:响应硬件事件、处理 CPU 异常。
- 系统调用:为用户态程序提供受控的内核接口。
- 文件系统与设备驱动:抽象磁盘、键盘、鼠标、显示器等硬件。
- 同步与通信:提供锁、信号量、消息队列等机制。
这六件事没有一件是“开机看到桌面”那么简单。它们之间还有复杂的依赖关系:没有可靠的内存管理,进程调度就没有隔离;没有中断,设备驱动就无法异步响应;没有系统调用,用户程序就无法安全地访问内核服务。任何一个环节出问题,都可能导致系统崩溃、数据损坏或安全漏洞。
1.3 自研引擎不只是“图形引擎”
在自制操作系统项目中,常见的第一步“引擎”是 VGA 文本输出。虽然看起来只是往0xB8000显存写字符,但它已经处在“设备驱动 + 显示抽象”的边界上。再往后,如果要支持鼠标、多窗口、字体、GPU,就需要设计输入事件系统、窗口树、绘制上下文、缓存管理和合成器。
所以,“自研引擎”在 OS 语境下应该理解为“应用程序运行所依赖的系统级基础设施”。一个自制 OS 至少要有自己的字符输出、字符串处理、内存分配、任务调度或 GUI 模块,才能撑起后续应用。如果不能区分“应用层引擎”和“系统级引擎”,讨论自研就很容易失真。
1.4 为什么“自研架构”比“自研内核”更容易被混淆
“自研内核”可以通过代码行数、文件结构来验证,但“自研架构”看不见摸不着。常见混淆是把“目录结构不同”当成“架构不同”。真正的架构差异体现在模块边界和通信机制上,例如:
- 驱动是运行在内核态还是用户态。
- 进程间通信走系统调用还是消息总线。
- 文件系统通过虚拟文件系统层统一接入,还是各个驱动直接操作硬件。
- 崩溃隔离是模块级隔离,还是整个内核一起崩溃。
下表对比了三种主流内核架构的设计思路:
| 架构 | 核心思想 | 优点 | 缺点 | 典型代表 |
|---|---|---|---|---|
| 宏内核 | 核心服务全部放内核态 | 调用路径短、性能高 | 内核态崩溃影响全局 | Linux |
| 微内核 | 内核态只保留 IPC 等最小机制 | 模块隔离、稳定性好 | IPC 开销大、实现复杂 | QNX、Minix |
| 混合内核 | 微内核思路但仍保留关键驱动在内核态 | 平衡性能与稳定 | 结构复杂、难验证 | Windows NT、macOS/XNU |
自制操作系统前,至少要明确自己的架构倾向。否则写到最后,模块越来越乱,可能连“内核态/用户态”边界都补不回来。
2. 从零写一个操作系统:知识准备和环境搭建
从零写内核并不需要急着安装一个复杂的 IDE。先准备一块干净的 Linux 环境、一个交叉编译工具链和一个硬件模拟器,让第一行代码能在虚拟机里跑起来,比任何理论都更有价值。
2.1 需要先建立的核心知识栈
- 汇编语言:x86 下的寄存器、栈、寻址、中断指令。
- C 语言:指针、内存布局、结构体、内联汇编。
- 链接脚本:理解
.text、.data、.bss段,以及内核加载地址。 - 引导协议:x86 加电后如何从 BIOS/UEFI 到引导加载器,再到内核。
- 硬件基础:中断控制器、定时器、串口、显存地址。
这些知识不需要全部精通后再动手。适合的顺序是:先通过 QEMU 跑通最小内核,然后反向补充中断和内存管理原理。如果一上来就啃 Intel 手册,很容易迷失。
2.2 在 Ubuntu/Debian 上安装构建工具
以下命令以 Ubuntu/Debian 为例:
sudo apt update sudo apt install -y build-essential nasm qemu-system-x86 grub-pc-bin xorriso mtools各组件作用如下:
| 工具 | 作用 |
|---|---|
| build-essential | 提供 gcc、make、ld 等基础编译工具 |
| nasm | 汇编代码编译器,用于编写内核入口 |
| qemu-system-x86 | 硬件模拟器,代替真机运行内核 |
| grub-pc-bin | 生成 GRUB 引导镜像的工具 |
| xorriso | 创建 ISO 需要的工具,grub-mkrescue 依赖它 |
| mtools | 操作 FAT 镜像的工具,GRUB 镜像生成时使用 |
检查是否安装成功:
nasm -v gcc --version qemu-system-i386 --version grub-mkrescue --version如果没有输出版本信息,说明安装不完整。后续所有构建都会卡在这一步。
2.3 目标平台选型:先走 x86_32,不要直接挑战 x86_64
自制内核最友好的起步平台是 x86_32。
- 32 位模式不需要处理长模式(Long Mode)下的四层页表,只要理解基本的 32 位分页即可。
- 32 位内核的 Multiboot 头更简单,GRUB 可以直接加载。
- VGA 文本模式在 QEMU 中非常稳定,适合最早期的可视化输出。
- 等 32 位版本跑通后,再迁移到 x86_64,把页表和长模式作为第二阶段挑战。
如果你在 Windows 上尝试,也不建议直接加载到真机。先装 WSL 或虚拟机,再进入 QEMU。很多初学者直接在自己的 PC 上测试自制内核,结果遇到 VMware “客户机操作系统已禁用 CPU”或无法启动虚拟机等问题,其实都不是内核代码的问题,而是虚拟化平台配置不当。用 QEMU 可以完全绕开这些环境噪声。
2.4 设计一个最小项目目录
推荐的最小工程结构如下:
minimal-os/ ├── boot/ │ └── boot.asm ├── kernel/ │ ├── kernel.c │ ├── vga.c │ └── vga.h ├── linker.ld └── Makefileboot.asm负责符合 Multiboot 规范的引导头,并调用 C 入口。kernel.c是 C 语言入口。vga.c是第一个“自研引擎”的最小实现。linker.ld决定内核二进制在内存中的布局。
这个结构足够小,但已经包含了引导、链接、入口、设备输出四个关键环节。后面每加一个功能,都能清楚地知道放在哪一层。
3. 最小自研内核:从引导到 VGA 输出
这一节的目标不是做一个操作系统,而是跑通“CPU 加电 -> GRUB -> 内核 -> 屏幕输出”的最小闭环。这个闭环能通过,说明自研内核的引导链是正确的。
3.1 先理解 Multiboot 引导协议
当计算机启动后,BIOS/UEFI 会加载引导加载器。在本文示例中,GRUB 检测到符合 Multiboot 规范的内核后,会把内核加载到内存,并跳转到入口。内核必须在内核文件头部前若干字节内提供multiboot header,包含三个 32 位字段:
- magic:固定值
0x1BADB002,表示这是 Multiboot 内核。 - flags:这里设置为
0,表示我们不要求 GRUB 特殊处理。 - checksum:魔术值加 flags 加 checksum 必须等于 0,公式为
-(magic + flags)。
如果这三个字段不正确,GRUB 会直接拒绝加载。
3.2 编写入口汇编 boot.asm
boot/boot.asm内容如下:
section .multiboot align 4 dd 0x1BADB002 ; magic dd 0x0 ; flags dd -(0x1BADB002 + 0x0) ; checksum section .text global start extern kernel_main start: cli ; 关闭中断,进入内核后自己管理 mov esp, stack_top ; 设置栈顶 call kernel_main ; 进入 C 代码 hlt ; 如果 C 代码返回,就停机 jmp start section .bss align 16 stack_bottom: resb 16384 ; 给内核准备 16KB 栈 stack_top:关键点:
.multiboot段放置 Multiboot header,并且align 4保证 4 字节对齐。cli在进入 C 代码前关中断,避免在 IDT 未初始化前收到中断。mov esp, stack_top必须放在栈段设置之后,否则调用 C 函数时栈不可用。resb 16384在.bss段分配 16KB 零初始化空间,作为内核栈。
这个文件已经覆盖了入口、栈、全局跳转三件事。很多自制内核起不来,就是漏了栈初始化。
3.3 编写链接脚本 linker.ld
内核不能直接使用可执行文件默认的加载地址,需要通过链接脚本控制段布局:
ENTRY(start) SECTIONS { . = 1M; .text : ALIGN(4K) { *(.multiboot) *(.text) } .rodata : ALIGN(4K) { *(.rodata) } .data : ALIGN(4K) { *(.data) } .bss : ALIGN(4K) { *(COMMON) *(.bss) } }为什么起始地址是1M?因为 x86 低地址的 1MB 空间被 BIOS、VGA 显存和实模式数据结构占用。GNU/Linux 内核也通常放在 1MB 以后的物理内存中。内核从 1MB 开始,可以避开这些早期资源。
3.4 C 入口和 VGA 文本输出
接下来实现“自研引擎”的最小雏形,直接向 VGA 文本显存写入字符。文本模式默认使用物理地址0xB8000,每 2 个字节表示一个字符,低字节是 ASCII 码,高字节是颜色属性。
kernel/vga.h:
#ifndef VGA_H #define VGA_H #include <stdint.h> #include <stddef.h> void terminal_initialize(void); void terminal_writestring(const char *data); #endifkernel/vga.c:
#include "vga.h" #define VGA_ADDR 0xB8000 #define VGA_COLS 80 #define VGA_ROWS 25 static uint16_t *vga_buffer; static size_t terminal_row; static size_t terminal_col; static inline uint8_t vga_entry_color(uint8_t fg, uint8_t bg) { return (uint8_t)(fg | bg << 4); } static inline uint16_t vga_entry(char c, uint8_t color) { return (uint16_t)c | (uint16_t)color << 8; } void terminal_initialize(void) { vga_buffer = (uint16_t *)VGA_ADDR; terminal_row = 0; terminal_col = 0; for (size_t y = 0; y < VGA_ROWS; y++) { for (size_t x = 0; x < VGA_COLS; x++) { const size_t index = y * VGA_COLS + x; vga_buffer[index] = vga_entry(' ', vga_entry_color(0x0f, 0x00)); } } } static void terminal_putchar(char c) { if (c == '\n') { terminal_col = 0; terminal_row++; } else { const size_t index = terminal_row * VGA_COLS + terminal_col; vga_buffer[index] = vga_entry(c, vga_entry_color(0x0f, 0x00)); terminal_col++; } if (terminal_col >= VGA_COLS) { terminal_col = 0; terminal_row++; } } void terminal_writestring(const char *data) { while (*data) { terminal_putchar(*data); data++; } }kernel/kernel.c:
#include "vga.h" void kernel_main(void) { terminal_initialize(); terminal_writestring("minimal-os running, self-made kernel.\n"); }这里的 VGA 输出模块可以看作最简单的“自研引擎”:
- 它不依赖任何标准库,不调用 BIOS 中断。
- 它直接访问硬件映射地址,属于内核态的底层输出。
- 它的接口是
terminal_writestring,未来可以被串口、framebuffer、GUI 模块替换。
3.5 Makefile 和构建流程
Makefile用来统一编译、链接和制作 ISO:
C_SOURCES = kernel/kernel.c kernel/vga.c ASM_SOURCES = boot/boot.asm CFLAGS = -m32 -ffreestanding -nostdlib -nostdinc -fno-builtin -fno-stack-protector -Wall -Wextra LDFLAGS = -m elf_i386 -T linker.ld all: minimal-os.bin boot/boot.o: $(ASM_SOURCES) nasm -f elf32 $< -o $@ %.o: %.c gcc $(CFLAGS) -c $< -o $@ minimal-os.bin: $(patsubst %.c,%.o,$(C_SOURCES)) boot/boot.o ld $(LDFLAGS) $^ -o $@ iso: minimal-os.bin mkdir -p iso/boot/grub cp minimal-os.bin iso/boot/minimal-os.bin echo 'menuentry "minimal-os" { multiboot /boot/minimal-os.bin }' > iso/boot/grub/grub.cfg grub-mkrescue -o minimal-os.iso iso run: iso qemu-system-i386 -cdrom minimal-os.iso debug: iso qemu-system-i386 -cdrom minimal-os.iso -s -S clean: rm -rf *.o boot/*.o kernel/*.o minimal-os.bin minimal-os.iso iso说明:
-ffreestanding告诉 GCC 编译时不要假设存在 C 标准库。-nostdlib避免链接标准库。-fno-stack-protector关闭栈保护,内核中暂时不需要额外的 guard。nasm -f elf32将汇编编译成 32 位 ELF 对象文件。ld -m elf_i386生成 32 位内核镜像。grub-mkrescue把内核放进 GRUB 可引导 ISO。
如果你的系统提示error: unrecognized command-line option '-m32',说明缺少 32 位编译支持。安装gcc-multilib即可:
sudo apt install -y gcc-multilib3.6 运行并验证
执行:
make run预期结果:QEMU 窗口出现一个 GRUB 菜单,选择minimal-os后,屏幕进入黑色底色的文本模式,并输出:
minimal-os running, self-made kernel.这一步验证了三条链路:
- GRUB 正确解析 Multiboot 头。
- 汇编入口正确设置栈并跳转到 C。
- VGA 输出模块能把数据写入显存并呈现到屏幕。
到这里,你已经拥有了一个“自研内核 + 自研显示引擎”的最小可运行闭环。它虽然还不能叫操作系统,但已经是一个完整的内核雏形。
4. 从“能启动”到“像操作系统”:中断、内存与进程
打印字符串只是起点。要让这个内核真正像操作系统,需要继续补三块:中断异常处理、内存管理和进程调度。每一步都会显著提高复杂度,也是检验“自研架构”能力的关键。
4.1 串行输出:先建立调试通道
在继续推进前,建议先实现串口输出。串口日志比 VGA 文本更适合调试,因为 QEMU 可以把串口内容重定向到终端文件,不会因为屏幕闪烁丢失信息。
QEMU 启动参数加上-serial stdio,就可以把 COM1 输出到终端:
qemu-system-i386 -cdrom minimal-os.iso -serial stdio串口初始化代码通常包含在serial.c中,关键流程是:
- 向
0x3F8 + 1写 0x00,关闭串口中断。 - 向
0x3F8 + 3写 0x80,启用 DLAB 位。 - 向
0x3F8 + 0和0x3F8 + 1写波特率除数。 - 向
0x3F8 + 3写 0x03,设置 8 位数据、无校验、1 停止位。 - 向
0x3F8 + 2写 0xC7,启用 FIFO。 - 写字符前检查
0x3F8 + 5的第 5 位是否为 1,表示发送缓冲为空。
这样后续所有调试信息都可以通过串口输出,不依赖屏幕显示。
4.2 GDT 与 IDT:内核不能裸奔
只有 VGA 输出和串口日志还不够,因为此时 CPU 还运行在实模式遗留的段模型下,中断也处于关闭状态。要让系统支持特权级和保护模式,需要建立全局描述符表 GDT;要让硬件事件不导致三重故障,需要建立中断描述符表 IDT。
GDT 至少要定义内核代码段和数据段,并设置段描述符中的特权级、段基址和段界限。IDT 则把每个中断向量绑定到对应的处理函数,例如:
- 向量 0:除零异常。
- 向量 14:缺页异常。
- 向量 32 以后:外部硬件中断,如 PIT 定时器、键盘。
- 自定义向量:系统调用入口。
常见错误是在没有建立 IDT 时提前开启中断。此时任何硬件中断都会进入未定义处理路径,CPU 抛出异常,异常又没地方处理,最后触发 Triple Fault 并重启。排查时可以在 QEMU 里加参数查看 CPU 重置日志:
qemu-system-i386 -cdrom minimal-os.iso -d int,cpu_reset -D qemu.log4.3 内存管理:从裸地址到分页
没有内存管理的内核,所有程序都直接访问物理内存,既无法隔离,也无法实现用户态。分页机制通过 CR3、页目录和页表将虚拟地址映射到物理地址,操作系统可以给每个进程独立的地址空间。
一个最小分页流程是:
- 分配一页物理内存作为页目录。
- 在该页目录中填充页表项,把某些虚拟地址映射到物理地址。
- 设置页目录项的特殊属性位,如可写、存在位。
- 把页目录地址写入 CR3,开启分页。
物理内存分配器是这里的“地基”。常见做法是用位图记录每个物理页是否被占用,分配时从位图中找到第一个空闲位。也可以用空闲链表把连续页组织起来。无论哪种方式,都必须考虑对齐、边界和内存碎片问题。
4.4 进程、系统调用和最小 Shell
内核一旦有了内存管理和中断处理,就可以实现最简单的协作式进程调度:
typedef struct task { uint32_t esp; uint32_t ebp; struct task *next; } task_t;进程切换的核心操作是保存当前进程的寄存器现场到自己的内核栈,然后恢复下一个进程的寄存器现场。协作式调度不依赖时钟中断,可以由当前进程主动让出 CPU。虽然简陋,但足够理解“上下文切换”的本质。
系统调用则需要在 IDT 中注册一个入口,例如使用int 0x80指令进入内核态。用户程序需要预留好参数,内核根据系统调用号分发到不同服务。这里可以实现的第一个系统调用通常是write,让用户态程序能够把字符串输出到屏幕。
有了进程和系统调用,就可以做一个最小 Shell:从键盘读取命令,解析后调用对应的内核功能。这个 Shell 是系统功能和用户之间的第一层“自研应用框架”,也是图形引擎之前最重要的交互入口。
5. 验证、调试和排错:自研内核启动失败就往这些方向查
自制内核的调试和普通应用完全不一样。没有断点堆栈、没有 print 调试器、失败时甚至不会留下错误信息。理解“从现象倒推原因”的排查链路,比写更多代码更关键。
5.1 用 QEMU 和 GDB 做真调试
QEMU 允许内核调试器连接。启动时加-s -S,表示开启 GDB server 并等待连接:
qemu-system-i386 -cdrom minimal-os.iso -s -S然后在另一个终端启动 GDB:
gdb minimal-os.bin target remote localhost:1234 break kernel_main continue有了 GDB,可以在 C 源码级别设置断点,查看寄存器和栈内容。结合-d int,cpu_reset日志,基本能覆盖 90% 的启动阶段问题。
5.2 常见启动失败现象和根因
下表是自制内核最常见的问题,以及对应的检查方式。
| 现象 | 可能原因 | 检查方式 | 处理方案 |
|---|---|---|---|
| GRUB 提示无法加载内核 | Multiboot header 不在文件开头 | hexdump -C minimal-os.bin查看开头字节 | 修正 boot.asm,确保.multiboot段在最前面 |
| 启动后黑屏无反应 | 未设置栈就调用 C 函数 | GDB 查看esp值 | 确认mov esp, stack_top已执行 |
| 启动后循环重启 | IDT 未建立就开启中断,导致 Triple Fault | qemu -d int,cpu_reset -D qemu.log查看日志 | 关闭中断或先安装 IDT |
| 屏幕输出乱码 | VGA 地址或颜色属性计算错误 | GDB 查看vga_buffer内容 | 检查行索引计算和颜色字节高位 |
编译报错-m32不支持 | 缺少 32 位编译支持 | gcc -m32 -v | 安装gcc-multilib |
grub-mkrescue报没有 xorriso | 缺少 ISO 工具 | which xorriso | 安装xorriso |
排查顺序可以固定为:
- 检查是否生成了正确的
minimal-os.bin。 - 检查 Multiboot header 是否位于文件开头。
- 检查链接脚本中入口地址是否和启动约定一致。
- 检查栈是否已初始化。
- 检查是否在无 IDT 时触发了中断。
- 检查 VGA 显存地址和颜色字节是否正确。
- 使用 QEMU 日志和 GDB 确认 CPU 实际执行路径。
5.3 学习环境和真机环境的差异
本文所有示例都建议在 QEMU 中运行。学习环境的好处是:
- 不需要真实硬件驱动,不需要处理 UEFI 安全启动。
- 崩溃后重启虚拟机即可,不会损坏实体机。
- 可以通过 GDB 单步调试,不会因为死机黑屏失去所有信息。
真正上真机时,还需要额外考虑:
- UEFI 启动需要不同的引导协议,比如
multiboot2或EFI system table。 - 键盘、鼠标、磁盘、显示器驱动必须切到真实硬件。
- ACPI 电源管理、PCI 枚举、设备树解析等都要做。
- 启动安全校验、固件配置、串口重定向都会影响调试。
所以学习阶段千万不要急着找真机验证。把 QEMU 跑熟,再逐步引入 UEFI 和真实驱动,是最稳妥的路径。
6. 动手前必读:环境检查清单与防御写法
最后一个部分不是“凑章节”,而是让整个学习过程少走弯路。自制内核的坑往往不是代码逻辑复杂,而是基础环境和防御习惯没有建立起来。
6.1 环境检查清单
开始写代码前,先确认以下几点:
gcc、make、nasm、ld都能正常执行。qemu-system-i386可以启动图形窗口。- 安装了
grub-pc-bin、xorriso、mtools。 - 项目目录没有中文路径和空格。
- 使用 Git 管理代码,每次改动前提交一次快照。
- 配置好 QEMU 日志参数,避免“没输出就重启”。
6.2 内核开发中的典型坑和防御写法
| 坑 | 错误现象 | 防御建议 |
|---|---|---|
| 忘记初始化栈 | 调用 C 函数时随机崩溃 | 入口汇编里显式设置esp |
| 不关闭中断就初始化 | 未建立 IDT 时收到中断 | 入口处先cli,需要时再开 |
| 直接使用标准库函数 | 编译通过,链接失败 | 使用-ffreestanding -nostdlib |
| 访问 I/O 寄存器不加 volatile | 编译器优化导致读写被忽略 | 对 MMIO 地址使用volatile或ioremap封装 |
| 空循环忙等浪费 CPU | 调度器无法切换任务 | 设计阻塞等待、睡眠队列或时钟中断 |
不要小看这些细节。比如volatile,编译器看到连续的显存写入,可能认为地址内容不会改变而优化掉部分写操作。内核中对硬件寄存器的访问必须使用 volatile,否则在 O2 优化下会出现“代码写了但没效果”的诡异问题。
6.3 推荐的学习路线和扩展方向
如果目标是真正理解操作系统而不是做标题党,推荐按下面的路线推进:
- 完成本文的最小内核,跑通 VGA 输出。
- 加入串口日志,替换
printf。 - 实现 GDT 和 IDT,处理键盘中断。
- 实现物理内存分配器和基本分页。
- 实现协作式进程调度。
- 增加
int 0x80系统调用接口。 - 做一个小 Shell,让用户能执行命令。
- 尝试任务 A 和任务 B 的上下文切换。
资料方面,可以参考 xv6、MIT 6.828 实验、osdev wiki 和 Linux 0.11 源码。不要一开始就膨胀到要“自研全部图形栈”,先跑通内核基础,再逐步增加文件系统、网络协议栈和窗口系统。
如果你已经有了一个成熟项目,想验证它到底是不是“自研”,最直接的方法不是看 README,而是把构建日志、源码历史、依赖清单和二进制产物摆在一张表里核对。内核的每个模块是谁写的、谁维护的、能否独立构建,这些信息远比一句“自研”更可信。
自制操作系统是一场漫长但极有回报的工程实践。它逼你重新理解“程序是如何被加载和执行的”“内存为什么是虚拟的”“进程切换到底保存了什么”。从最小内核到可以日常使用的系统之间,还有驱动、网络、文件系统、安全模型、用户态应用等大量工作。今天的 VGA 字符串,就是通向这些复杂系统最可靠的第一级台阶。