
示例工程【免费下载链接】os-tutorialHow to create an OS from scratch项目地址https://gitcode.com/gh_mirrors/os/os-tutorial点击查看免费下载导读本文围绕开源仓库 os-tutorialHow to create an OS from scratch的根级 README系统梳理从零编写操作系统的完整实战路线从 BIOS 引导扇区、16 位实模式汇编起步依次跨越 32 位保护模式、GDT、C 语言内核、屏幕驱动、中断与 IRQ、键盘与时钟、简易 shell 与内存分配器最终形成可运行在 QEMU 上的最小操作系统。读完本文你将掌握一条可复现的从第一行引导汇编到 C 内核、再到硬件中断驱动与用户输入的渐进式开发路径并能独立运行、调试和扩展这个迷你 OS。项目定位面向动手派的零理论 OS 课程os-tutorial 是一个个人学习型开源项目作者在根 README 中坦言其动机大学里学过分页、信号量、内存管理等高级特性却从未从自己的引导扇区开始动手实现过操作系统。这个仓库因此被设计成一份代码教程面向对底层计算感兴趣、但没有时间或精力通读 Linux 内核的开发者核心特征是几乎不讲理论作者认为过多的理论比没有理论更糟因为它让事情看起来比实际更难。理论交给 Google教程只负责动手。课时短小每课 515 分钟即可完成配合一份简洁 README 和注释详尽的代码示例。严格按序推进所有章节基于前一章的代码层层叠加跳过章节会错过上下文例如不知道第 05 课里mov ah, 0x0e为何存在就是因为没看第 02 课。仓库根 README 同时给出明确的使用方法先按编号顺序打开每个子目录的 README阅读第一行应预先熟悉的 Concepts 关键词与第二行本课 Goal再读正文最后打开注释完善的代码文件有余力者可以先读完 README 后尝试自己写代码再对照示例并动手破坏/复现以验证理解。仓库的课程策略清单Strategy展示了完整路线图其中已完成项包括无 GRUB 从零引导、进入 32 位模式、从汇编跳转到 C、中断处理、屏幕输出与键盘输入、随需增长的迷你libc尚未完成项包括内存管理、文件系统、更完整的 shell、用户态、文本编辑器、多进程与调度以及如果足够大胆BASIC 解释器、GUI 与网络。需要特别说明的是README 顶部的警示信息指出这是一个已停止维护的旧项目存在已知技术与设计问题其定位是玩得开心的教学资料读者若想深入学习 OS 设计应同时参考更新、更权威的资料。本文基于该仓库当前内容进行事实性讲解不将其包装为最佳实践。环境准备安装 QEMU、NASM 与工具链仓库第 00 章00-environment/README.md明确了运行全部课程所需的环境QEMU 模拟器与NASM 汇编器。作者的开发环境是 Mac并注明 Linux 体验更好因为标准工具通常已就绪。在 macOS 上先安装 [Homebrew]再执行brew install qemu nasm。关键警告不要使用 Xcode 自带的nasm它多数情况下不可用务必使用/usr/local/bin/nasm。部分系统把 QEMU 拆分成多个二进制此时应以qemu-system-x86_64 binfile的方式调用。对于进入 C 内核阶段第 11 章起还需要交叉编译工具链i386-elf-gcc、i386-elf-ld等其构建过程详见下文交叉编译器一节。第 0102 课写出第一个被 BIOS 认可的引导扇区引导扇区的原理512 字节与0xAA55魔数计算机上电后BIOS 并不知道如何加载操作系统因此把这项任务委托给引导扇区boot sector它必须被放在磁盘的固定标准位置——磁盘的第一个扇区cylinder 0, head 0, sector 0大小恰为 512 字节。为了确认这个磁盘可引导BIOS 检查该扇区的第 511、512 字节是否为0xAA55。第 01 课01-bootsector-barebones/README.md给出了最简单的引导扇区二进制形态除首部用于跳转的e9 fd ff无限跳转外全是零末尾以55 aa收尾。注意 x86 是小端序因此 16 位值0xAA55在内存中写作字节序列55 aa。仓库中的 boot_sect_simple.asm 以汇编形式复现了同样的结构; Infinite loop (e9 fd ff) loop: jmp loop ; Fill with 510 zeros minus the size of the previous code times 510-($-$$) db 0 ; Magic number dw 0xaa55其中times 510-($-$$) db 0负责把扇区填充到 510 字节$为当前位置、$$为段起始二者相减得到已用字节数dw 0xaa55在最后 2 字节写入魔数。用 NASM 编译为纯二进制并交给 QEMU 运行nasm -f bin boot_sect_simple.asm -o boot_sect_simple.bin qemu boot_sect_simple.bin某些系统需改用qemu-system-x86_64 boot_sect_simple.bin若报 SDL 错误可尝试--nographic与/或--curses标志。运行后会看到一个提示 Booting from Hard Disk... 的窗口随后便是无尽的死循环——这是新手第一次亲手让硬件按自己的代码运转。用 BIOS 中断打印字符第 02 课02-bootsector-print/README.md让沉默的引导扇区开口说话。做法是借助 BIOS 的视频服务中断int 0x10把字符写入alax的低 8 位把功能码0x0e写入ahax的高 8 位0x0e的含义是以 tty 模式写出al的内容然后触发int 0x10。仓库 boot_sect_hello.asm 完整演示了逐字符打印 Hellomov ah, 0x0e ; tty mode mov al, H int 0x10 mov al, e int 0x10 mov al, l int 0x10 int 0x10 ; l is still on al, remember? mov al, o int 0x10 jmp $ ; jump to current address infinite loop ; padding and magic number times 510 - ($-$$) db 0 dw 0xaa55这个例子的精妙之处在于复用连续两次int 0x10之间没有重新设置al因此第二个 l 会重复打印——顺便证明了al的值在中断返回后保持不变。作者同时提醒现实中不应假设ah恒定其他进程可能污染寄存器只是此刻我们是 CPU 上唯一运行的东西。可用xxd file.bin检查二进制内容再用同样的nasm -fbinqemu组合运行屏幕上将出现 Hello 并挂起在死循环中。第 0306 课内存寻址、栈与 16 位实模式基本功内存布局与0x7C00偏移第 03 课03-bootsector-memory/README.md的目标是搞清引导扇区被 BIOS 加载到0x7C00这一关键事实。为了讲透偏移概念作者设计了一个对照实验先在数据区定义the_secret: db X然后用四种方式尝试访问它mov al, the_secret—— 把标签的偏移量当作值错误mov al, [the_secret]—— 把偏移量当作地址解引用缺少0x7C00基址仍是错的mov al, the_secret 0x7C00—— 手动加上基址正确mov al, 2d 0x7C00—— 用字节计数手工计算 X 的实际位置同样正确但脆弱运行 boot_sect_memory.asm 会看到类似1[2¢3X4X的输出第 1、2 种策略得到的是随机垃圾字节第 3、4 种策略打印出正确的 X。注意如果增删指令必须重新数字节更新0x2d这个硬编码偏移。作者强调没有 100% 理解引导扇区偏移和内存寻址之前不要进入下一节。随后引入的解决方案是汇编器的全局偏移指令[org 0x7c00]见 boot_sect_memory_org.asm声明之后所有内存标签的偏移自动加0x7C00于是第 2 种策略mov al, [the_secret]变成标准写法。栈bp与sp第 04 课04-bootsector-stack/README.md篇幅很短bp寄存器保存栈底基址sp保存栈顶栈从bp向下增长即sp递减。作者建议在代码不同位置自行尝试访问栈内地址观察发生了什么。控制结构、函数调用与字符串第 05 课05-bootsector-functions-strings/README.md是进入磁盘读写前最重要的汇编基本功训练覆盖四个主题字符串以 null 字节结尾和 C 一致如mystring: db Hello, World, 0。引号内的文本会被汇编器转换为 ASCII孤零零的0则作为0x00字节写入。控制结构除了已经用过的jmp $死循环汇编跳转由上一条指令的结果决定例如cmp ax, 4 ; if ax 4 je ax_is_four ; do something (by jumping to that label) jmp else ; else, do another thing jmp endif ; finally, resume the normal flow ax_is_four: ..... jmp endif else: ..... jmp endif ; not actually necessary but printed here for completeness endif:je相等跳转、jl小于跳转等条件跳转很多语义直观。函数调用调用函数本质是跳转到标签难点在参数与返回。最原始的做法是预先约定用al/ax传参、用固定标签如endprint返回但这会迅速变成意大利面条式代码且无法复用。正确解法分两步用call/ret代替jmp对——CPU 会把返回地址压栈ret自动弹出返回点因此可变用pusha/popa在函数入口把所有寄存器压栈、出口恢复保证子函数修改寄存器没有副作用。包含外部文件语法为%include file.asm。主文件 boot_sect_main.asm 示范了完整用法先mov bx, HELLO; call print打印新行后mov dx, 0x12fe; call print_hex最后jmp $挂起并在挂起指令之后而不是之前%include两个子例程文件。这里也展示了调用约定print用bx接收字符串基址print_hex用dx接收 16 位数值。子例程实现见 boot_sect_print.asmprint用pusha保护现场后循环执行mov al, [bx]、cmp al, 0、je done检查串尾再mov ah, 0x0e; int 0x10打印add bx, 1步进print_nl则依次发送换行0x0a与回车0x0d两个字节可以删除回车字节观察差异。十六进制打印例程 boot_sect_print_hex.asm 是教学重点它以dx示例假设dx0x1234为输入循环 4 次每次用and ax, 0x000f屏蔽出低 4 位加0x30转成数字字符00x3090x39若超过0x39再额外加 7A0x41与 58 相差 7以表示 AF随后按HEX_OUT 5 - cx计算写入位置用ror dx, 4把下一个 nibble 移到低位循环结束把HEX_OUT初始为db 0x0000,0交给print输出。运行结果会依次打印 Hello, World、换行、Goodbye、换行和0x12fe。实模式分段第 06 课06-bootsector-segmentation/README.md讲解 16 位实模式的分段寻址熟悉者可直接跳过。分段指通过cs代码、ds数据、ss栈、es附加即用户自定义四个段寄存器为所有数据访问指定偏移这些寄存器被 CPU隐式使用一旦设置了ds后续所有内存访问都会自动加上ds的偏移。实际地址的计算不是拼接而是重叠segment 4 address。例如ds 0x4d时[0x20]实际指向0x4d0 0x20 0x4f0。代码层面有一个实用提醒不能直接把字面量mov进段寄存器必须先用通用寄存器中转。第 07 课用 BIOS 中断从磁盘读数据第 07 课07-bootsector-disk/README.md是从引导扇区走向内核的关键一跃OS 不可能装进 512 字节的引导扇区必须从磁盘读出更多代码。好消息是不必亲自控制盘片转动只需像打印字符那样调用 BIOS 例程把al设为0x02读扇区功能码配合所需的柱面cylinder、磁头head与扇区sector参数触发int 0x13。本课首次引入进位标志carry bit它是寄存器之外的一个额外位用于记录运算是否溢出。例如mov ax, 0xFFFF; add ax, 1之后ax 0x0000且 carry 1。进位不能直接读取而是被jccarry 置位则跳转等控制指令使用。另外BIOS 会把al设为实际读到的扇区数因此务必把它与期望值比较以确认读取完整。仓库的 boot_sect_disk.asm 实现了完整的磁盘读取例程boot_sect_main.asm 准备参数并调用它。磁盘几何关系在此明确引导扇区是 hdd 0 的 cylinder 0、head 0 的sector 1扇区从 1 编号因此第 512 字节之后的所有数据恰好属于 sector 2——主例程把这些越界数据填成样例数据供引导扇区读回验证。文档还记录了 QEMU 的实战坑BIOS 在调用引导程序前会把驱动器号放入dl但 QEMU 从硬盘引导时可能有兼容问题两种解决办法是使用-fda标志如qemu -fda boot_sect_main.bin把dl设为0x00通常即可正常工作显式使用-boot标志如qemu boot_sect_main.bin -boot c自动把dl设为0x80引导程序便可读取数据。第 0810 课32 位保护模式、GDT 与模式切换直接写 VGA 显存打印进入 32 位模式后 BIOS 中断将失效因此第 08 课08-32bit-print/README.md提前准备了一套不依赖int 0x10的打印例程直接操作 VGA 视频内存。VGA 内存起始地址为0xb8000其文本模式80×25 网格可避免操作像素。单个字符的寻址公式为0xb8000 2 * (row * 80 col)即每个字符占 2 字节一字节 ASCII、一字节颜色/属性行与行在内存中连续拼接。实现见 32bit-print.asm它固定把字符串打印到屏幕左上角更高级的例程留待 C 内核阶段。GDT全局描述符表第 09 课09-32bit-gdt/README.md说明保护模式下分段机制的变化段偏移不再是地址而成为GDT 中段描述符Segment Descriptor的索引。描述符定义段基址32 位、大小20 位及权限等标志位为便于编程最省事的方式是定义重叠的代码段与数据段各一个——放弃内存保护但足够完成引导后续用高级语言实现再修复。教学上还有两个要点第一个 GDT 表项必须为0x00用于暴露程序员管理地址时的失误CPU 不能直接加载 GDT 地址需要一层元结构GDT 描述符16 位大小 32 位地址用lgdt指令装载。进入保护模式的七个步骤第 10 课10-32bit-enter/README.md把切换过程封装进 32bit-switch.asm标准流程为禁用中断cli加载 GDTlgdt设置控制寄存器cr0的保护模式位通过精心构造的远跳转冲刷 CPU 流水线更新全部段寄存器更新栈跳转到已知标签BEGIN_PM32 位下第一段有效代码的入口。运行 32bit-main.asm 即可在屏幕上看到两段消息标志着我们已经站在保护模式中下一步就是编写真正的内核。第 1113 课交叉编译器与第一个 C 内核搭建 i386-elf 交叉编译器从第 11 课11-kernel-crosscompiler/README.md开始开发语言转向 C为此需要一个交叉编译工具链。Mac 用户必须立即执行本课Linux 用户可稍后。步骤分两段准备安装依赖包 gmp、mpfr、libmpc、gccMac 上经 Homebrew构建交叉 gcc 需要本机 gcc 而非已弃用的 clang然后导出环境变量export CC/usr/local/bin/gcc-4.9 export LD/usr/local/bin/gcc-4.9 export PREFIX/usr/local/i386elfgcc export TARGETi386-elf export PATH$PREFIX/bin:$PATH构建 binutilsmkdir /tmp/src cd /tmp/src curl -O http://ftp.gnu.org/gnu/binutils/binutils-2.24.tar.gz # 若 404 请寻找更新版本 tar xf binutils-2.24.tar.gz mkdir binutils-build cd binutils-build ../binutils-2.24/configure --target$TARGET --enable-interwork --enable-multilib --disable-nls --disable-werror --prefix$PREFIX 21 | tee configure.log make all install 21 | tee make.log构建交叉 gcccd /tmp/src curl -O https://ftp.gnu.org/gnu/gcc/gcc-4.9.1/gcc-4.9.1.tar.bz2 tar xf gcc-4.9.1.tar.bz2 mkdir gcc-build cd gcc-build ../gcc-4.9.1/configure --target$TARGET --prefix$PREFIX --disable-nls --disable-libssp --enable-languagesc --without-headers make all-gcc make all-target-libgcc make install-gcc make install-target-libgcc产物位于/usr/local/i386elfgcc/bin全部带i386-elf-前缀以避免与本机编译器冲突建议把$PATH写入.bashrc。文档特别提醒粘贴网上的长命令前要小心推荐逐行复制。注意仓库中的版本号binutils-2.24、gcc-4.9.1是写作当时的选择链接失效时应自行查找更新版本。用 C 编写同样的底层代码第 12 课12-kernel-c/README.md的核心实验是用 C 编译出和汇编等价的东西并反汇编验证。编译独立的系统级代码需要-ffreestanding标志i386-elf-gcc -ffreestanding -c function.c -o function.o i386-elf-objdump -d function.o反汇编输出中的机器码与汇编课程里见过的如出一辙。随后用链接器生成无元数据的裸二进制本例把偏移固定在0x0i386-elf-ld -o function.bin -Ttext 0x0 --oformat binary function.o用xxd对比function.o含调试信息、标签与function.bin纯机器码再用ndisasm -b 32 function.bin反汇编。文档还布置了三个可选的进阶小实验localvars.c局部变量、functioncalls.c函数调用、pointers.c指针并抛出思考题为什么pointers.c的反汇编结果与预期不符Hello 的 ASCII0x48656c6c6f去哪儿了第一个可引导的 C 内核第 13 课13-kernel-barebones/README.md拼合出完整的最小 OS一个打印 X 的 C 内核 一个能引导它的引导扇区。内核kernel.c 里有一个空函数dummy_test_entrypoint()——它的存在是为了迫使内核入口例程指向一个真实标签main()而不是内核的第 0 字节。编译命令为i386-elf-gcc -ffreestanding -c kernel.c -o kernel.o。入口例程kernel_entry.asm 演示了汇编的[extern main]用法声明外部符号后用call main调用 C 函数链接器负责解析它的内存位置随后jmp $挂起。它不再生成二进制而是生成 ELF 目标文件nasm kernel_entry.asm -f elf -o kernel_entry.o。链接把两个目标文件链接成单个裸二进制内核并把内核放到0x1000引导扇区必须知道这个地址i386-elf-ld -o kernel.bin -Ttext 0x1000 kernel_entry.o kernel.o --oformat binary引导扇区与第 10 课的版本高度相似bootsect.asm 去掉打印消息的行后只有几十行。编译nasm bootsect.asm -f bin -o bootsect.bin。拼接与运行两个文件直接拼接成一个镜像cat bootsect.bin kernel.bin os-image.bin用qemu-system-i386 -fda os-image.bin运行若遇磁盘读取错误可在软盘0x0与硬盘0x80间调整驱动器号。屏幕上应依次出现四段内容16 位实模式启动消息、Loading kernel into memory、左上角 Landed in 32-bit Protected Mode以及覆盖其上的 X。MakefileMakefile 把这个过程自动化kernel.bin由i386-elf-ld -o $ -Ttext 0x1000 $^ --oformat binary生成kernel_entry.o由nasm -f elf生成kernel.o由i386-elf-gcc -ffreestanding -c生成kernel.dis用ndisasm -b 32反汇编便于调试os-image.bin用cat $^拼接run目标执行qemu-system-i386 -fda另有clean清理产物。首个规则all: run使无参数make直接进入运行。第 14 课代码重组与 gdb 调试第 14 课14-checkpoint/README.md是一个里程碑停下来把代码整理进boot/、kernel/等目录多数文件是从前几课符号链接而来日后需要修改时应删除软链重建实体文件编写可扩展的 Makefile并引入qemu 的 gdb 远程调试。由于 OSX 的lldb与 ELF 格式不兼容Homebrew 的 gdb 也不行需要交叉编译一个i386-elf-gdb步骤与交叉编译器类似--program-prefixi386-elf-。make debug目标会构建带符号的kernel.elf得益于 gcc 的-g标志用strings kernel.elf可以看到其中的字符串并启动 qemu 的 gdb 连接。调试演示步骤极具教学价值b main ; 在 kernel.c:main() 设断点 continue ; 运行 OS next ; 单步 next ; 再单步屏幕上还没有 X print *video_memory ; 查看显存能看到 Landed in 32-bit Protected Mode 的 L print video_memory ; 确认指针地址正确 next ; 执行写入 print *video_memory ; X 已经出现在显存与屏幕上文档还顺带预告了内核架构取向鉴于实现难度预计是宏内核monolithic微内核设计留作未来的 15-b 可能。第 1517 课I/O 端口、VGA 驱动与屏幕滚动用 C 访问 I/O 端口第 15 课15-video-ports/README.md开始用 C 与设备通信核心是 drivers/ports.c 中的内联汇编——它的语法与手写汇编不同源/目标操作数顺序相反、操作数绑定变量的写法特殊。示例目标查询屏幕光标位置。具体做法是向端口0x3d4写入14请求光标位置的高字节写入15请求低字节查询结果保存在端口0x3d5实现见 drivers/ports.h 声明的端口读写函数。文档再次强调 gdb 的用途因为还无法在屏幕上打印 C 变量可对kernel.c:21设断点用print检查变量。最后把查到的光标位置用于写一个字符。kprint 屏幕驱动第 16 课16-video-driver/README.md正式实现字符串输出。驱动接口定义在 drivers/screen.h屏幕常量 三个公开函数——清屏clear_screen()与写字符串的kprint()/kprint_at()kernel print 的著名命名。drivers/screen.c 内部结构为两个 I/O 端口例程get_cursor_offset()/set_cursor_offset()复用第 15 课直接操作显存的print_char()三个行列与偏移互转的小工具函数。kprint_atcol/row传-1表示在当前光标位置打印。它先设定行列与偏移三个变量再遍历char*逐个调用print_char()print_char返回下一个光标的偏移并回传给循环。kprint只是kprint_at的薄封装。print_char同样允许-1坐标此时从硬件读光标位置。它还处理换行——把光标偏移定位到下一行的第 0 列。VGA 单元占 2 字节1 字节字符 1 字节属性。新版 kernel/kernel.c 终于能打印字符串并刻意测试了跨行定位、换行与越界写为下一课的滚动埋下伏笔。屏幕滚动第 17 课17-video-scroll/README.md在 drivers/screen.c 的print_char底部新增检查当当前偏移超过屏幕大小时滚动文本。滚动由新函数memory_copy承担——它是标准memcpy的简化版为避免命名空间冲突而改名实现位于 kernel/util.c。为直观演示滚动还实现了整数转文本的int_to_ascii标准itoa的快速实现注意多位整数会反序打印——这是有意为之后续课程再扩展。kernel.c每行先打印行号后续的kprint会迫使内核滚动。文档指出本课覆盖完了 os-dev.pdf 的全部内容之后转向 OSDev wiki 的 Meaty Skeleton 路线。第 1820 课IDT、中断、IRQ 与首个硬件中断中断描述符表IDT第 18 课18-interrupts/README.md深受 JamesM 教程启发建立 IDT。首先在 cpu/types.h 定义u8/u16/u32类型以解耦原始字节与 char/int并让既有文件改用新类型从此 C 头文件一律带 include guard。中断是内核必须处理的核心事项之一例子包括除零、越界、非法操作码、页错误等。IDT 的条目与 GDT 类似第 09 课但用 C 而非汇编实现cpu/idt.h 定义idt_gate必须配满 256 个条目哪怕为空否则 CPU 可能 panic与idt_register仅含内存地址与大小类似 GDT 寄存器以及供汇编访问的变量cpu/idt.c 为每个结构填入 handler然后执行lidt汇编指令装载。ISR中断服务例程cpu/isr.h 手动声明 32 个 ISRextern因为实现位于汇编cpu/isr.c 提供一次安装全部 ISR 并装载 IDT 的函数、错误消息列表与高层 handler默认 kprint 信息后停机可自定义cpu/interrupt.asm 是胶水层公共低层 ISR 模板负责保存/恢复状态并调用 C 代码随后是isr.h引用的具体 ISR 汇编函数。registers_t结构正是interrupt.asm中压栈的全部寄存器的镜像。最后把interrupt.asm纳入 Makefile让内核安装 ISR 并触发其中一个——注意 CPU 不会自动停机虽然部分中断后停机是好习惯。重映射 PIC打通 IRQ第 19 课19-interrupts-irqs/README.md解决一个关键冲突CPU 启动时PIC 把 IRQ 07 映射到 INT 0x80xF、IRQ 815 映射到 INT 0x700x77这与上一课编程的 ISR 031 冲突标准做法是把 IRQ重映射到 ISR 3247。PIC 通过 I/O 端口通信见第 15 课主 PIC 命令口0x20、数据口0x21从 PIC 命令口0xA0、数据口0xA1。重映射代码含掩码细节见 OSDev 的 PIC 页面。后续改动沿 C→汇编→C 的链路推进在isr.c中为 IRQ 添加 IDT 门在interrupt.asm的global声明末尾补充 IRQ 符号、在文件底部添加 IRQ handler跳转到新的irq_common_stubirq_common_stub与 ISR 版类似并声明[extern irq_handler]C 侧的irq_handler()向 PIC 发送 EOI中断结束命令并调用interrupt_handlers数组中的对应 handler数组与注册函数定义在isr.h顶部/isr.c中。本课kernel.c无改动成果留待下一课检验。时钟与键盘第一个实战 IRQ第 20 课20-interrupts-timer/README.md终于可以检验硬件中断。时钟在 cpu/timer.h 声明、cpu/timer.c 实现init_timer()——计算时钟频率并把字节发送到相应端口。同时修正int_to_ascii()使其正序打印数字需要实现reverse()与strlen()。回到kernel.c做两件事重新启用中断关键并初始化定时器中断make run即可看到时钟跳动。键盘drivers/keyboard.c 中有一个回调函数与一个配置中断回调的初始化函数接口见 drivers/keyboard.h。难点在于 PIC 返回的不是 ASCII 码而是键按下/键抬起的扫描码scancode因此文件中有一张长表把扫描码翻译成 ASCII当前仅覆盖美式键盘的简单子集。至此已具备构建简易 shell 的前提。第 2124 课shell、内存分配器与收尾修复简易 shell解析用户输入第 21 课21-shell/README.md做两件事清理代码 解析用户输入。代码清理随着功能增长把utils.c拆分为 libc/mem.c 与 libc/string.c各自带头文件这就是内核版 C 库 的开端新增irq_install()让内核一次调用即可初始化所有 IRQ类似isr_install()同时禁用timer_callback()中的kprint()以免刷屏把drivers/ports.*移入cpu/明显是 CPU 相关代码Makefile 增加更严格的 CFLAGS关闭-nostdlib/-nostdinc带来的隐患、把警告升级为错误并为此修正若干指针声明在 libc/function.h 加宏避免未使用参数的警告错误。键盘字符的流动按键时回调通过新数组取得 ASCII 码 → 追加到key_buffer缓冲区 → 同时打印到屏幕操作系统需要读取输入时调用libc/io.c:readline()。keyboard.c还解析退格键从缓冲区移除末字符并调用screen.c:kprint_backspace()从屏幕删除为此print_char()被改成打印退格时不推进偏移。响应输入键盘回调检测到换行便通知内核用户有输入。最后一块拼图是libc/string.c中的strcmp()两串相等返回 0。当用户输入 END 时内核停机——这就是从零实现的最基本 shell。文档鼓励继续扩展kernel.c解析更多命令未来接入文件系统后就能执行更多指令。kmalloc极简内核内存分配器第 22 课22-malloc/README.md在 libc/mem.c 中实现分配器。原理极简一个指向空闲内存的指针free_mem_addr不断增长。kmalloc(size, align, phys_addr)行为如下align 1时把地址对齐到 4K0x1000边界free_mem_addr 0xFFFFF000; free_mem_addr 0x1000若传入phys_addr则同时保存真实物理地址返回当前指针并free_mem_addr size递增。kernel.c保留全部 shell 代码仅演示新函数首个页面从硬编码的0x10000开始随后的kmalloc()依次产出与前一次相隔 4096 字节0x1000的地址。配套改动新增strings.c:hex_to_ascii()以便更友好地打印十六进制数把types.c改名为type.c保持语言一致性。修复 JamesM 教程的已知问题第 23 课23-fixes/README.md针对 OSDev wiki 记录的 JamesM 教程缺陷第 1822 课沿用了该教程逐项修复CFLAGS 错误编译.o时补上-ffreestanding删除此前禁用了 libgcc 的-nostdlib及下一项需要的-nostdinc。main()命名gcc 视main为特殊关键字故改为kernel_main()并同步修改boot/kernel_entry.asm为消除 cannot find entry symbol _start 警告在kernel_entry.asm中增加global _start;与_start:标签。自定义类型放弃u32等非标类型改用 C99 标准定宽类型uint32_tstdint.h在-ffreestanding下仍可用删除type.h中的自定义类型及多余的__asm__/__volatile__下划线。kmalloc对齐与类型大小参数改用size_tstddef.h凡计数且不可能为负的参数都应使用它未来将把kmalloc改造成真正的内存管理器。缺失函数mem*系列函数留待后续课程实现。中断处理器cli冗余IDT 条目的idt_gate_t标志已决定 handler 内是否开中断sti同样冗余iret从栈中恢复的 eflags 已含中断开关位即中断处理器自动恢复中断状态。registers_t中声称的esp改名useless它属于当前栈上下文而非被中断现场useresp改名esp。在cpu/interrupt.asm的call isr_handler前加cld最关键的是 ABI 修复公共 stub 在栈上构造registers实例再调用 C handler 会破坏 ABI栈归被调函数所有应改为传指针——registers_t r改为registers_t *t、字段访问由.改-汇编中调用isr_handler/irq_handler前push esp传指针、事后pop eax清理。timer 与 keyboard 两个回调同样改为接收registers_t指针。El Capitan 环境升级第 24 课24-el-capitan/README.md面向从课程一开始跟随、升级 macOS El Capitan 后 Makefile 无法编译的读者升级交叉编译器其余读者可跳过。流程与第 11 课基本一致brew upgradegcc 升到 5.0、xcode-select --install更新命令行工具、导出CC/usr/local/bin/gcc-5与LD/usr/local/bin/gcc-5随后按相同步骤重建 binutils 与交叉 gcc源码版本同样需要按需更新。完成后在本课目录执行make验证一切编译顺畅。总结与延伸路径从仓库根 README 的 Strategy 清单看os-tutorial 完成了从引导到输入输出的完整闭环无 GRUB 从零引导 → 32 位保护模式 → 汇编到 C → 中断处理 → 屏幕/键盘 → 迷你 libc →部分内存管理 → 简易 shell尚未完成的有正式的内存管理、文件系统、更完整的 shell、用户态、文本编辑器、多进程与调度以及可选的 BASIC 解释器、GUI 与网络。每一课的成果都以可编译、可运行、可调试为验收标准从 01-bootsector-barebones/boot_sect_simple.asm 的 7 行死循环到 23-fixes 中带严格 CFLAGS、标准类型与指针化中断处理的工程化代码读者可以清楚地看到一条由简到繁、不断重构的 OS 开发真实轨迹。结合根 README 顶部的弃坑警告建议把本仓库当作第一遍动手的体验教材它教会你 BIOS 引导协议、实模式寻址、模式切换、GDT/IDT、端口 I/O、中断驱动的设备交互等核心机制在形成整体概念后再转向更现代、权威的 OS 设计资料如 OSDev wiki 的 Meaty Skeleton 及其后续章节进行系统学习。动手方式上始终遵循仓库 README 给出的六步法按顺序读每章 README 首行预备概念与次行课时目标→ 通读正文 → 尝试自行编写 → 对照注释详尽的示例 → 主动破坏与复现验证。这样每个 515 分钟的课时最终都会沉淀为一个可运行的最小部件。赞分享示例工程【免费下载链接】os-tutorialHow to create an OS from scratch项目地址https://gitcode.com/gh_mirrors/os/os-tutorial点击查看免费下载相关推荐os-tutorial从零构建操作系统的终极指南os tutorial从零构建操作系统的终极指南 想要了解 操作系统开发 的奥秘吗os tutorial项目为你提供了一条从零开始构建操作系统的完整路径。这示例工程os-tutorial革命性教程手把手教你写自己的操作系统os tutorial革命性教程手把手教你写自己的操作系统 你是否曾经好奇操作系统是如何工作的想不想亲手打造一个属于自己的操作系统现在通过os tuto示例工程如何从零开始编写操作系统网卡驱动os-tutorial完整指南如何从零开始编写操作系统网卡驱动os tutorial完整指南 os tutorial是一个专注于从零开始构建操作系统的开源项目通过一系列循序渐进的教程和代示例工程上一篇蓝奏云直链解析终极指南3秒获取高速下载链接的完整方案下一篇Navicat Mac版无限试用期终极解决方案3种高效重置方法详解创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考