☰
95分Linux操作系统实验报告:从命令到内核观察的七个实验闭环
2026/11/2 4:54:50 网站建设 项目流程

简介:这份西南交通大学操作系统实验报告由软件工程专业学生完成,任课教师为胡晓鹏,评分95分,适合正在学习Linux操作系统课程、准备完成课程实验或复盘系统调用与进程管理实践的高校学生参考。压缩包内为1个docx文档,大小约1.48MB,正文按个人声明、实验目的、实验内容、实验步骤和实验总结等模块编排,结构完整,便于对照课程要求查阅。已有404人学习,在同类课程实验中具有一定参考价值。报告覆盖Linux界面与目录结构、Shell常用命令、重定向与管道机制,以及gcc编译和gdb调试流程;同时围绕/proc文件系统观察cpuinfo、meminfo、loadavg、interrupts等内核状态,并记录软中断通信、FCFS与RR进程调度验证、线程同步等实验过程。读者可借此理解实验设计思路、梳理报告写作框架,并对照步骤与结果查漏补缺。

1. 一份95分Linux操作系统实验报告:从命令到内核观察的七个实验闭环

如果你正在做 Linux 操作系统实验报告,最难受的往往不是不会敲命令,而是命令敲完不知道往报告里放什么:目录结构看了、gcc 用了、/proc 读了,最后写成流水账。这份来自某高校软件工程专业的操作系统实验报告(评分 95 分)把七个实验串成了一条线:Linux 环境与 C 调试、/proc 观察、软中断通信、进程调度、线程同步、内存管理、系统调用与缺页统计。它适合课程实验复现、期末前补进程与内存的动手环节,也适合准备面试时把 fork、signal、wait、/proc 这些点重新跑一遍。下面按“能复现”的标准拆开讲,重点放在参数、执行顺序和翻车点。

2. Linux环境与C程序调试:目录结构、vim、gcc、gdb一条线走通

2.1 目录结构与Shell命令:先建立可复现的观察路径

实验报告第一部分看似简单,实际决定了后面所有实验能不能顺利跑起来。Linux 的目录不是随便分的:/bin 放常用命令,/home 放用户目录,/etc 放配置,/proc 是内核状态视图,/tmp 放临时文件,/lib 放库文件。复现时不要只背目录名,要把“看目录、看进程、看权限、看重定向”做成一条命令链,这样报告里才有可核对的输出。

# 查看当前用户和当前目录,确认实验环境没有跑错位置 whoami pwd # 只看目录本身的权限和属主,-d 表示不展开目录内容 ls -ld /bin /home /etc /proc /tmp # 查看磁盘挂载,确认 /home 和根目录的关系 df -h # 查看当前进程,head 限制输出行数,避免刷屏 ps -ef | head -n 5 # 查看当前 shell 进程打开的文件描述符,$$ 表示当前 shell 的 PID ls -l /proc/$$/fd | head

这段命令的逻辑是先从“我是谁、我在哪”确认环境,再观察目录权限和挂载,最后把进程和 /proc 联系起来。参数上,-ld的-d很关键,少了它会把目录内容全列出来;$$是 shell 内置变量,换成具体 PID 也能观察别的进程。报告里记录输出时,建议保留命令和关键行,不要只贴一张截图。

重定向和管道是实验报告里容易写虚的部分,实际用法可以这样落地:

# 统计 /proc 下条目数量,wc -l 统计行数 ls /proc | wc -l # 只取 CPU 型号第一行,-m1 表示匹配到第一行就停 grep -m1 'model name' /proc/cpuinfo # 把启动日志末尾写入临时文件,2>/dev/null 丢弃标准错误 dmesg 2>/dev/null | tail -n 20 > /tmp/boot_tail.txt # 查看写入结果 cat /tmp/boot_tail.txt

2>/dev/null的含义是把文件描述符 2(标准错误)重定向到空设备,避免权限不足的报错干扰结果;>是覆盖写入,>>才是追加。grep -m1的-m是最大匹配数,不加会输出所有 CPU 核的型号。报告里写清楚这些参数,比只写“使用了管道”更有说服力。

2.2 用vim写第一个C程序:编译、运行、改错

实验报告里提到用 vim 编辑argc.c,再用 gcc 编译。复现时我建议直接用 heredoc 生成文件,避免 vim 交互步骤在文档里断掉;实际练习时再走一遍i进入编辑、Esc返回、:wq保存的流程。下面这个程序只做一件事:打印命令行参数,方便观察argc和argv。

