尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

操作系统实验zip全解析:从环境搭建到进程调度与内存管理

操作系统实验zip全解析:从环境搭建到进程调度与内存管理 简介华中科技大学操作系统实验配套资源包聚焦系统调用实现与内核编程面向操作系统课程本科生以及希望深入理解用户态与内核态切换机制的中高级开发者。资源对应HUST实验一中的系统调用部分涵盖系统调用接口、进程管理、文件I/O、错误处理等关键知识点可帮助学习者通过实际编码掌握操作系统底层运作原理完成从理论知识到系统级编程实践的过渡。压缩包共41个文件大小约63KB主体为17个C语言头文件与15个C源码文件同时辅以3个汇编文件、2个链接脚本、Makefile及配置文件构成一套结构完整、便于直接编译调试的工程框架。目前已有170人学习该资源适合配合实验指导书或课堂内容深入练习。借助spike模拟器相关接口与底层汇编读者可跟踪系统调用从用户态陷入内核态的完整路径理解sys_write、sys_open等接口用法结合README与预览图快速定位关键代码用GDB分析调用栈与返回值逐步提升系统级编程和问题排查能力为后续实验打下基础。1. 华中科技大学操作系统实验.zip 到底装了什么华中科技大学操作系统实验.zip 不是一个装好就能跑的软件包而是一组 C 工程、实验指导书和测试脚本的集合。解压后通常能看到进程调度、内存管理和文件系统三块实验目录对应计算机操作系统课程里最核心的章节。把模板代码改成能通过验收的实现才是这门课真正的时间开销。很多人把这类实验当算法题写其实它更接近小型系统开发换一个环境、改一个输出格式甚至一个边界条件都可能让本地跑通的代码在验收时报错。我处理这类实验包的流程是先校验压缩包、搭好编译环境再把调度、换页、文件系统三个实验逐一讲透最后从评分脚本的视角做提交前的检查。文章里的命令都按 Linux 环境给出只依赖 gcc、make、gdb 和常见命令行工具不需要特殊硬件或付费软件。上课的同学可以按章节顺序跟做做实验答疑的助教也能直接复用中间的检查步骤。2. 先把操作系统实验 zip 解压跑通环境、目录与最小构建2.1 解压前先校验压缩包完整性与中文文件名拿到华中科技大学操作系统实验.zip 之后第一步不是解压而是算哈希。课程页面或实验群公告里通常会给出文件的 SHA-256 校验值用sha256sum对一下。不一致说明下载过程中文件被截断或改写这种包解到一半就会报错直接重新下载比修复省事。如果解压时出现error: unexpected end of file或error read zip archive多数是压缩包不完整少数是文件头损坏。可以先用zip -FF尝试修复把能抢救的文件导出来再决定要不要重新下载。注意zip -FF修复的是压缩包内部结构对真正的下载残缺不一定有效修完一定要跑unzip -t做完整性测试。# 比对课程页面公布的校验值确认下载没有截断 sha256sum 华中科技大学操作系统实验.zip # 遇到 error read zip archive 先尝试修复再决定重下 zip -FF 华中科技大学操作系统实验.zip --out fixed.zip unzip -t fixed.zip # 中文文件名乱码时按 GBK 解压 unzip -O GBK 华中科技大学操作系统实验.zip -d labsha256sum输出哈希值和页面比对即可zip -FF会扫描损坏包的剩余结构并写出fixed.zip-t模式逐个测试包内文件的 CRC。-O GBK告诉 unzip 包内文件名用的是 GBK 编码而不是默认的 UTF-8专门解决 Windows 老式压缩工具打包导致的中文乱码。装了 p7zip 的话用7z x也能直接解不需要关心编码参数。关于密码实验包偶尔会加密密码一般在课程通知或实验课 PPT 里用7z x 包名.zip -p密码解压。如果密码丢了直接找助教要比在网上找 zip 密码恢复工具快得多。拿到加密包后我一般会重打包一次成不带密码的 zip省得每次解压都输入一遍。现象常见原因处理方式error: unexpected end of file下载中断或文件未传完重新下载zip -FF 修复后 unzip -t 验证解压后中文文件名乱码包内文件名是 GBK 编码unzip -O GBK 或 unar 自动识别提示输入密码实验包被加密从课程通知找密码不用破解工具unsupported compression method压缩工具用了少见算法换 7-Zip 或 unar 解压2.2 实验包目录结构与知识点对应解压完成后先tree -L 2看一眼整体结构不要把 zip 里的内容直接拖到中文目录或带空格的路径下编译。常见做法是每个 lab 一个目录底下有src/、include/、Makefile和report/顶层放实验指导书 PDF 和 README。目录结构这样组织不是为了好看评分脚本会按固定路径找 Makefile 和可执行文件乱改路径会让提交在平台侧直接编译失败。目录/文件常见内容对应知识点lab1PCB 结构体、调度算法模板进程管理、处理机调度lab2页面置换、页表地址转换模拟内存管理、虚拟存储lab3文件系统模拟或内核模块文件系统、设备管理指导书.pdf实验要求、验收用例、输出格式评分口径Makefile编译目标构建环境与依赖对照这张表可以在写代码之前就定位清楚实验要考哪个章节遇到分歧也知道去翻哪章笔记。比如 lab1 改的是调度算法就回去翻进程管理那章的调度笔记lab2 改的是缺页计数就回去看虚拟存储。这样做的好处是当输出和标准答案对不上时能快速判断是算法实现问题还是判定口径问题。2.3 用最小 Makefile 过一遍编译环境实验代码是 C 写的通常必须在 Linux 操作系统环境里编译。我习惯用 Ubuntu 20.04 或 22.04 LTS装好 build-essential 就够跑大部分模拟实验。如果实验涉及内核模块还需要与当前内核版本对应的头文件如果实验要求启动一个小型操作系统镜像才需要 qemu单纯写 C 模拟的实验用不上。sudo apt update sudo apt install -y build-essential gdb valgrind # 涉及内核模块的实验补装内核头文件 sudo apt install -y linux-headers-$(uname -r)build-essential 提供 gcc 和 makegdb 用来断点调试调度实验里的死循环valgrind 用来检测文件系统实验的内存泄漏。$(uname -r)会自动展开成当前内核版本号内核头文件必须和运行内核严格对应版本不一致时 insmod 会报invalid module format。验证环境是否就绪最快的办法是在第一个 lab 目录下直接跑make。如果课程提供的 Makefile 能过说明环境没问题如果过不了先别动代码把报错信息拿去搜索。下面这个最小 Makefile 是我处理任何实验包都先放进去的CC : gcc CFLAGS : -Wall -Wextra -g -O0 -stdgnu11 TARGET : scheduler SRCS : $(wildcard src/*.c) $(TARGET): $(SRCS) $(CC) $(CFLAGS) $^ -o $ -lpthread clean: rm -f $(TARGET) *.o test: $(TARGET) ./$(TARGET) tests/case1.in-g保留调试信息-O0关闭优化避免编译器重排影响打印顺序-Wall -Wextra把潜在问题暴露成 warning写操作系统实验时不要把 warning 当噪音很多未初始化变量就是在这一步被发现的。$^表示全部依赖文件-lpthread链接 POSIX 线程库同步实验必须带。Windows 下编辑过的 Makefile 会带 CRLF 换行make 报\r: command not found时用sed -i s/\r$// Makefile src/*.c一次性清理。如果指导书要求-m32编译 32 位程序还需要补装 gcc-multilib否则链接阶段报一堆 undefined reference。提示把自己用的依赖写进 README 或在报告中注明评分环境缺包时助教能快速判断这是环境问题而不是代码问题。3. 操作系统实验的进程调度从 PCB 到能对齐的甘特图3.1 调度算法实验的数据结构与主循环进程调度实验通常提供 PCB 结构体和一组进程的到达时间、服务时间要求实现先来先服务 FCFS、短作业优先 SJF、时间片轮转 RR、优先级调度中的若干种输出甘特图并计算平均周转时间和平均带权周转时间。平均周转时间是所有进程的完成时间减到达时间之和除以进程数平均带权周转时间是周转时间除以服务时间这两个数必须和指导书给出的标准答案一致不一致就说明某个进程的完成时间算错了。新手最常见的错误是把调度写成排序后依次执行。这在非抢占的 FCFS 下成立但 RR 需要按时间片切分执行区间可抢占的 SJF 还要在新进程到达后重新选择最短剩余时间的进程。正确做法是维护一个按 tick 驱动的主循环每个 tick 结束检查是否有新进程到达并把它放入就绪队列。typedef struct { int pid; int arrive; /* 到达时间 */ int burst; /* 服务时间 */ int remain; /* 剩余时间RR 使用 */ int finish; /* 完成时间 */ } pcb_t; /* 时间片轮转主循环每次取出队首进程执行一个时间片 */ int tick 0, rest n; while (rest 0) { pcb_t *cur dequeue(ready); int slice cur-remain QUANTUM ? cur-remain : QUANTUM; cur-remain - slice; printf([%03d] pid%d running %d ticks\n, tick, cur-pid, slice); tick slice; if (cur-remain 0) enqueue(ready, cur); /* 时间片用完但没结束重新排队 */ else { cur-finish tick; rest--; } /* 检查是否有新进程到达按到达时间入队 */ for (int i 0; i n; i) if (procs[i].arrive tick) enqueue(ready, procs[i]); }这个循环有三个要点。第一tick 的推进单位必须和指导书一致有的实验把时间片定义为 1有的定义为 2混用会让整个甘特图错位。第二上面用slice一次执行多个 tick如果时间片大于 1中途到达的进程会被漏掉更稳妥的做法是把执行粒度拆成 1 个 tick每 tick 都扫描一次到达队列。第三printf 的格式进程号用%03d还是%d、行首有没有[tick]前缀都要以指导书样例为准评分脚本经常是逐字符比对输出。调度算法就绪队列组织实现要点常见扣分点FCFSFIFO 队列按到达时间依次执行输入未按到达时间排序SJF非抢占每次选最短剩余服务时间插队逻辑要正确抢占后没有重新选择RRFIFO 队列 时间片剩余时间为 0 才算结束时间片耗尽与到达顺序颠倒优先级调度按优先级选队首同优先级按 FCFS 兜底比较符号用反3.2 同步与互斥信号量用错顺序会死锁同步实验一般是生产者-消费者、读者-写者或哲学家就餐要求在 Linux 下用 POSIX 信号量或 pthread 互斥锁实现。这类实验的评分重点不是算法复杂度而是会不会死锁、会不会丢唤醒。以生产者-消费者为例信号量初始化三个empty 表示空槽位数full 表示已占用的槽位数mutex 保护缓冲区这个临界区。#include semaphore.h #include pthread.h sem_t empty, full, mutex; int main(void) { sem_init(empty, 0, BUFFER_SIZE); sem_init(full, 0, 0); sem_init(mutex, 0, 1); /* 生产者临界区 */ sem_wait(empty); /* 先等空槽位 */ sem_wait(mutex); /* 再进临界区 */ put(item); sem_post(mutex); sem_post(full); return 0; }这段代码有两个高频错误。第一sem_init的第二个参数是 pshared必须传 0表示信号量在线程间共享传非 0 表示进程间共享和 pthread 的使用场景不匹配行为会不符合预期。第二sem_wait的顺序不能颠倒必须先等 empty/full 再加锁反过来会出现持有互斥锁等待空槽位而消费者又拿不到锁去释放空槽位的死锁。验证死锁最简单的方式是timeout 5 ./producer_consumer脚本返回值是 124 说明程序挂死没能正常结束。读者-写者实验还有一个常见误用把读者优先实现成读者永远插队导致写者饥饿。正确做法是维护一个 readcount第一个读者进入时等待写者信号量最后一个读者离开时释放它。如果指导书要求管程版本可以用 pthread 条件变量实现逻辑和教材上管程的伪代码一一对应验收时按条件变量的唤醒方式解释即可。3.3 用日志和 diff 验证调度输出调度实验的验收动作通常是喂一个输入把输出和标准输出做 diff。因此本地验证要准备至少三个用例全部同时到达、顺序到达、到达时间和服务时间都比较极端的各一份。用手算或表格算出每个用例的甘特图再和程序输出对比。diff -bB可以忽略行尾空格和空行但评分脚本不一定有这种容错最好把输出格式彻底对齐而不是依赖 diff 参数。# 生成实际输出再和标准答案比对 ./scheduler tests/case1.in /tmp/case1.actual diff -bB tests/case1.expected /tmp/case1.actual # 程序无响应时用 timeout 兜底退出码 124 说明挂死 timeout 5 ./scheduler tests/case2.in echo exit$?diff -bB的-b忽略行尾空白-B忽略空行建议本地再跑一次不带参数的严格 diff提前暴露格式问题。timeout给程序硬性执行时限调度实验正常运行时间应该在小数秒以内超过 5 秒基本可以断定有死循环。如果怀疑是死锁而不是死循环用gdb -p $(pgrep scheduler)附加到进程执行thread apply all bt看每个线程停在哪一行比在代码里乱插 printf 快得多。4. 操作系统实验的内存管理页面置换算法与缺页计数口径4.1 四种置换算法在数据结构上的差异内存管理实验通常给出一段页面访问串和若干物理块要求实现 FIFO、LRU、OPT、CLOCK 中的几种输出每次访问的命中或缺页情况。四种算法在代码层面只差一个数据结构FIFO 用队列置换队首的页OPT 需要预知整个访问串置换未来最晚才被访问的页LRU 维护每页的最后使用时间置换最久没用的页CLOCK 是环形队列加参考位的折中用一位参考位近似最近使用过这个信息。#define NFRAMES 4 int page[NFRAMES]; /* 物理块里放的是哪个页号 */ int last_used[NFRAMES]; /* 该页最近一次访问的 tick */ int faults 0; void access_page(int pno, int tick) { for (int i 0; i NFRAMES; i) { if (page[i] pno) { /* 命中 */ last_used[i] tick; return; } } /* 缺页找一个 last_used 最小的块淘汰 */ int victim 0; for (int i 1; i NFRAMES; i) if (last_used[i] last_used[victim]) victim i; page[victim] pno; last_used[victim] tick; faults; }这段代码把 LRU 实现成缺页时线性扫描找最旧。命中时只更新时间戳不产生淘汰缺页时选last_used最小的物理块替换。逻辑简单时间复杂度 O(frames)而实验里 frames 通常是个位数完全够用。唯一要注意的是 tick 必须严格单调递增如果用time()取墙钟秒同一秒内的多次访问会拿到相同的时间戳结果就不稳定。CLOCK 的坑在参考位三个字上每次访问命中要把它对应的参考位置 1置换时从环形指针当前位置扫描参考位为 1 的清零并继续碰到 0 才淘汰。常见错误是忘了访问时置 1CLOCK 退化成 FIFO或者指针在扫描后没有停在被淘汰页的下一个位置导致同一页被反复扫描。4.2 缺页计数口径一个决定分数的小数点同一份访问串、同一个算法缺页次数会因为指导书写没写清初始状态差出几个数。以操作系统知识点里那个经典访问串 7,0,1,2,0,3,0,4,2,3,0,3,2,1,2,0,1,7,0,1 为例三个物理块下 FIFO 缺页 15 次LRU 缺页 12 次OPT 缺页 9 次这是物理块初始为空前三次访问也算缺页口径下的参考答案。如果换成预填前三个页号三个算法的缺页数都少 3单看每个都对但和标准答案对不上。算法3 个物理块缺页次数4 个物理块缺页次数说明FIFO15184 块比 3 块多出现 Belady 异常LRU1210需要时间戳或栈结构OPT98需要完整访问串只能离线仿真这张表解释了一个高频考点FIFO 不是栈算法可能出现 Belady 异常LRU 和 OPT 是栈算法增加物理块不会让缺页次数变多。实验里如果指导书给了标准输出直接 diff 对齐如果没给就以初始全空为口径并在实验报告的说明部分写清楚你采用的口径避免助教按别的口径复核时产生分歧。提示缺页次数差 3 次这种问题原因几乎都是初始口径不一致。改代码前先重新读一遍指导书里的初始状态描述比调试省时间。4.3 地址转换与页表查询的位运算实现如果实验还包含逻辑地址到物理地址的转换核心公式是页号等于逻辑地址整除页大小页内偏移等于逻辑地址模页大小。因为页大小在实验里几乎总是 2 的幂位运算比除法和取模更快也更接近真实硬件的做法。页大小为 4KB 时低 12 位是偏移其余高位就是页号。#define PAGE_SHIFT 12 /* 4KB 页 */ #define PAGE_MASK ((1u PAGE_SHIFT) - 1) unsigned long vaddr 0x12345; unsigned long pno vaddr PAGE_SHIFT; unsigned long offset vaddr PAGE_MASK; unsigned int frame page_table[pno] ~PAGE_MASK; /* 低 12 位是标志位 */ unsigned long paddr (frame PAGE_SHIFT) | offset;PAGE_SHIFT是页大小以 2 为底的对数4KB 对应 12PAGE_MASK是低 12 位全 1 的掩码vaddr PAGE_MASK得到页内偏移。页表项的高位是帧号低位通常是有效位、脏位、访问位这些标志所以取帧号时先用 ~PAGE_MASK把低 12 位清掉再左移拼回物理地址。调试时建议每次转换后打印一行vaddr - pno/offset - paddr拿一条已知答案的地址手工推一遍能过滤掉大部分位运算错误。5. 操作系统实验的文件系统与系统调用用脚本逆向验收5.1 模拟文件系统的核心结构与空闲块分配文件系统模拟实验要求用内存或磁盘文件模拟块设备实现 mkfs、文件创建删除、目录项管理和空闲块分配。这类实验的代码量比调度大得多关键是把位图、inode、目录项拆开写不要在 main 里堆逻辑。常见结构是三个全局对象超级块记录总块数和剩余块数位图用char bitmap[BLOCK_COUNT/8]记录每个块是否被占用inode 数组保存文件名、长度和块指针。#define BLOCK_COUNT 256 unsigned char bitmap[BLOCK_COUNT / 8]; /* 分配一个空闲块成功返回块号失败返回 -1 */ int alloc_block(void) { for (int i 0; i BLOCK_COUNT; i) { if ((bitmap[i 3] (1 (i 7))) 0) { bitmap[i 3] | (1 (i 7)); return i; } } return -1; /* 磁盘满 */ } void free_block(int b) { bitmap[b 3] ~(1 (b 7)); }i 3定位字节下标1 (i 7)定位字节内的位分配时置 1释放时清 0。两个隐蔽 bug 最常见一是把 inode 里的逻辑块号直接当下标用没有先转成物理块号二是按文件字节数分配块数4097 字节的文件如果按 4097 除以 4096 向下取整会漏一块正确写法是(size BLOCK_SIZE - 1) / BLOCK_SIZE向上取整。删除文件的边界条件也常被验收脚本打到重复删除要返回错误而不是把位图再清一次删除目录前要检查它是不是空目录。这两条在指导书的注意事项里经常出现但模板代码大多只留了函数签名需要自己补判断。5.2 系统调用实验用内核模块绕开全量编译如果实验要求验证系统调用或观察内核行为不同学校的做法差别很大。华中科技大学操作系统实验里常见的套路是验证某个调度或内存行为而不是真的改内核源码后重编整个 Linux因为全量编译在普通笔记本上要一两个小时评分环境根本等不起。我的做法是用可加载内核模块承载实验逻辑把结果暴露到/proc下评分脚本读文件就能完成验收。obj-m : oslab.o KDIR : /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean#include linux/module.h #include linux/proc_fs.h #include linux/seq_file.h static struct proc_dir_entry *oslab_entry; static int oslab_show(struct seq_file *m, void *v) { seq_printf(m, oslab: running\n); return 0; } static int oslab_open(struct inode *inode, struct file *file) { return single_open(file, oslab_show, NULL); } static const struct proc_ops oslab_fops { .proc_open oslab_open, .proc_read seq_read, }; static int __init oslab_init(void) { oslab_entry proc_create(oslab, 0444, NULL, oslab_fops); return oslab_entry ? 0 : -ENOMEM; } static void __exit oslab_exit(void) { proc_remove(oslab_entry); } module_init(oslab_init); module_exit(oslab_exit); MODULE_LICENSE(GPL);Makefile 里的obj-m声明模块目标KDIR指向当前内核的构建树前提是装好了对应版本的 linux-headers。模块代码在/proc下创建名为 oslab 的文件seq_printf写入要展示的实验结果用户态cat /proc/oslab就能读到。加载验证的完整命令是make然后sudo insmod oslab.ko查看cat /proc/oslab结束用sudo rmmod oslab内核日志用dmesg | tail看。要注意proc_ops是 5.6 之后内核的写法更老的实验环境用file_operations编译报错时先查这个版本差异。实现路线耗时适用场景主要风险重编内核30 分钟到数小时必须真正改系统调用表环境配置复杂一次失败成本高内核模块分钟级验证调度/内存行为proc_ops 与内核版本相关用户态模拟分钟级纯算法模拟不涉及真实内核行为5.3 自动化测试编译、运行、比对输出文件系统实验的验收用例通常是一串命令比如创建目录、写文件、读文件、删除最后检查目录内容。手工敲一遍太慢而且容易漏边界。我一般写一个验收脚本循环跑所有测试输入和预期输出比对任何一条失败都立即打出来#!/usr/bin/env bash set -euo pipefail make -s for input in tests/*.in; do expected${input%.in}.out ./fs_mgr $input /tmp/fs.actual if diff -q $expected /tmp/fs.actual /dev/null; then echo PASS $(basename $input) else echo FAIL $(basename $input) diff $expected /tmp/fs.actual | head -20 fi done # 内存检查有泄漏就让退出码非 0脚本据此报错 valgrind --leak-checkfull --error-exitcode1 ./fs_mgr tests/smoke.inset -euo pipefail让脚本在未定义变量或管道命令失败时立即退出避免看着全绿其实后面的用例根本没跑的假象。diff -q只报告文件是否相同不刷屏失败时再完整 diff 前 20 行。valgrind --error-exitcode1把内存错误转换成非零退出码这样内存泄漏也能被脚本自动捕获适合在提交前当回归测试跑一遍。6. 提交操作系统实验前按评分脚本做一轮逆向验收实验课的评分脚本不会看代码风格它拿到 zip 之后的动作基本固定解压、找 Makefile、make、跑用例、diff 输出。逆向验收就是把自己提交的东西当成陌生人的代码站在这个流水线上再过一遍。这一步花 10 分钟通常能挡掉一半以上的格式扣分。而且操作系统期末复习里最硬的三块——进程管理、内存管理、文件系统——正好被你用实验完整过了一遍这时候对照考纲把每个实验的输入输出口径复习一遍效率比单独背知识点高很多。6.1 让提交包在全新环境里一键通过三个检查点按优先级排列。第一是包名和目录层级提交包命名为 学号_姓名_labX.zip解压后第一层是一个 labX 目录而不是散落一堆文件用unzip -l 包名.zip | head提前确认。第二是产物清理.o、可执行文件和临时测试输出都不要进包提交前挨个目录执行find . -name *.o -delete make clean用了 git 的话git status --ignored能一眼看出哪些文件不会被追踪。第三是默认目标评分脚本往往只执行make你的 Makefile 第一个目标必须是评分要的可执行文件别把入口设计成make run或者必须加参数才能编译。6.2 一个命令完成解压、构建、回归把验收流程固化成脚本每次写完代码跑一次比手动操作可靠得多。脚本接收提交包路径在临时目录里解压、编译、跑用例全部通过才算完成#!/usr/bin/env bash set -euo pipefail pkg$1 tmp$(mktemp -d) trap rm -rf $tmp EXIT unzip -q $pkg -d $tmp cd $tmp/* # 进入解压出来的唯一目录 make -s for t in tests/*.in; do ./fs_mgr $t /tmp/check.actual diff -q ${t%.in}.out /tmp/check.actual /dev/null \ echo PASS $t || echo FAIL $t donemktemp -d创建临时目录trap ... EXIT保证脚本结束自动清理不会往实验目录里留垃圾。cd $tmp/*在解压目录唯一的前提下直接进入如果评分环境解压出来是多层嵌套这个脚本会立刻暴露问题。打包时记住一个原则先cd到 labX 的父目录再执行zip -r 学号_姓名_labX.zip labX/这样包内第一层就是目录本身不要用zip -j展平评分脚本按目录结构找 Makefile 时会直接找不到。提交前最后执行一次unzip -l 学号_姓名_labX.zip | head -5看到第一层是labX/而不是散落的一堆源文件这一眼就能挡掉最频繁的提交格式扣分。本文还有配套的精品资源点击获取
返回列表