
简介哈工大操作系统实验李治军是一套面向计算机专业本科生的操作系统实践资源覆盖进程管理、内存管理、文件系统、设备管理、调度算法与死锁处理等核心实验场景适合用于课程配套实验、考研复习或自学巩固。压缩包共245个文件约21.89MB以c/h源码、o目标文件、makefile构建脚本和txt说明为主另有s汇编、a静态库及辅助文件便于还原实验工程并对照源码理解实现细节。资源已获得3447人学习下载。除哈工大实验资料外包内还汇集了其他高校实验、开源系统源码分析与讲座视频等补充材料便于多视角理解操作系统原理从经典同步问题到虚拟内存、文件系统模拟与磁盘调度可系统训练基于Linux内核的编程分析与问题排查能力适合希望深入掌握操作系统底层机制的学习者。 如果你在大学阶段学过操作系统一定听说过“哈工大操作系统实验”这个名号——尤其是李治军老师带的那门课几乎成了很多计算机专业学生又爱又恨的回忆。我当年做这套实验的时候事情还没现在这么方便没有那么多现成博客可以参考只能一边啃Linux 0.11源码一边在Bochs黑乎乎的窗口里对着内核日志发呆。后来帮学弟学妹带过几轮实验才发现大家踩的坑几乎一模一样。今天就把我自己从“跑不通”到“跑通关”的全过程、踩坑经验和解题思路整理出来希望能帮你少走几周弯路。这套实验的核心不是让你背几道题而是让你亲手改造一个真实可运行的操作系统内核。从开机启动代码bootsect.s开始到系统调用、进程调度、内存管理、文件系统十几个实验下来你会对教科书上的“操作系统”四个字建立完全不同的体感。这篇文章适合所有准备做或正在做哈工大操作系统实验的同学也适合那些想通过Lab方式入门内核开发的自学者。1. 实验全景李治军版操作系统实验到底在做什么1.1 一个实验串起整本《计算机操作系统》哈工大这套实验最厉害的地方是它真的做到了“实验跟着课程走”。上课讲到启动流程实验就让你去改bootsect.s让自己打印一段字幕讲到系统调用实验就让你增加两个自己的系统调用讲到进程调度实验就逼你去改调度算法。它不是零散的编程题而是有一条清晰的路线从CPU加电后的第一条指令一路做到Shell能跑起来。我按自己当年的体验把整套实验的大致内容整理成了一个表方便你对照课程章节看实验模块核心对象对应知识点操作系统引导bootsect.s、setup.s实模式、中断、磁盘加载系统调用int 0x80、sys_call_table用户态/内核态切换进程调度schedule()、task_struct时间片、优先级、上下文切换内核级线程与同步信号量、临界区PV操作、生产者消费者内存管理page目录、缺页中断虚拟内存、地址映射文件系统inode、buffer cache文件读写、高速缓存进程间通信管道、消息队列内核同步与阻塞唤醒Shell与终端终端驱动、exec命令解释、程序的加载执行学习建议是不要把这十几个Lab当成8个孤立的任务而是当成“一起把0.11内核从无到有加固起来”的过程。每做完一个Lab你对整个系统的理解都会往上抬一层。我当时做完前两个实验后才真正明白“操作系统不是跑在电脑里的神秘黑盒而是一堆结构体的组合和函数调用”。1.2 为什么选Linux 0.11和Bochs很多第一次接触这套实验的人都很疑惑为什么不直接用现代Linux而要用一个1991年的老内核我的理解是Linux 0.11大概是人类历史上可读性最高的现代操作系统内核之一。它总代码量很小接近完整的系统调用、进程管理、文件系统全都有但又不至于庞大到让人找不到入口。你在课堂上学的各种理论比如进程控制块、页表、inode在这个版本里都能在几百行代码内找到对应的完整实现。配合的模拟器是Bochs。它不是虚拟机而是一个纯软件模拟的x86环境可以逐条指令执行、下断点、查看寄存器和内存。这样即使内核panic了也不会把宿主机搞崩溃。更重要的是Bochs高度可控能看到实模式和保护模式切换的每个瞬间。这一点比在真机或QEMU里调试要方便得多。2. 环境搭建先让内核在Bochs里转起来2.1 需要的软件与实验包做这套实验你不需要多高配的电脑但环境最好是一次性准备齐全。核心软件有四个Linux环境建议Ubuntu/Debian方便用apt装包、Bochs模拟器、一个老版本GCC工具链或实验包自带的编译器、以及哈工大实验的完整代码包。网上能找到各种版本的实验包老一些的有linux-0.11-lab新一些的还有带注释和测试脚本的改进版。我的建议是优先选择带“测试程序”的版本因为实验最后提交时老师或评测脚本会运行这些测试程序来检查你的内核是否真的实现了预期功能。实验包内部通常是一个完整的Linux 0.11源码树外加Makefile、Bochs配置文件和用于验证的评测程序。2.2 从make到Bochs跑通的完整步骤我印象最深的是第一次跑通时那种“好像什么也没干但内核起来了”的兴奋感。整体流程其实不复杂# 1. 安装依赖以Ubuntu为例 sudo apt-get update sudo apt-get install build-essential gcc-multilib bochs bochs-x bochsdbg nasm # 2. 解压实验包并进入内核目录 tar xzf linux-0-11-lab.tar.gz cd linux-0.11 # 3. 直接编译 make # 4. 用Bochs启动 make start但“直接编译”四个字背后往往藏着不少状况。由于实验源码是给上世纪90年代的GCC准备的现代GCC即使加-m32也可能报一堆警告甚至错误。有的实验包会自带一个tools目录里面有配套的交叉编译工具这种情况下你需要把交叉工具链的路径加到PATH里再重新make clean make。如果你拿到的是一个老实验包在64位系统上编译时最常见的问题是as汇编器不认识旧的语法或者链接时出现“cpu 32-bit”相关的段错误。这时优先看看README或Makefile注释不要一上来就乱改源码。Bochs启动还需要一个配置文件实验包一般已经准备好了通常叫bochsrc.bxrc或bochs.conf。核心配置项我列几个方便你排查问题注意floppya: 1_44bootimage, statusinserted是用来挂载内核启动镜像的如果你改了镜像路径或名字这个配置必须同步改否则一启动就提示“Boot failed”。启动后你会看到一个黑底白字的终端接着出现引导菜单然后内核开始滚动日志。如果走到这一步恭喜你环境已经通了后面的实验才有意义。2.3 调试环境准备不用printk也能断点很多同学一味靠printk打日志排查问题其实Bochs自带的调试器比想象中好用得多。安装bochsdbg后你可以用带调试功能的窗口启动系统bochsdbg -q -f bochsrc.bxrc启动后会出现一个类似GDB的调试终端里面比较常用的命令包括b 0x7c00 # 在内存地址0x7c00处下断点引导扇区入口 c # 继续运行 regs # 查看当前寄存器 sreg # 查看段寄存器 xp /10wx 0x0 # 查看物理内存内容 u /20 0x0 # 反编译对应地址的代码这套调试器对理解“引导扇区被加载到0x7c00”这类的细节特别有用。我建议你在做第一个“引导实验”时就用Bochs断点把整个启动过程看一遍比我当时干读源码高效得多。3. 核心实验拆解把最难啃的骨头先啃下来3.1 系统调用实验真正读懂用户态到内核态的路径第一个真正卡住很多人的实验是“添加系统调用”。实验要求你在Linux 0.11里实现两个新的系统调用比如iam()和whoami()然后写一个用户程序调用它们验证结果。听起来简单但里面藏着一整条链路。系统调用的本质是用户程序通过int 0x80中断陷入内核内核根据eax寄存器里的“系统调用号”去sys_call_table里查表找到对应的内核函数并执行。所以实现一个系统调用至少要做四件事在include/linux/sys.h中声明新函数并把它加进sys_call_table数组。在include/unistd.h中增加系统调用号并给用户态提供_syscallN宏定义。在内核的lib/sys.c或其他合适位置实现真正的系统调用函数。重新编译内核在用户态写程序调用验证。这四步每一步都有细节。比如系统调用号的编号不能冲突_syscall2(int, iam, const char*, name)这个宏的第二个参数是返回值类型第三个是函数名后面是参数类型和参数名写错一个编译就崩。还有一个特别容易踩的坑是返回值和错误码需要放在eax里如果你在内核函数里返回的是一个负数用户态拿到的可能是一个很大的无符号数这是因为Linux 0.11时代对错误处理还非常原始不像现代内核会用ERR_PTR包装。我当时把这条链路画在一张纸上每做一步就标记一次后来面试被问到系统调用流程时脑子里直接浮现出这张图。这也是这套实验的额外价值它不是应试而是真的建立“系统感”。3.2 进程调度实验修改schedule()的关键思路第二个必须啃的硬骨头是“进程调度实验”。它会要求你修改Linux 0.11的kernel/sched.c里的schedule()函数实现基于优先级或时间片的调度策略。你要理解几个关键变量current指针、task[]任务数组、need_resched标志、counter时间片计数器。如果你直接打开schedule()函数会看到它是遍历task[]选一个counter最大的进程来切换。作业要求通常有两种方向一是修改这个遍历逻辑比如让进程被选中后递减一个静态优先级二是要求调整counter的赋值规则达到类似多级反馈队列的效果。做之前我强烈建议你先画一个“就绪队列–运行–阻塞–再次就绪”的状态图把每个进程在任何时刻处于什么状态标出来然后再回到代码里找对应的状态切换点。在这个实验里我见过最多的问题是只改调度算法却忘了在进程时间片用完时正确设置need_resched结果进程永远不切换系统看起来像死锁了。另一个问题是修改task_struct增加新字段后没有在所有初始化和复制进程的地方同步处理导致新字段是随机值调度逻辑直接乱掉。解决办法是全局搜索task_struct的分配和初始化代码把所有新增字段都显式清零或赋初值。3.3 内存管理与文件系统两个硬仗的破局思路做完前三个实验你会觉得自己已经是个“内核开发者”了然后就会被内存管理和文件系统实验教做人。内存管理实验通常要求完善缺页中断处理、实现进程地址空间复制或者改一版简化的写时复制机制文件系统实验通常涉及buffer cache或inode操作。我的经验是这两个实验的核心都不是写代码而是“看懂原来的实现”。内存管理最好先把x86分页机制捋清楚目录表、页表、页目录项、页表项、缺页异常编号。Linux 0.11里的地址转换比现代内核简单很多但你要弄明白copy_page_tables()为什么能把父进程的地址空间映射给子进程还要弄明白缺页时怎么从cr2寄存器拿到出错地址。文件系统实验则要看三样东西super_block、inode、buffer_head。最好先搞懂“一个文件从open到read到底经过了哪些缓冲层”再去看代码。不要一上来就试图改read/write系统调用那样容易把自己绕晕。我当时用了一个笨办法在每个关键函数入口加一行printk打印当前函数名和参数然后跑一个简单的测试用对照日志梳理调用路径。这个方法很土但对理解文件和缓存的关系特别有效。提示做内存和文件系统实验时Bochs的单步执行和内存查看命令是你的救星。缺页异常处理时先确认cr2和页表内容是否符合预期再去看代码逻辑能省去大量瞎猜时间。4. 避坑指南我踩过的那些坑你别再踩了4.1 编译与版本问题这套实验最大的敌人不是操作系统原理而是工具链。我身边十个出问题的同学里至少七个卡在编译阶段。最常见的几类问题我整理成了表格现象根源解决办法as: Error: operand type mismatch for mov新版GNU汇编不兼容旧语法使用实验包自带工具链或调整汇编写法链接时提示cannot find -lc缺少32位库安装gcc-multilib和libc6-dev-i386编译全是warning但能生成镜像新版GCC太“多嘴”不用管只要没error就继续运行时Kernel panic: VFS: Unable to mount root fs镜像制作失败检查是否执行过make clean后重新make确认根文件系统镜像被正确挂载遇到编译问题务必先看一眼有没有“实验包要求的GCC版本”说明。有的环境下只需要make menuconfig里改一下优化等级有的则必须用高版本的独立工具链。不要一上来就“重新安装GCC全家桶”很可能把系统环境搞乱。4.2 Bochs运行与调试问题成功编译之后你会在Bochs里遇到第二类问题。最典型的有三个黑屏没反应、不断重启、以及“floppy drive not found”。黑屏多半是启动镜像没加载上去bochsrc.bxrc里确认floppya指向的镜像路径是否正确。不断重启则通常是保护模式切换或中断表出了问题尤其在你改完setup.s或head.s之后要用Bochs调试器在模式切换处下断点看有没有触发异常。还有一个很容易忽略的地方是Bochs默认没有启用dbg模式导致你输不进调试命令这时要确认你自己启动的是bochsdbg而不是bochs。另外如果你在Windows上做实验尽量不要用新版Bochs默认的SDL显示方式渲染容易卡。把显示模式换成x或term会流畅很多——当然这也看你用的平台。4.3 常见内核panic与排查套路内核panic本身不可怕可怕的是你不会利用它定位问题。Linux 0.11的panic信息一般会打印出“函数名行号”并停在一个死循环里。看到panic先不要慌去查它是在哪个文件哪个行号触发的。很多问题本质是空指针引用、数组越界、或者某个链表被破坏printk日志反而是最快的线索。我自己常用的排查套路是这样先在可疑函数入口和出口加printk打印关键参数与返回值然后跑用户态测试程序让内核崩溃最后从最后一个printk输出往回倒推。这样一种二分法定位比满屏找panic快得多。等你定位到具体某个结构体字段不对再去对照源码看有没有初始化遗漏。重要心得内核代码里千万不要图省事用未经初始化的变量。用户态程序里面那样写也许能跑内核态里几次都是直接死机。拿到新增字段后第一时间把它在task_struct的初始化和复制路径里都赋值能让你少掉很多头发。5. 写在最后这个实验到底给了我什么整套实验做下来最明显的改变不是“我会写内核了”而是“我不怕读源码了”。以前看现代Linux源码面对几百万行文件只能望洋兴叹但Linux 0.11让我知道再复杂的系统也是由一个个函数、一个个结构体拼装起来的。你每改一个调度逻辑、每加一个系统调用都是在和这个系统对话。等你把最后一个炸掉的Shell调通再回头看教科书里的概念全都能在脑子里自动对应到具体的代码逻辑。如果一定要给准备入坑的同学说句实用建议我的建议是做实验前先花一小时看Bochs调试命令做实验中每改一次代码就重新做一次最小验证做实验后把每个Lab的改动整理成一份“为什么这样改”的笔记。这套方法也许不能让你跑得最快但一定让你理解得最深。最后再分享一个小技巧如果你实在调不出来试着把printk加上一个独一无二的前缀比如zzy_debug:这样在内核日志里一眼就能看到你打的调试信息比满屏刷屏舒服太多。祝你在Linux 0.11的世界里玩得开心。本文还有配套的精品资源点击获取