/* argc.c:打印命令行参数,配合 gcc -g 观察调试信息 */ #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("argc = %d\n", argc); for (i = 0; i < argc; i++) { printf("argv[%d] = %s\n", i, argv[i]); } return 0; }
# 建立实验目录,避免文件散落在 home 下 mkdir -p ~/oslab/exp1 cd ~/oslab/exp1 # 用 heredoc 写入源文件,实际练习时可改用 vim argc.c cat > argc.c <<'EOF' #include <stdio.h> int main(int argc, char *argv[]) { int i; printf("argc = %d\n", argc); for (i = 0; i < argc; i++) printf("argv[%d] = %s\n", i, argv[i]); return 0; } EOF # -g 保留调试符号,-Wall 打开常用警告,-Wextra 打开额外警告 gcc -g -Wall -Wextra -o argc argc.c # 传入两个参数运行 ./argc one two

-g不是可有可无的装饰,后面 gdb 能定位到行全靠它;-Wall -Wextra会把未使用变量、参数不匹配这类问题提前暴露。-o argc指定输出文件名,不写会默认生成a.out。实验报告里提到用mv agrc.c oslab.1.c改文件名,这个操作在 shell 里是mv 源文件 目标文件;在 vim 里另存为则是命令模式下输入:w new_filename,两个场景不要混。

2.3 gdb调试与常见编译错误:把报错定位到行

实验报告要求熟悉 gcc、gdb 的调试技术。很多人卡在“程序能编译但结果不对”,这时 gdb 比反复改 printf 更有效。下面这个程序故意留了一个未初始化变量的问题,用来演示断点、单步和打印变量。

/* bug.c:sum 里的 s 未初始化,用来观察 gdb 定位过程 */ #include <stdio.h> int sum(int n) { int s; int i; for (i = 1; i <= n; i++) { s += i; } return s; } int main(void) { int r = sum(10); printf("r = %d\n", r); return 0; }
# -O0 关闭优化,保证变量和行号对应关系稳定 gcc -g -O0 -Wall -o bug bug.c # 进入 gdb gdb ./bug

进入 gdb 后按顺序执行:

break main run next next print s continue quit

break main在主函数入口下断点,run启动程序,next单步执行不进入函数内部,print s查看变量值。这里的关键参数是-O0:优化打开后变量可能被寄存器化,gdb 里看到的值和源码顺序不一致。常见编译错误可以按表排查:

现象常见原因处理方式
undefined reference to 'sqrt'数学库未链接编译命令末尾加-lm
implicit declaration of function 'fork'缺少头文件加#include <unistd.h>
undefined reference to 'pthread_create'线程库未链接编译加-pthread
运行时报Segmentation fault空指针或越界gdb 里run后执行bt看调用栈

2.4 从实验报告到可复现记录:命令和输出怎么留痕

实验报告里把命令用粗体加下划线标出来,是为了方便阅卷,但复现时更重要的是留下“命令 + 输出 + 退出码”。我一般用script记录终端会话,这样编译和运行过程都能回到文件里,后面写报告不用凭记忆补。

# -q 安静模式,-c 执行指定命令,最后参数是记录文件 script -q -c "gcc -g -Wall -o argc argc.c && ./argc alpha beta" ~/oslab/exp1/argc.log # 查看记录内容 cat ~/oslab/exp1/argc.log

script的-c适合把一条命令链完整录下来,&&保证编译成功才运行。记录文件名带上实验编号,后面做进程实验、内存实验时不会混。命令输出里的退出码可以用echo $?单独确认,0 表示成功,非 0 表示上一条命令失败。报告里把失败输出也保留下来,反而能体现调试过程,但不要只贴运行结果而不写命令和参数。

3. /proc观察与系统调用:把内核状态读成可核对的字段

3.1 /proc虚拟文件系统原理:为什么读文件就能看内核

/proc 不是磁盘上的普通目录,它是内核在内存里暴露出来的视图。读/proc/cpuinfo看 CPU 信息,读/proc/version看内核版本,读/proc/stat看进程和 CPU 统计,读/proc/uptime看系统运行时间,读/proc/meminfo看内存,读/proc/loadavg看平均负载,读/proc/interrupts看中断分布,读/proc/filesystems看当前内核支持的文件系统类型。实验报告里有一处写成filesystem,实际路径是/proc/filesystems,复现时以实际文件名为准。

