简介西南交通大学操作系统实验报告胡晓鹏课程评分A、95分面向软件工程等专业学生适合用于对照完成Linux环境熟悉、C语言程序调试与内核状态观测实验。资源以单个docx文档交付压缩包约1.48MB正文含封面、目录、个人声明及实验目的、内容、步骤与总结章节清楚便于按顺序查阅借鉴。已有404人学习。报告对应2020—2021学年第二学期课程覆盖Linux目录结构与Shell常用命令、重定向和管道、gcc与gdb调试以及/proc下处理器、版本、运行时间、内存、负载、中断和文件系统等状态信息的读取观测还涉及软中断通信、先来先服务与时间片轮转进程调度、线程同步实验。读者可据此梳理实验流程、搭建操作步骤、完善报告框架并复盘关键调试方法与系统观测思路对课程实验写作和Linux基础实践均有参考价值。1. 操作系统实验的整体地图从用户态走到内核态再走回来Linux 操作系统实验最容易被误解成七次互不相干的编程作业第一次配环境第二次读/proc第三次写信号后面依次是调度、线程、内存、系统调用、缺页统计。真正把这门课的实验报告按顺序翻一遍会发现它其实是一条路线先在用户态站稳再借内核暴露的接口去看内核然后用进程和线程去操纵资源最后用统计量验证自己看到的东西。地图可以画成四段环境与工具链负责让你能编译、能运行/proc观测负责让你看见内核变量信号、调度、线程同步负责让你操纵进程内存管理、系统调用与缺页统计负责让你触碰内核入口并量化结果。那份评分拿到 A 档的报告价值不在代码本身而在于每个实验都留了“为什么这么设计、出错时看哪里”的痕迹。下面按这条线重排一遍。2. 环境与工具链Shell、vim 与虚拟机的三种网络模式Linux 操作系统实验的第一道门槛不在内核而在手上那套工具链能不能一次跑通。很多人环境还没配好就去写信号通信结果gcc报错、虚拟机之间互相 ping 不通、文件传到一半找不到路径时间全耗在排错上。这一章把第一个实验拆成四件事认目录、认编辑器、认编译调试、认虚拟机的网络模式。四件事都通了后面的实验才有地基否则每做一个新实验都要回头补课那种翻车经历一次就够了。2.1 目录结构一张分工表胜过十次 cdLinux 没有盘符整棵树只有一个根/路径分隔符是正斜杠而不是反斜杠这是从 Windows 迁过来时第一个要改的肌肉记忆。目录不是随便长的每个目录都有明确分工/bin放最常用的命令/home下每个用户一个以账号命名的目录/media是系统自动识别外接设备后的挂载点/opt留给额外安装的软件/tmp是临时文件堆场/lostfound则要等非正常关机之后才有内容。做实验时经常要在这些目录之间跳记不住分工就会一直在cd里打转。目录职责实验里什么时候会碰到/bin最常用的可执行命令用ls、ps、chmod时其实就在调用它/home用户主目录实验代码和报告默认放这里/etc系统与软件的配置文件改网络配置、看环境变量时/lib共享库与 C 编译器依赖的库编译链接失败时先想到这里/proc内核状态的虚拟视图第二个实验的主战场/tmp临时文件编译中间产物、日志/opt额外安装的软件装 IDE 或工具链时/lostfound非正常关机后的碎片文件一般用不到但要知道它为什么存在提示判断一个路径是目录还是文件用ls -l看第一个字符d是目录-是普通文件。2.2 vim 到 gcc 的最小闭环实验报告里记录的工作流很朴素vim打开源文件按i、o、a或r中任意一个进入编辑模式写完按Esc回到一般模式再输入:wq保存退出最后用gcc编译。这套动作看起来原始但它是所有 Linux 开发环境的公约数后面换成任何编辑器底层还是“编辑—编译—运行—调试”四步。# 最小闭环编辑、编译、运行 vim argc.c # 打开或新建源文件i 进入编辑模式Esc 后 :wq 保存 gcc argc.c -o argc # -o 指定输出文件名不写则默认为 a.out ./argc # 运行当前目录下的可执行文件-o后面跟的是你想要的可执行文件名养成显式命名的习惯后面一个目录里堆十几个a.out时会庆幸。调试时再加-g把符号表编进去gdb才能按函数名下断点gcc -g argc.c -o argc # -g 生成调试信息-Wall 打开常用警告 gdb ./argc # (gdb) b main # 在 main 函数入口下断点 # (gdb) r # 运行到断点 # (gdb) n # 单步执行不进入函数内部 # (gdb) p argc # 打印变量值-Wall不是可有可无的装饰它能提前拦下未初始化变量、隐式类型转换这类问题在信号实验里漏掉一个#include signal.h报错信息会指向一堆看不懂的符号-Wall往往能让问题在第一行就暴露。报告里还记了一个小技巧想把当前缓冲区另存为别的文件名在 vim 里用:w new_filename比退出后mv更省事。2.3 虚拟机网络桥接、NAT、仅主机三种模式怎么选环境配好之后下一个坑往往是网络。报告里记录的运行环境是 3.10 系内核配 4.8.5 的gcc虚拟机装好、配置看着也没问题但网络连接反复失败最后是顺着三种网络模式的差异才定位到原因。这三种模式的差别本质上是“虚拟机的 IP 由谁分配、谁能主动访问谁”。模式虚拟机访问互联网宿主机访问虚拟机IP 分配方式典型用途易错点桥接可以可以与宿主机同网段独立 IP虚拟机和宿主机平起平坐需要被局域网内其他机器访问同网段设备多时容易撞 IPNAT可以一般不行由虚拟网络分配可跨网段只想让虚拟机上网、不暴露给外部外部机器主动连不进来仅主机不行可以独立虚拟网段只做宿主机与虚拟机之间的隔离通信误以为能上网实际完全隔离理解方式可以借用报告里的比喻桥接像是把物理主机虚拟成一台交换机所有桥接的虚拟机都插在这台交换机上彼此可以互访代价是同网段设备一多地址冲突的概率就上来了。NAT 则让虚拟机借宿主机的地址出去外部看不到虚拟机冲突少但反过来也进不来。仅主机模式干脆把虚拟机和外部隔开适合做纯本地实验。多数课程实验选 NAT 就够用既不冲突又能装包。2.4 redirect 与 pipeShell 真正的骨架cd、ls、ps、chmod这些命令只是砖头重定向和管道才是把这些砖头搭起来的水泥。重定向改变数据流的去向管道把前一个命令的输出直接交给后一个命令当输入两者合起来一行命令就能完成“筛选进程、统计数量、写入文件”这种组合动作。ps aux | grep argc # 管道把 ps 的全部输出交给 grep 过滤 ls -l /proc proc_list.txt # 重定向把标准输出写进文件覆盖原内容 wc -l proc_list.txt # 输入重定向把文件内容喂给 wc 统计行数是覆盖写是追加写是把文件当标准输入2单独接管标准错误。实验里调试程序时把错误输出和正常输出分开存两个文件比在满屏日志里找一行报错高效得多。chmod则决定谁能读、写、执行一个文件脚本跑不起来时先看它有没有执行位x这是新手最常见的“命令明明存在却提示权限不足”的原因。3./proc与内核观测从 cpuinfo 到 jiffies 的八个观测点第二个实验的核心只有一句话内核把一部分内部状态以文件的形式暴露出来你读文件就是在读内核。这个说法第一次听很反直觉因为传统认知里“文件”应该占磁盘而/proc下的文件大多不占任何磁盘空间内容是内核在你看的那一刻现算出来的。理解这一点后面所有观测实验的“为什么数值会变”就不需要再解释了。3.1/proc的定位一个没有磁盘体积的文件系统/proc是虚拟文件系统它不落在任何一块硬盘上每次读取都由内核现场生成内容。目录下有两类东西一类是固定名字的文件比如cpuinfo、version、meminfo回答的是“这台机器的硬件和内核长什么样”另一类是以数字命名的子目录数字就是进程号里面装的是那个进程的身份证、内存映射、打开的文件描述符、调度参数。这个设计带来两个直接后果。第一读/proc得到的永远是快照隔一秒再读可能就变了所以做观测实验必须写清楚采样时刻和采样间隔否则结论没法复现。第二普通用户能读大部分文件但改不了能改的那部分比如某些进程参数需要权限这也是为什么内存管理和系统调用实验里有些操作必须提权才能验证。3.2 八个观测点的分工表实验要求先用现成程序观察cpuinfo、version、stat、uptime再自己编程观测meminfo、loadavg、interrupts、filesystem。这八个文件不是随便挑的它们分别对应 CPU、内核版本、全局统计、运行时长、内存、负载、中断、文件系统八个维度合起来正好是一次系统体检。文件回答什么问题关键字段或判读方式实验任务/proc/cpuinfoCPU 型号、核数、主频、缓存model name、processor编号、cpu cores观察统计逻辑核数/proc/version内核版本与编译信息版本号、编译器版本观察判断稳定版还是开发版/proc/stat全局 CPU、进程、上下文切换统计cpu行的 user/system/idle 时间片观察理解累计量含义/proc/uptime系统已运行时长与空闲时长两个浮点数单位秒观察和jiffies换算对照/proc/meminfo内存总量、可用量、缓存、交换区MemTotal、MemAvailable、Buffers、Cached编程读取并打印/proc/loadavg1、5、15 分钟平均负载与运行队列前三个数 正在运行/总进程数编程读取/proc/interrupts每个 CPU 上各中断号的触发次数按 CPU 分列最后一列是中断控制器编程读取并观察变化/proc/filesystems当前内核支持的文件系统类型每行一个类型名nodev表示非设备型编程读取提示/proc/stat里的时间是累计值两次采样相减才是有意义的区间占用率直接看绝对值会被误读成“CPU 一直满载”。3.3 jiffies内核用什么数时间报告里有一段很值得抄进笔记的追查Windows 下时间常用“滴答”计数那 Linux 靠什么记账答案是jiffies内核里的一个全局变量以节拍时间为单位递增内核每过一个节拍它就加一。开机时内核从 RTC 硬件读一个初始基准时间把这个时间换算成对应的jiffies值之后系统里任何一个时刻都能用jiffies标注。换算方法是基准时间减去 1970-01-01 00:00:00 UTC得到的时间段再按节拍频率折成节拍数。这也是为什么/proc/uptime里的秒数能和jiffies对上——两者只是同一件事的两种单位。理解这层关系之后再看/proc/stat里那些以节拍为单位的时间片就不会再困惑“为什么数值这么大”。#include stdio.h int main(void) { char key[64]; long value; FILE *fp fopen(/proc/meminfo, r); // 以只读方式打开虚拟文件 if (!fp) { perror(fopen); return 1; } for (int i 0; i 5 fscanf(fp, %63s %ld, key, value) 2; i) { printf(%-16s %ld\n, key, value); // 逐行读出前五个字段 } fclose(fp); return 0; }这段程序没有用任何特殊接口fopen打开普通文件的方式同样适用于/proc/meminfo差别只在于读到的内容由内核现场生成。fscanf里的%63s限定了键名长度防止缓冲区溢出格式串中键和值之间没有冒号因为meminfo的冒号紧贴键名用空白分隔正好跳过。改一下文件名和字段数同一套代码就能读loadavg和interrupts。3.4 从读到写内存管理实验里/proc之外还要看什么内存管理实验的报告结构是“目的—内容—结果展示—结果解读—代码—复盘”重点其实在“解读”那一节。光把meminfo打印出来不算完成得说清楚MemTotal、MemFree、MemAvailable、Buffers、Cached之间的差额去了哪里为什么MemAvailable比MemFree大得多。这些字段背后是页框管理、页缓存和回收策略读一次数值只能看到表层。再往下走一步缺页统计就接上了。/proc/vmstat里的pgfault、pgmajfault记录全局缺页次数/proc/pid/stat里的min_flt、maj_flt记录单个进程的缺页次数前者是软缺页页在内存里但没建立映射后者是硬缺页必须读磁盘。做缺页统计实验时先明确统计的是全局还是单进程、是软缺页还是硬缺页口径定不下来后面所有数字都没有意义。4. 进程与并发软中断、调度策略与线程同步进程这部分是整门课的重心三个实验分别对应三个问题进程之间怎么通信多个就绪进程谁先上 CPU多个线程同时改一块内存怎么不出错。三个问题的答案都指向同一件事——并发不是“同时执行”而是“交替执行加上一套控制规则”。没理解这句话代码能跑通但解释不了运行结果。4.1 软中断通信fork、signal、kill的三角关系实验要求父进程创建两个子进程按下Ctrl\之后由父进程依次终止子进程再终止自己。整条链路只有三个函数fork负责造进程signal负责登记“收到某个信号时执行哪个函数”kill负责把信号投递给指定进程。Ctrl\在终端里对应的就是SIGQUIT所以父进程只要捕获它就能把键盘输入变成一次进程间通信。#include stdio.h #include signal.h #include unistd.h int wait_mark 1; // 全局标志位1 表示仍在等待信号 void stop(int signo) { // 信号处理函数收到信号就把标志位清零 wait_mark 0; } int main(void) { pid_t p1; while ((p1 fork()) -1); // fork 失败就重试避免创建失败后继续执行 if (p1 0) { // 父进程分支 signal(SIGQUIT, stop); // 登记Ctrl\ 触发 stop while (wait_mark); // 忙等直到用户按下 Ctrl\ kill(p1, SIGUSR1); // 投递自定义信号给子进程 wait(NULL); // 回收子进程不留僵尸 printf(parent %d exit\n, getpid()); } else { // 子进程分支 signal(SIGQUIT, SIG_IGN); // 子进程忽略 Ctrl\终止权统一交给父进程 signal(SIGUSR1, stop); // 只等父进程发来的 SIGUSR1 while (wait_mark); printf(child %d exit\n, getpid()); } return 0; }这段代码里有三个容易看漏的设计。子进程把SIGQUIT设成SIG_IGN是为了保证按Ctrl\时只有父进程响应终止顺序才是可控的原理上可以继续写数字但可读性差换成SIGUSR1、SIGUSR2更好。while (wait_mark);是忙等CPU 空转教学实验能接受真实场景应该用pause()或条件变量。wait(NULL)是在回收子进程漏掉它父进程先退出就会留下僵尸进程。报告代码里出现的lockf(1, 1, 0)和lockf(1, 0, 0)是在给标准输出加锁和解锁防止多个进程的printf输出互相插队。4.2 进程树为什么终止顺序总是从叶子开始实验的第二条路径是父—子—孙三级结构按下Ctrl\后先杀孙进程再杀子进程最后父进程退出。顺序不是随便定的子进程要等自己的子进程结束才能退出父进程要等子进程结束才能退出所以信号必须由根向下逐层传递回收则由叶向根逐层进行。报告里的两次运行结果都印证了这一点一次父进程号是 3148两个子进程分别被父进程杀掉另一次父进程号是 3657子进程 3658 先创建了孙进程 3659终止顺序是 3659、3658、3657。看输出顺序时要注意printf之间如果不加锁实际打印顺序和进程实际终止顺序可能不一致判断依据应该是进程号而不是行的先后。4.3 调度FCFS 与 RR 的差别落在哪几个指标上调度实验要求验证先来先服务和时间片轮转两种策略。FCFS 按到达顺序排队非抢占实现最简单但一个长作业排在前面就会把后面所有短作业堵住平均周转时间被拉长这就是常说的护航效应。RR 给每个进程一个固定时间片用完就排到队尾响应时间明显改善代价是上下文切换次数上升时间片设得太小切换开销会吃掉收益。维度FCFSRR是否抢占否一旦上 CPU 就跑完是时间片用完强制切换排序依据到达时间到达时间 轮转队列平均周转时间长作业在前时明显偏大通常更均衡响应时间差后到进程可能等很久好等待上限约为时间片乘以队列长度主要开销几乎无切换开销切换次数与时间片大小成反比典型误用拿它跑交互式任务时间片设成 1ms开销远超收益算平均周转时间时公式是“完成时间减到达时间”等待时间则是“周转时间减服务时间”两个指标别混。实验里可以用一组到达时间和服务时间手算一遍再和程序输出对照这个过程比看十遍定义都管用。报告里先创建工程再验证两种策略的步骤本质是把调度器当成一个可配置的黑匣子改参数看结果重点在读懂指标变化的方向。4.4 线程同步临界区保护的三种写法线程同步实验要解决的是一个经典问题多个线程对同一个变量做加一最后结果比预期小。原因在于counter不是原子操作它由读、改、写三步组成两个线程交错执行就会丢更新。解决办法是把这三步包进临界区同一时刻只允许一个线程进入。#include pthread.h #include stdio.h int counter 0; // 共享变量即临界区保护对象 pthread_mutex_t lock PTHREAD_MUTEX_INITIALIZER; // 静态初始化互斥量 void *worker(void *arg) { for (int i 0; i 100000; i) { pthread_mutex_lock(lock); // 进入临界区前加锁 counter; // 读-改-写三步非原子 pthread_mutex_unlock(lock); // 离开临界区必须解锁 } return NULL; }编译时要带-lpthread否则链接阶段会报找不到符号。互斥量适合保护临界区较短的场景如果临界区里有阻塞操作就该考虑条件变量或信号量。实验报告里用到的lockf是文件锁加锁对象是文件描述符写法更轻但它的粒度是文件而不是变量用来给标准输出排队很合适用来保护内存变量就不够精确。方式加锁对象适用场景注意点pthread_mutex内存中的互斥量多线程共享变量忘记解锁会死锁lockf文件描述符多进程写同一文件或终端只对文件有效信号量计数器需要控制并发数量初值设错会直接卡住5. 易错点排查七个实验里最容易翻车的五个地方把实验报告从头到尾捋一遍出问题的地方高度集中反复踩的坑就那么几个。下面五条按“容易漏什么、为什么会漏、怎么补”展开每条都对应实验里真实出现过的现象复习时对着自己的代码过一遍能省掉大量重跑的时间。5.1 信号编号写死成 16、17易漏点代码里直接写kill(p1, 16)、signal(17, stop)跑起来没问题换一台机器或者换一个信号就全乱。为什么漏数字能跑通编译不报错运行时也没提示看起来完全正确。但 16、17 在部分平台上含义并不固定和SIGUSR1、SIGUSR2对不上时就出错而且读代码的人根本不知道这两个数字代表什么。怎么补一律用宏SIGUSR1、SIGUSR2、SIGQUIT、SIGINT这些名字自带语义。发送端和接收端必须约定同一套宏改的时候两边一起改。顺带把signal的返回值检查一下注册失败时能第一时间发现而不是等到按Ctrl\没反应才回头查。5.2fork之后父子进程抢输出wait忘了写易漏点父进程和子进程同时printf输出行互相插队子进程已经退出父进程还在跑进程列表里留一堆僵尸。为什么漏单次运行的输出看起来“大概对”顺序偶尔乱也没人追究僵尸进程在ps里显示为Z状态不主动看根本发现不了。更深一层fork之后父子进程共享文件偏移量谁先写谁后写由调度决定不是由代码顺序决定。怎么补约定一个输出顺序或者用lockf给标准输出加锁让一段打印变成一个整体每个fork之后都要有成对的wait父进程等子进程、子进程等孙进程形成一棵自底向上的回收链。跑完用ps看一眼有没有Z状态残留比事后猜要靠谱。5.3 文件名和路径的低级错让编译报错看起来像内核问题易漏点gcc argc.v把扩展名写成.vvim agrc.c把文件名拼错结果打开的是空文件编译报一堆“未定义引用”看起来像是系统环境坏了。为什么漏报错信息里出现undefined reference、No such file这类词第一反应往往是去查库、查环境变量很少有人先回头看一眼自己敲的文件名。报告里就记录过一次改文件名的操作用mv重命名或者用:w new_filename另存说明这类问题确实高频。怎么补编译前先用ls确认文件存在、扩展名正确出现链接错误时先看错误里提到的符号是不是自己写的函数再看是不是漏了-lpthread、-lm这类链接参数。把“文件名—编译命令—报错首行”三样并排看九成的报错能在一分钟内定位。5.4 把/proc的瞬时值当成稳定结论易漏点读一次meminfo就下结论说“可用内存只有这么多”读一次loadavg就说“系统负载很高”没有采样间隔也没有重复观测。为什么漏/proc的文件长得像普通文件读起来太顺手很容易忘记它每次读取都是现算的。系统里的缓存、后台进程、中断都在变单点采样得到的只是一个瞬间。怎么补观测类实验至少采样三次写清间隔取趋势而不是取单值对比前后变化时用同一口径、同一时间窗口。报告里把cpuinfo、version、stat、uptime放在一起观察就是为了交叉验证——版本决定字段含义运行时长决定累计量的大小。5.5 系统调用与缺页统计的口径没对齐易漏点统计缺页次数时一会儿读/proc/vmstat的全局值一会儿读/proc/pid/stat的单进程值两个数字放在一张表里比较结论自然对不上。为什么漏两个文件里的字段名相似pgfault和min_flt都叫“缺页”但一个统计整机、一个统计进程一个是累计值、一个是进程生命周期内的计数。口径不写清楚数字就是自说自话。怎么补先把统计对象写进实验步骤——统计谁、统计多长时间、软缺页还是硬缺页再决定读哪个文件。系统调用实验也是同理用户态封装、陷入内核、内核入口分发、返回值回传这四步要按顺序写清楚每一步对应哪个文件或哪个返回值不能跳步。做完整理成一张“调用—入口—观测点”的对照表比背定义有效。6. 复习清单考前两小时怎么把八个模块过一遍复习这门课最有效的方式不是重读报告而是拿一张白纸把每个实验的“输入—动作—输出—观测点”四列默写出来写不出来的地方就是漏洞。下面这张表可以当自检用右列是合上笔记后应该能答上来的问题。模块必会内容自检问题环境与命令目录分工、cd/ls/ps/chmod、重定向与管道和差别在哪2管什么编辑与编译vim 三种模式、gcc -o -g -Wall、gdb断点编译通过但运行段错误第一步查什么虚拟机网络桥接、NAT、仅主机的可达性差异外部机器连不进虚拟机最可能是哪种模式/proc观测八个观测文件的字段含义MemAvailable为什么大于MemFree时间与节拍jiffies的递增规则与换算/proc/uptime和jiffies怎么对应信号通信fork、signal、kill、wait的配合子进程为什么要忽略SIGQUIT进程调度FCFS 与 RR 的指标差异、周转时间算法时间片调小平均响应时间和切换开销怎么变线程同步临界区、互斥量、文件锁的取舍counter为什么会丢更新系统调用与缺页陷入内核的路径、软缺页与硬缺页的口径全局缺页和单进程缺页分别读哪个文件具体做法可以分三步。第一步只画结构不写细节把七个实验按“用户态→内核观测→进程并发→内核入口”排成一条线确认自己能说清每个实验在整条线的哪个位置。第二步挑三个最容易混的点做对比FCFS和RR的指标、lockf和pthread_mutex的适用场景、全局缺页和单进程缺页的统计口径每个点用三句话讲完。第三步把报告里出现过的报错现象列出来倒推原因比如“按Ctrl\没反应”对应信号没注册成功“进程列表出现 Z”对应wait漏写。还有一个很实用的小技巧把每个实验的核心代码缩到二十行以内只保留创建、通信、同步、等待这几个关键调用注释写清每一行在做什么。缩不下来的部分往往就是自己没真懂的部分。从那以后我每次做系统类实验都强制先跑一遍最小闭环确认文件路径、确认编译参数、确认进程回收再动业务逻辑。这套习惯救过我好几次也让我少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取