文件主要字段实验用途
/proc/cpuinfomodel name、processor观察 CPU 型号和核数
/proc/version内核版本、编译信息确认实验环境
/proc/statcpu、intr、ctxt统计 CPU 时间和上下文切换
/proc/uptime系统运行秒数、空闲秒数计算运行时长
/proc/meminfoMemTotal、MemFree、Buffers观察内存分配
/proc/loadavg1/5/15 分钟负载、运行队列判断系统压力
/proc/interrupts每 CPU 中断计数观察硬件中断分布

实验报告里提到 jiffies:内核用节拍计数,开机时读 RTC 得到基准时间,之后每个节拍 jiffies 加一。/proc/uptime的第一个值就是系统启动后的秒数,第二个值是空闲累计秒数。读这些文件不需要特殊驱动,普通用户也能读大部分内容,但写/proc/sys下的部分文件需要权限,实验里不要随手改。

3.2 用C程序读取/proc:fopen/fgets/sscanf的落地写法

实验报告要求编程观察meminfo、loadavg、interrupts、filesystems。最直接的写法是用fopen打开,fgets逐行读,sscanf解析关键字段。下面这个程序读内存和负载,输出两行结果,适合作为报告里的“编程观察”部分。

/* read_proc.c:读取 /proc/meminfo 和 /proc/loadavg 的关键字段 */ #include <stdio.h> #include <string.h> int main(void) { FILE *fp; char line[256]; unsigned long mem_total = 0, mem_free = 0; double load1, load5, load15; fp = fopen("/proc/meminfo", "r"); if (fp == NULL) { perror("fopen /proc/meminfo"); return 1; } while (fgets(line, sizeof(line), fp) != NULL) { if (sscanf(line, "MemTotal: %lu kB", &mem_total) == 1) continue; if (sscanf(line, "MemFree: %lu kB", &mem_free) == 1) continue; } fclose(fp); fp = fopen("/proc/loadavg", "r"); if (fp == NULL) { perror("fopen /proc/loadavg"); return 1; } if (fscanf(fp, "%lf %lf %lf", &load1, &load5, &load15) != 3) { fprintf(stderr, "parse /proc/loadavg failed\n"); fclose(fp); return 1; } fclose(fp); printf("MemTotal = %lu kB\n", mem_total); printf("MemFree = %lu kB\n", mem_free); printf("loadavg = %.2f %.2f %.2f\n", load1, load5, load15); return 0; }

编译时用gcc -Wall -Wextra -o read_proc read_proc.c。fgets的缓冲区大小256对/proc/meminfo的单行足够,但如果读/proc/interrupts这种每 CPU 一列的长行,缓冲区要放大到 1024 以上。sscanf的返回值表示成功匹配的字段数,等于 1 才说明MemTotal解析成功;fscanf返回 3 才说明三个负载值都读到了。perror会把系统错误原因打出来,比只打印“打开失败”更容易排查。

3.3 系统调用实验:open/read/write/lseek/close与用户态封装

实验报告里的系统调用部分,核心是理解用户态函数最终落到内核。下面用open/read/write/lseek/close做一个文件复制,既能看到系统调用返回值,也能观察文件偏移量。

/* syscall_copy.c:用系统调用复制文件,演示 open/read/write/lseek/close */ #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> int main(int argc, char *argv[]) { int in_fd, out_fd; ssize_t n; char buf[4096]; if (argc != 3) { fprintf(stderr, "usage: %s src dst\n", argv[0]); return 1; } in_fd = open(argv[1], O_RDONLY); if (in_fd < 0) { perror("open src"); return 1; } out_fd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0644); if (out_fd < 0) { perror("open dst"); close(in_fd); return 1; } while ((n = read(in_fd, buf, sizeof(buf))) > 0) { ssize_t written = 0; while (written < n) { ssize_t w = write(out_fd, buf + written, n - written); if (w < 0) { perror("write"); close(in_fd); close(out_fd); return 1; } written += w; } } if (n < 0) { perror("read"); } /* 回到文件头,验证 lseek 对偏移量的影响 */ if (lseek(out_fd, 0, SEEK_SET) < 0) { perror("lseek"); } close(in_fd); close(out_fd); return 0; }

open的第二个参数是标志位:O_RDONLY只读,O_WRONLY | O_CREAT | O_TRUNC表示只写、不存在则创建、存在则截断。第三个参数0644是新建文件的权限,受 umask 影响。read返回 0 表示读到文件末尾,返回 -1 表示错误;write可能只写一部分,所以内层while要循环到written == n。lseek(out_fd, 0, SEEK_SET)把偏移量移到开头,实验报告里如果涉及文件读写位置,这个调用要写清楚。

3.4 观察结果怎么解读:uptime、loadavg、interrupts不是随便看

/proc/uptime的两个数分别是系统运行秒数和空闲累计秒数,不是“开机时间”和“当前时间”。/proc/loadavg前三个数是 1、5、15 分钟平均负载,第四个字段是“运行队列数/总进程数/最后创建的 PID”,第五个是最近运行的 PID。/proc/interrupts每个 CPU 一列,后面是中断控制器和设备名,数值是累计次数。读这些字段时不要按固定列号硬解析,内核版本变化可能导致列数变化,按关键字和空格切分更稳。

# 用 awk 取 /proc/loadavg 前三个字段 awk '{printf "load1=%.2f load5=%.2f load15=%.2f\n", $1, $2, $3}' /proc/loadavg # 取 /proc/uptime 的第一个字段 awk '{printf "uptime=%.0f s\n", $1}' /proc/uptime # 查看中断计数前 10 行,避免输出过长 head -n 10 /proc/interrupts

awk的$1、$2、$3是按空白切分后的字段,printf控制小数位方便写报告。head -n 10只取前 10 行,因为中断表可能很长。实验报告里如果要写“结果解读”,把字段含义、单位、采样时间写清楚,比只贴一张截图更有信息量。

4. 软中断通信与进程控制:fork、signal、kill 的三进程实验

4.1 信号机制与进程树:软中断到底中断了什么

软中断通信实验里的“软中断”指的是信号,不是硬件中断。父进程用fork创建子进程,子进程用signal注册处理函数,父进程收到终端Ctrl+\产生的SIGQUIT后,用kill给子进程发自定义信号,子进程在信号处理函数里把等待标志清零,主循环退出,父进程再用wait回收。整个过程的关键不是“发信号”三个字,而是信号注册顺序、等待循环和回收顺序。

信号常见来源实验里的用途
SIGQUIT终端Ctrl+\通知父进程开始终止流程
SIGUSR1kill -USR1 pid父进程通知子进程退出
SIGUSR2kill -USR2 pid通知另一个子进程退出
SIGCHLD子进程状态变化内核通知父进程回收,注意不要和自定义编号冲突

实验报告里的代码用 16、17 作为自定义信号。这里有一个血泪经验:Linux 上 17 通常是SIGCHLD,直接拿来当自定义信号可能和子进程回收机制撞号。复现时我一般改用SIGUSR1、SIGUSR2,语义清楚,也不容易和系统信号冲突。

4.2 父进程创建两个子进程:完整代码与执行顺序

下面这个程序对应实验报告里的“父进程创建两个子进程,按Ctrl+\后依次终止”。代码用SIGUSR1、SIGUSR2替代 16、17,wait_mark用volatile sig_atomic_t,保证信号处理函数里改的值能被主循环看到。

/* sigtwo.c:父进程创建两个子进程,收到 SIGQUIT 后依次通知退出 */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> static volatile sig_atomic_t wait_mark = 1; void stop(int signo) { (void)signo; wait_mark = 0; } void waiting(void) { while (wait_mark != 0) pause(); } int main(void) { pid_t p1, p2; p1 = fork(); if (p1 < 0) { perror("fork p1"); return 1; } if (p1 == 0) { /* 子进程 P1:忽略终端 SIGQUIT,等待父进程发 SIGUSR1 */ signal(SIGQUIT, SIG_IGN); signal(SIGUSR1, stop); waiting(); lockf(1, 1, 0); printf("child P1 %d is killed by parent %d\n", getpid(), getppid()); lockf(1, 0, 0); exit(0); } p2 = fork(); if (p2 < 0) { perror("fork p2"); return 1; } if (p2 == 0) { /* 子进程 P2:等待父进程发 SIGUSR2 */ signal(SIGQUIT, SIG_IGN); signal(SIGUSR2, stop); waiting(); lockf(1, 1, 0); printf("child P2 %d is killed by parent %d\n", getpid(), getppid()); lockf(1, 0, 0); exit(0); } /* 父进程:先注册 SIGQUIT,再等待终端 Ctrl+\ */ signal(SIGQUIT, stop); waiting(); kill(p1, SIGUSR1); kill(p2, SIGUSR2); waitpid(p1, NULL, 0); waitpid(p2, NULL, 0); printf("parent %d is killed\n", getpid()); return 0; }

执行顺序要盯住:父进程先fork出 P1,再fork出 P2;两个子进程都忽略SIGQUIT,只等各自的SIGUSR1、SIGUSR2;父进程收到SIGQUIT后先给 P1 发信号,再给 P2 发信号,最后waitpid回收。lockf(1, 1, 0)锁住标准输出,lockf(1, 0, 0)解锁,避免多进程输出粘在一起。pause()让等待循环不空转 CPU,比while (wait_mark);更稳。

4.3 父-子-孙进程链:终止顺序为什么容易反

实验报告第二个场景是父进程创建子进程,子进程再创建孙进程,收到Ctrl+\后依次终止孙进程、子进程、父进程。这里的顺序容易反,是因为信号从父进程往下传,但回收要从下往上等,代码里必须让子进程先等孙进程结束,再打印自己的结束信息。

/* sigtree.c:父-子-孙三层进程,按 SIGQUIT 后从孙到父依次退出 */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> static volatile sig_atomic_t wait_mark = 1; void stop(int signo) { (void)signo; wait_mark = 0; } void waiting(void) { while (wait_mark != 0) pause(); } int main(void) { pid_t child; child = fork(); if (child < 0) { perror("fork child"); return 1; } if (child > 0) { /* 父进程:等待 Ctrl+\,然后通知子进程 */ signal(SIGQUIT, stop); waiting(); kill(child, SIGUSR1); waitpid(child, NULL, 0); printf("parent %d exit\n", getpid()); return 0; } /* 子进程:创建孙进程 */ pid_t grandchild = fork(); if (grandchild < 0) { perror("fork grandchild"); return 1; } if (grandchild > 0) { /* 子进程:等父进程信号,再通知孙进程 */ signal(SIGQUIT, SIG_IGN); signal(SIGUSR1, stop); waiting(); kill(grandchild, SIGUSR2); waitpid(grandchild, NULL, 0); printf("child %d exit\n", getpid()); return 0; } /* 孙进程:只等子进程信号 */ signal(SIGQUIT, SIG_IGN); signal(SIGUSR2, stop); waiting(); printf("grandchild %d exit\n", getpid()); return 0; }

父进程收到SIGQUIT后只给子进程发SIGUSR1,子进程收到后再给孙进程发SIGUSR2,孙进程退出后子进程waitpid回收,最后父进程回收子进程。这个顺序保证了输出从孙进程开始,最后才是父进程。如果子进程先打印再waitpid,输出顺序就会看起来“反了”,不是信号没生效,而是回收顺序写错。

4.4 用ps和kill验证进程树:可复现的观察方法

信号实验只看终端输出不够,最好用ps看进程树,用kill手动发信号验证。下面命令先编译运行,把前台进程放到后台,拿到 PID 后观察父子关系,再发SIGQUIT。

# 编译,-Wall 打开警告 gcc -Wall -Wextra -o sigtree sigtree.c # 后台运行并记录 PID ./sigtree & pid=$! echo "tree root pid = $pid" # 查看进程树,-o 指定输出列,--forest 用树形显示 ps -o pid,ppid,stat,cmd --forest -p "$pid" --ppid "$pid" # 给根进程发 SIGQUIT,触发整条终止链 kill -QUIT "$pid" # 等待后台进程结束 wait "$pid"

$!是上一条后台命令的 PID,ps --forest能把父子关系画出来,--ppid只看指定父进程的子进程。kill -QUIT发送的是SIGQUIT,和终端Ctrl+\效果一致。如果进程没有按预期退出,先看ps -o stat里的状态:Z是僵尸,说明父进程没有wait;S是睡眠,可能卡在pause();R是运行,可能等待循环在空转。

5. 避坑与排查:Linux实验里最容易翻车的五处细节

5.1 环境与编译类:网络模式和跨平台编译

第一个坑是虚拟机网络连不上。现象是主机能上网,虚拟机里ping不通外网,ip addr看不到有效地址。原因常见有三类:虚拟网络模式选错、虚拟网卡没有启用、DHCP 没有拿到地址。解决时先确认采用 NAT 模式,虚拟机网卡处于连接状态,然后在虚拟机里执行ip addr看有没有inet地址;没有就重启网络服务或重新获取地址。桥接模式也能用,但同网段设备多时容易地址冲突,实验环境里 NAT 更省心。

第二个坑是在桌面系统里写含fork的 C 代码,拿回 Linux 编译报错。现象是 IDE 里提示找不到unistd.h,或者链接时报undefined reference to 'fork'。原因是fork属于 POSIX 接口,桌面工具链默认不提供这套头文件和库。解决方式不是硬改代码,而是在 Linux 里编译,或者用条件编译把 POSIX 部分隔开。带线程的程序还要注意链接参数:

# 线程程序必须加 -pthread,只加 -lpthread 在部分环境仍可能缺宏定义 gcc -Wall -Wextra -pthread -o thread_demo thread_demo.c

5.2 进程与信号类:信号编号和回收顺序

第三个坑是自定义信号编号撞车。现象是子进程还没收到父进程的自定义信号,就触发了处理函数,或者wait行为异常。原因是在 Linux 上 17 通常是SIGCHLD,实验代码里把 17 当自定义信号,等于覆盖了子进程状态变化的通知。解决方式是改用SIGUSR1、SIGUSR2,并在报告里写清楚信号用途。查看信号编号可以用kill -l,不要凭记忆写数字。

第四个坑是Ctrl+\后子进程不退出。现象是父进程打印了结束信息,ps里还能看到子进程。原因可能是子进程先signal(SIGQUIT, SIG_IGN)后没有注册自定义信号,或者父进程kill的 PID 不对,或者子进程卡在pause()但信号被忽略。排查时先ps -o pid,ppid,stat,cmd --forest看进程树,再确认kill返回值,最后检查子进程的信号注册顺序。注册信号一定要放在waiting()之前,顺序反了信号可能丢。

# 查看信号编号,确认 SIGUSR1/SIGUSR2 的实际值 kill -l | grep -E 'USR1|USR2|QUIT' # 查看进程状态,Z 表示僵尸,S 表示睡眠 ps -o pid,ppid,stat,cmd --forest -p "$pid" --ppid "$pid"

5.3 内存与统计类:缺页统计口径

第五个坑是缺页次数统计和预期对不上。现象是同一个程序跑三次,缺页次数差距很大,或者加了打印之后数字明显变化。原因是统计区间没有固定:程序启动、动态库加载、文件缓存、编译器优化都会影响缺页。解决时固定工作集大小,编译用-O0关闭优化,运行前清一次缓存或至少重复多次取中位数,并把统计区间写进报告。用/usr/bin/time -v可以看到Minor (reclaiming a frame) page faults和Major (requiring I/O) page faults两项,区分它们比只看一个总数更有意义。

# -v 输出详细统计,包括缺页次数和上下文切换 /usr/bin/time -v ./memtest

报告里写缺页实验时,把程序的工作集、循环次数、编译参数、采样次数列出来,读者才能判断数字是否可信。只写“缺页次数为多少”而没有口径,换一台机器就对不上。

6. 把实验报告变成可复现验证脚本:三个进阶技巧

第一个技巧是写一个批量编译脚本,把七个实验的源文件统一编译,避免每次翻目录。脚本里用数组列出源文件,编译失败就打印文件名并退出,成功则输出可执行文件路径。

#!/usr/bin/env bash # run_all.sh:批量编译实验源码,失败即停 set -euo pipefail srcs=(argc.c read_proc.c syscall_copy.c sigtwo.c sigtree.c) mkdir -p build for src in "${srcs[@]}"; do if [[ ! -f "$src" ]]; then echo "missing: $src" continue fi out="build/${src%.c}" gcc -g -O0 -Wall -Wextra -pthread -o "$out" "$src" echo "built: $out" done

set -euo pipefail让脚本遇到未定义变量、管道失败、命令失败时直接退出;${src%.c}去掉后缀拼出可执行文件名。这个脚本适合放在实验目录根部,报告里附上脚本和编译输出,复现路径就清楚了。

第二个技巧是给/proc读数做前后快照,用diff看变化。比如跑内存实验前后各抓一次meminfo和stat,只保留关键字段,对比差异比截两张图更直观。第三个技巧是固定随机种子和循环次数,进程调度和内存实验里凡是带随机性的输入都写死,否则每次结果不同,报告里没法解释。我现在的习惯是:每做一个进程实验,先ps --forest确认进程树,再发信号,最后检查有没有僵尸进程。从那以后,信号编号和回收顺序这类问题很少再让我返工。需要对照完整报告排版和可编译代码的,可以在文末获取入口按需取用。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询