news 2026/10/28 2:09:19

95分Linux操作系统实验报告:从命令行到进程调度八实验入门

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
95分Linux操作系统实验报告:从命令行到进程调度八实验入门

简介:面向软件工程与操作系统课程学习者的一份实验报告文档,对应西南交通大学胡晓鹏老师课程,适合需要完成Linux环境熟悉、C程序编译调试及内核状态观测实验的学生参考。内容覆盖Shell常用命令与重定向/管道、vi或gedit编辑配合gcc/gdb调试,以及通过/proc观察cpuinfo、version、stat、uptime并编程读取meminfo、loadavg、interrupts等;目录还延伸至软中断通信、FCFS与RR进程调度和线程同步实验,可作为实验过程记录、结果整理与报告排版的样例。资源包共1个docx文件,约1.48MB,结构完整、便于直接查阅。已有403人学习,适合用来对照实验要求、梳理解析思路和查漏补缺。

1. 第一次打开虚拟机,黑底白字的光标在闪,然后呢

很多人对 Linux 操作系统的第一印象,就停在虚拟机启动后的那一屏:没有桌面图标,没有开始菜单,光标一闪一闪,鼠标点了也没反应。其实这里就是我们要开始熟悉的地方——它是某高校软件工程专业的一份操作系统实验报告,评分 95 分,按顺序排了八个实验:熟悉 Linux 目录和命令、用 gcc 编译 C 程序、观察 /proc 文件、写 fork 和软中断、比对进程调度策略,一直做到内存管理、系统调用和缺页统计。它解决的正是新手最难受的那个问题:课上听了一堆概念,却不知道代码跑在哪、命令在干什么。适合刚开这门课、或者想补一遍 Linux 底子的零基础同学,顺着顺序做一遍,比看十篇概念文章更实在。

2. 为什么第一步是打通命令行:Linux 的地基只有三块

在动手敲命令之前,得先搞清楚这一章在解决什么问题。很多新手以为学 Linux 就是背命令,背完ls、cd、ps就算入门了,结果一换环境就懵。真正的地基其实是三块:知道文件放在哪里、知道怎么用终端跟系统对话、知道一段 C 代码怎么从文本变成能跑的程序。这三块通了,后面 fork、信号、调度这些听起来很玄的东西才有落脚点。这份报告的前两个实验,做的恰好就是这三件事。

2.1 目录结构:为什么 Linux 没有 C 盘 D 盘

打开 Windows 我的电脑,你看到的是 C 盘、D 盘、E 盘,每个盘各自一棵树。Linux 不是这样,它只有一棵树,树根是一个正斜杠/,所有东西都挂在下面,包括你的 U 盘、第二块硬盘,都是挂到某个目录里去。这个差异第一次看会很别扭,但习惯之后反而更好记,因为位置是固定的,你永远知道去哪找。

刚上手时记住下面这几个目录就够了,不用一次背全:

目录放什么新手什么时候会用到
/bin最常用的命令程序敲ls、cp时,实际跑的就是这里的文件
/home每个用户自己的家目录你的代码、笔记基本都在这,路径形如/home/用户名
/etc系统和软件的配置文件改网络、改服务配置时来找
/lib程序运行需要的库文件编译时报“找不到库”,先想到它
/proc内核和进程的实时信息下一章重点讲,先记个位置
/tmp临时文件,重启可能被清放测试用的中间文件
/media自动挂载的 U 盘、光驱插上设备后去这里找
/opt额外安装的大型软件装第三方工具时常见
/lost+found非正常关机后的碎片恢复断电重启后可能会看到

路径分隔符也别搞混:Linux 用正斜杠/,Windows 用反斜杠\。写路径时敲反了,命令会告诉你“没有那个文件或目录”,这就是新手第一次遇到的小问题,记住符号方向就不会再犯。

还有一个概念叫绝对路径和相对路径。以斜杠开头的是绝对路径,比如/home/user/oslab,从根开始数;不以斜杠开头的是相对路径,比如oslab/hello.c,从你当前所在的目录开始数。搞不清自己现在在哪儿,敲一个pwd就打印出来了,这个命令后面会反复用到。

2.2 先把五个命令用熟:cd、ls、pwd、ps、chmod

命令不用背一箩筐,先让这几个上手,其余等用到再查。查的办法就是man,比如man ls会翻出这个命令的完整说明书,按q退出。新手最常犯的错是把man当装饰,其实它是你最好的老师,比搜索引擎快。

# 看看我现在站在哪个目录 pwd # 列出当前目录下的文件,加上 -l 看权限和大小,加上 -a 看隐藏文件 ls -la # 回到家目录,cd 后面不跟参数就是回家的意思 cd ~ # 新建一个实验目录并进去,-p 表示父目录不存在就一起建 mkdir -p ~/oslab cd ~/oslab # 看看系统里现在有哪些进程在跑 ps -ef # 给文件加上可执行权限,755 是最常见的组合 chmod 755 hello

ps -ef这一条尤其值得多敲几次,它会列出当前系统所有进程的编号、父进程编号和启动命令。后面做 fork 实验时,你要靠它确认自己创建的子进程到底有没有活下来。chmod的数字含义也别死记:三位数字分别对应文件所有者、同组用户、其他人,读是 4、写是 2、执行是 1,加起来 7 就是全权限,5 是读加执行。第一次看不懂没关系,敲多了自然就顺。

还有两个机制值得早点认识:重定向和管道。>把命令输出写进文件,>>是追加而不是覆盖;|把前一个命令的输出当成后一个命令的输入。比如cat /proc/cpuinfo | head -20,意思是先读出 CPU 信息,再只取前 20 行。这两个符号在后面的实验里会一直出现,早一点熟悉能省很多事。

提示:>是覆盖写,一个手抖就可能把已有文件清空。想追加内容用>>,写之前先ls确认目标文件存在与否。

2.3 第一个能跑通的例子:vim 写、gcc 编、./跑

概念说再多不如跑一次。下面这六步,是这份报告第一个实验的完整流程,也是你在这台机器上跑通的第一个程序。

第一步,确认编译器在不在。

# 打印 gcc 版本,能看到版本号就说明环境齐了 gcc -v

如果提示“command not found”,说明这台机器没装编译器,需要先装。报告里用的是带图形界面的发行版,通常自带 gcc;换成精简版系统就得自己补上。

第二步,写代码。用 vim 打开一个新文件。

vim hello.c

进去之后按i进入插入模式,把下面这段代码敲进去(或者粘贴进去):

/* hello.c —— 第一个在 Linux 下编译运行的程序 */ #include <stdio.h> /* 引入标准输入输出,printf 要用 */ #include <unistd.h> /* 后面实验里 getpid 这类函数来自这里 */ int main(void) { /* getpid() 返回当前进程的编号,先记住这个用法,后面 fork 实验还会遇到 */ printf("hello, my pid is %d\n", getpid()); return 0; }

敲完按Esc退出插入模式,再输入:wq回车,文件就保存并退出了 vim。这里有个新手高频翻车点:按Esc之后如果光标还在闪但打不了字,说明你还在普通模式,这是正常的,直接打冒号加命令就行。想不保存退出是:q!。

第三步,编译。

# -o 后面跟的是输出文件名,不写的话默认叫 a.out gcc hello.c -o hello # 加上 -Wall 会把警告都打出来,新手强烈建议一直带着 gcc -Wall hello.c -o hello

第四步,运行。

# ./ 表示“当前目录下的这个文件”,不写 ./ 系统会去 PATH 里找,找不到 ./hello

屏幕上打出hello, my pid is 12345这样的结果,第一个程序就跑通了。整个过程里,gcc做的是把人类能读的 C 代码翻译成机器能执行的指令,中间会经过预处理、编译、汇编、链接四步,入门阶段不用深究,只要知道报错通常出在哪一步就行:报undefined reference是链接阶段找不到函数实现,报expected ';'是语法写错了。

第五步,学会看报错。故意把printf后面的分号删掉再编译一次,看看 gcc 怎么说。它会告诉你文件名、行号、哪一行有问题,这三个信息足够定位九成的低级错误。很多人一看到满屏红字就慌,其实只需要看第一行报错,后面的往往是连锁反应。

第六步,简单调试。编译时加-g保留调试信息,然后用 gdb 跑:

# -g 让可执行文件带上调试符号 gcc -g hello.c -o hello # 进入调试器 gdb ./hello

进去之后常用四招:break main在 main 函数开头下断点,run开始跑,next单步往下走,print 变量名看变量当前的值,最后quit退出。这份报告的实验要求里明确写了“熟悉调试方法”,指的就是这几步。新手容易卡在“程序跑完直接退出了,没机会看变量”,原因就是没下断点,补上break就好。

三块地基到这里就铺完了:知道东西在哪、知道怎么跟系统对话、知道代码怎么变成能跑的文件。后面的实验,本质上都是在这个地基上盖东西。

3. 把 /proc 当成一份体检报告:读懂系统的实时状态

进入第二个实验,位置在/proc这个目录。这一章要解决的是一个很实际的问题:系统卡了、内存满了、CPU 跑满了,你怎么在不装任何第三方工具的情况下看出问题在哪。答案就藏在/proc里,它相当于系统给自己开的一份实时体检报告,随时翻,随时是最新的。

3.1 /proc 是什么:内核开在文件系统里的一个窗口

/proc不是硬盘上的真实目录,它里面的文件不占磁盘空间,你读的时候才临时生成内容。这个设计挺巧妙:内核本来是一大堆内存里的数据,普通人没法直接访问,于是内核把这些数据包装成“文件”,你用cat一读,就等于在向内核问问题。

打个比方,汽车仪表盘不是油箱本身,但油量表能告诉你还剩多少油。/proc就是这个仪表盘,它不生产数据,只负责把内核里已有的状态显示出来。这也是为什么它叫虚拟文件系统——看起来是文件,实际是接口。

里面有两类内容值得区分。一类是固定名字的文件,比如/proc/cpuinfo、/proc/meminfo、/proc/uptime,每个对应一种系统状态。另一类是纯数字命名的子目录,比如/proc/1、/proc/3148,这些数字就是进程号 PID,目录里面装着这个进程的详细信息:它开了哪些文件、用了多少内存、命令行是什么。换句话说,系统中的每个进程,在/proc下都有自己的一间档案室。

为什么入门阶段要学这个?因为它是排查问题的常用手段。程序卡住了,cat /proc/进程号/status看看状态;怀疑内存泄漏,看看meminfo的变化;想知道机器开了多久、负载多高,uptime一眼就够。学会读这些文件,你排查问题就从猜变成了看。

3.2 先动手 cat 四个文件:cpuinfo、version、stat、uptime

先别写代码,用最简单的cat把四个文件读一遍,建立直观印象。注意每个命令后面我都加了head或者grep,因为有些文件内容特别长,直接cat会刷屏好几屏,新手经常被这个吓到。

# CPU 信息,前 20 行足够看出型号、核数、主频 cat /proc/cpuinfo | head -20 # 只关心核数的话,数一数 processor 出现了几次 grep -c "processor" /proc/cpuinfo # 内核版本信息,第一行通常是发行版和内核版本号 cat /proc/version # 系统启动至今的秒数,以及最近 1/5/15 分钟的平均负载 cat /proc/uptime # 进程状态统计和 CPU 时间分配,内容很长,先看前几行 cat /proc/stat | head -10

这四个文件各管一摊。cpuinfo告诉你硬件底子,多少个逻辑核、什么型号、缓存多大。version告诉你内核版本,报告里提到过一个老说法:偶数结尾是稳定版、奇数结尾是开发版,这条规律现在的内核早就换了编号规则,当作历史故事听听就行,别拿它去判断手上的系统。uptime的第一个数字是开机秒数,除以 3600 就知道开了几小时;后面三个数字是平均负载,粗略理解成“过去一段时间平均有多少个任务在排队等 CPU”,如果这个数长期大于你的核数,就说明机器忙不过来了。stat更细,第一行cpu后面那串数字是各种状态下的累计时间片,所有核加在一起。

看这些文件的时候,有个心态上的调整很重要:不用一次看懂全部字段。新手最容易犯的毛病是从第一行开始逐字研究,看了半小时还没看到有用的部分。正确的做法是先带着问题去读——我想知道几核?我想知道开了多久?我想知道负载高不高?带着问题找字段,找到就走。剩下的字段等以后真用得上再回来查。

提示:/proc里的文件大多是只读的,但确实有少数可以写,写进去会改变内核行为。入门阶段只读不写,别拿生产环境做实验。

3.3 写一段 C 程序,把 meminfo 读出来

光用cat是被动看,报告的要求是编程实现对meminfo、loadavg、interrupts、filesystem这几类信息的观测。说白了就是用 C 语言打开这些文件,把内容读出来自己处理。这一节就走通第一个:读meminfo。

思路很简单,三步:用fopen打开文件,用fgets一行一行读,用printf打出来。真正要留意的是路径写对、文件指针判断空、读完关闭。

/* read_meminfo.c —— 读取并打印 /proc/meminfo 的前若干行 */ #include <stdio.h> #include <string.h> int main(void) { FILE *fp; /* 文件指针,代表打开的这个文件 */ char line[256]; /* 缓冲区,一次装一行,256 字节够用 */ int count = 0; /* 计数器,控制只打印前 10 行 */ /* 以只读方式打开虚拟文件,路径必须是绝对路径 */ fp = fopen("/proc/meminfo", "r"); if (fp == NULL) { /* 打开失败必须判断,否则后面会崩 */ printf("open /proc/meminfo failed\n"); return 1; } /* fgets 每次读一行,读到文件末尾返回 NULL,循环自然结束 */ while (fgets(line, sizeof(line), fp) != NULL && count < 10) { printf("%s", line); /* line 里已经带了换行符,不用再加 \n */ count++; } fclose(fp); /* 打开就要关,养成习惯 */ return 0; }

编译运行的方式和上一章一样:

gcc -Wall read_meminfo.c -o read_meminfo ./read_meminfo

输出大致是MemTotal、MemFree、MemAvailable、Buffers、Cached这些键值对,单位是 kB。看这几个字段能推出不少东西:MemTotal是总内存,MemAvailable是真正还能拿来用的估计值,SwapTotal和SwapFree能看出交换分区用了多少。新手最容易误读的是MemFree,看到它很小就以为内存快满了,其实 Linux 会把空闲内存拿去做缓存,MemAvailable才更接近真实情况,这个认知差是很多人第一次看meminfo时留下的问题。

把这段程序稍微改一改,就能覆盖报告要求的另外几个文件。读loadavg只需要把路径换掉,输出会变成三个小数加两个数字,分别是 1、5、15 分钟平均负载和当前运行的、总的任务数。读interrupts路径也换掉,输出按 CPU 核分列,每一行是一种中断源被处理的次数,初学者看个热闹就行。读filesystems更简单,那个文件本身就是一行一个文件系统名字,纯粹列清单。

真正的练习点在于改程序:把打印行数从 10 改成全部、把每行按冒号切开只留左边名字、把MemAvailable的数值单独取出来做判断。每改一次都编译运行看结果,比干看代码有效得多。这份报告在这一部分还要求比较gcc、gdb和图形化 IDE 的差别,实际做下来感受很直接:IDE 点一下就能跑,但要看系统底层状态、要写只有几行的验证程序,命令行反而更快,不用等界面加载。

4. 进程、信号、调度:三个概念怎么真正上手

做到这里,命令行顺了,/proc也读过了,接下来进入这门课真正的核心区。这一章的三个东西——创建进程、进程之间发信号、多个进程抢 CPU——是后面所有实验的地基,也是考试和面试最常问的部分。名词听起来抽象,但只要各写一个能跑的小程序,理解起来会快很多。

4.1 fork 是什么:一次调用,两个返回值

fork()是 Linux 里创建新进程的唯一方式,它的行为第一次见会觉得反直觉:调用一次,返回两次。父进程里它返回新创建子进程的 PID,子进程里它返回 0,如果创建失败返回负数。所以标准写法永远是先判断返回值大于 0 还是等于 0,再决定这段代码给谁跑。

理解它的关键是建立一个画面:程序执行到fork()这一行时,就像走到了一个岔路口,系统把当前进程原样复制一份,父子两个从同一个位置继续往下走,但各自拿着不同的返回值,于是走进了不同的分支。报告里的进程树、父子孙三层结构,全是靠这个岔路口一层层分出来的。

先写个最小可运行版本:

/* fork_demo.c —— 观察一次 fork 之后发生了什么 */ #include <stdio.h> #include <unistd.h> /* fork、getpid、getppid、sleep 都在这里 */ #include <sys/wait.h> /* wait 需要这个头文件 */ int main(void) { pid_t pid; /* pid_t 是专门存进程号类型,本质是个整数 */ pid = fork(); /* 关键的一行:从这里开始变成两个进程 */ if (pid < 0) { /* 返回负数说明创建失败 */ printf("fork failed\n"); return 1; } else if (pid == 0) { /* 返回 0,说明这是子进程 */ printf("child : my pid=%d, my parent=%d\n", getpid(), getppid()); } else { /* 返回正数,这是父进程,值是子进程 PID */ printf("parent : my pid=%d, my child=%d\n", getpid(), pid); wait(NULL); /* 等子进程结束,把它回收掉 */ } return 0; }

编译运行后,两行输出谁先谁后是不确定的,这正好是本章第一个重点:进程是并发执行的,调度顺序由内核决定,你不能假设父进程一定先打印。报告里为此专门用了一个lockf给输出加锁,目的就是防止两个进程同时往屏幕上写,字符交错成乱码。新手看到输出顺序跟预想不一样就以为程序写错了,其实这是正常现象。

wait(NULL)这一句也别漏。父进程如果不等子进程就退出,子进程会变成孤儿被系统接管;反过来子进程先退出而父进程不回收,子进程会留下一个僵尸状态,在ps里显示成defunct。这两种情况在后面做三层进程树实验时都会遇到,先把wait当成固定动作写上去,能省掉很多莫名其妙的排查。

4.2 软中断通信:Ctrl+\ 到底做了什么

第二个核心概念是信号。它本质上是软件层面的一种中断通知:某个进程出事了、或者有人想让它停下来、或者定时器到点了,内核就给它递一个信号。进程收到信号后有三种选择:用默认动作处理(多数是终止)、忽略掉、或者自己注册一个函数来决定怎么办。

报告里那个实验,操作方式是按下Ctrl+\,看起来像键盘在起作用,实际上链路是这样的:终端驱动捕捉到这个组合键,向前台进程组里的进程发送SIGQUIT信号,父进程事先用signal()注册了处理函数,函数把全局标志位一改,父进程的主循环发现标志变了,就去给各个子进程发信号让它们退出,最后自己退出。整个过程里,键盘只是起点,真正的通信发生在进程之间。

把这条链路拆成代码,核心就三个函数:signal(信号编号, 处理函数)注册,kill(进程号, 信号编号)发送,处理函数里改标志。下面是一个简化版本,父进程创建子进程,等信号来了再通知子进程退出:

/* signal_demo.c —— 父进程收到 SIGQUIT 后,通知子进程退出 */ #include <stdio.h> #include <signal.h> /* signal、kill、SIGQUIT 都在这 */ #include <unistd.h> #include <sys/wait.h> volatile sig_atomic_t stop_flag = 0; /* 处理函数里只改这种类型,安全 */ /* 信号处理函数:收到 SIGQUIT 时被调用,只做一件事——改标志 */ void on_quit(int signo) { stop_flag = 1; } int main(void) { pid_t pid = fork(); if (pid == 0) { /* 子进程:安静地等父进程通知,这里用循环模拟等待,入门先这样写 */ printf("child %d is waiting...\n", getpid()); while (!stop_flag) { sleep(1); } printf("child %d exits\n", getpid()); } else { signal(SIGQUIT, on_quit); /* 注册:收到 SIGQUIT 就调 on_quit */ printf("parent %d is waiting for Ctrl+\\ ...\n", getpid()); while (!stop_flag) { sleep(1); /* 主循环里不停检查标志位 */ } kill(pid, SIGTERM); /* 通知子进程退出,这里用 SIGTERM */ wait(NULL); /* 回收子进程,别留僵尸 */ printf("parent %d exits\n", getpid()); } return 0; }

跑起来之后,在终端里按Ctrl+\,两个进程会依次退出。

参数上要留个心:报告里的示例代码用了signal(16, stop)这种写法,16 和 17 是 SIGUSR1、SIGUSR2 这类用户自定义信号在某些平台上的编号。直接写数字能跑,但换一台机器或者换一种架构,编号可能对不上,这就是典型的可移植性问题。入门阶段建议养成写宏名的习惯,SIGUSR1、SIGUSR2比16、17清楚太多。

还有一点值得提前知道:用signal()加忙等循环是教材里常见的教学写法,能跑通、能看懂原理,但在实际项目里更推荐sigaction()和sigsuspend(),因为前者在不同系统上的行为细节有差异。入门先用简单写法把链路走通,等理解了“注册—等待—发送—处理”这四个动作,再去看更严谨的接口,会顺很多。

4.3 进程调度:FCFS 和 RR 到底在比什么

第三个概念是调度。CPU 只有一个(或者说核心数有限),一堆进程都想跑,谁先跑、跑多久,就是调度策略要回答的问题。报告里比对的是两种最基础的策略:FCFS 先来先服务,以及 RR 时间片轮转。

FCFS 的规则简单到不用解释:谁先到谁先跑,跑完为止。它的毛病也很明显,如果先来的那个任务要跑很久,后面的短任务全得干等,平均等待时间被拉得很长,这个现象有个专门的名字叫护航效应。

RR 的规则是给每个进程一个固定的时间片,跑完一个片就排到队尾去,下一个上。这样短任务不用等长任务完全跑完,响应快,代价是多了一次上下文切换的开销。时间片设得太小,切换开销占比高;设得太大,就退化成 FCFS 了。

光看文字容易糊,手算一遍最清楚。假设三个进程几乎同时到达(到达时间都是 0),服务时间分别 24、3、3,单位是毫秒:

策略执行顺序平均等待时间平均周转时间
FCFSP1 → P2 → P3(0 + 24 + 27) / 3 = 17(24 + 27 + 30) / 3 = 27
RR(时间片 4)P1 → P2 → P3 → P1……明显更低也更低

FCFS 那一行里,P2 明明只要 3 毫秒,却足足等了 24 毫秒才开始,这就是护航效应的具体形态,那个 17 毫秒的平均等待时间看着就让人难受。换成时间片 4 的轮转,P2 在第一轮就能跑完,等待时间立刻降下来。这张表建议自己拿纸重算一遍,比自己盯着定义看有用得多。

报告里做这个实验的方式是创建一个工程,然后分别验证两种策略下的执行顺序和各项时间指标。新手在这里最容易出的问题是把“到达时间”和“开始时间”搞混,算等待时间要用开始时间减到达时间,不是开始时间本身。另外,周转时间是完成时间减到达时间,跟等待时间不是一回事,两个指标算错一个,后面整张表都是错的。

注意:手算调度指标时,先把时间轴画成一条横线,把每个进程的执行区间标上去,再回头填表。直接心算很容易把累计时间加串。

5. 新手最容易出问题的五个地方:现象、原因和补救

前面的例子都是顺利路径,真上手时不太可能一次就通。这一章把新手在这类实验里最常遇到的几个问题集中列出来,每个都按“看到什么—为什么—怎么办”说清楚。遇到问题时,先来这里对一对,比盲目搜索会快很多。

5.1 编译报错:undefined reference to 'fork'

现象很典型:代码在编辑器里写得好好的,语法检查也过了,一编译就冒出一堆链接错误,说找不到fork、wait、signal这些函数的定义。

原因基本只有一个:你在 Windows 环境里编译。fork是 POSIX 标准里的接口,Windows 的 C 运行库根本没有这个函数,编辑器自带的编译器自然找不到。报告正文里提到的那次经历就是这个问题——在 Windows 的 IDE 里写带fork()的代码,一直报错,怎么改都不行。

解决办法是让代码回到 Linux 环境里编译。要么把源文件拷进虚拟机再gcc,要么直接在虚拟机的终端里写。中间传输文件这一步,用共享文件夹或者文件传输工具都行,重点是别在 Windows 上编译依赖 POSIX 接口的代码。判断标准很简单:代码里出现了fork、getpid、signal、lockf、pipe这类函数,就必须在 Linux 下编译。

5.2 输出顺序乱套、两个进程的打印粘在一起

现象是屏幕上出现类似child parP1ent isprocess 3148 killed这样的乱码,或者子进程的输出跑到父进程前面去了,跟代码里的书写顺序对不上。

原因有两层。第一层是并发本身导致的:父子进程谁先被调度是不确定的,你没法靠代码顺序控制输出顺序。第二层是两个进程同时往同一个终端写,写操作不是原子的,字符会交错。

解决办法分两种。如果只是想让输出别乱,用报告里的做法,在打印前后加lockf(1, 1, 0)和lockf(1, 0, 0),把标准输出锁住再写、写完解锁。如果是想让顺序可控,就在父进程里用wait()等子进程结束再继续,用同步换顺序。这两个手段解决的是不同问题,别指望一个顶两个。

5.3 程序跑完终端不回来,或者ps里多出一堆defunct

现象是程序看着执行完了,但终端一直停在那儿,不出现新的命令提示符,得按Ctrl+C才回来;或者用ps一查,发现有几个状态是Z或defunct的进程挂着不走。

原因是父进程没有回收已经退出的子进程。子进程结束后不会立刻从进程表里消失,它要等父进程调用wait()或waitpid()读取它的退出状态,才能被彻底清理。父进程不管它,它就一直挂在那儿,这就是僵尸进程。

解决办法是给每个fork()都配一个wait()。报告里的示例代码在父进程退出前连写了两个wait(0),对应它创建的两个子进程,一个都不能少。三层进程树的实验更要注意,子进程创建孙进程后,子进程自己也得wait一次,否则孙进程也会变僵尸。养成“有 fork 就有 wait”的配对习惯,这类问题基本不会再出现。

5.4 虚拟机连不上网,装不了东西

现象是宿主机上网正常,虚拟机里ping不通外网,包管理器装不了软件,或者反过来宿主机访问不到虚拟机的服务。

原因多半出在虚拟机的网络模式上。常见的三种模式行为差别不小:桥接模式让虚拟机像是在同一个局域网里的另一台机器,能双向访问,但跟别人抢地址时可能冲突;NAT 模式让虚拟机借宿主机的地址出去访问,能上网但外面主动连不进来,冲突风险低得多;仅主机模式则基本隔离开,只跟宿主机通信。

解决办法是入门阶段先选 NAT,能上网、不容易冲突。改完之后如果还不通,把虚拟网卡先禁用再启用一次,或者重启一下网络服务。这类问题不用深究虚拟交换机的原理,知道三种模式的差别、知道默认选 NAT,就足够应付实验了。

5.5 vim 里按什么都没反应,退也退不出来

现象是进了 vim 之后敲键盘没反应,或者屏幕下方出现一堆奇怪字符,想退出又不知道按什么。

原因是 vim 是模式化编辑器,你当前处在的模式决定了按键的含义。刚进去是普通模式,按字符不会插入文字,i才进入插入模式;写完之后按Esc回到普通模式,才能敲冒号开头的命令。

解决办法是把四个组合记牢:i进入编辑,Esc回到普通模式,:wq保存退出,:q!不保存强退。迷路的时候连按两次Esc再打冒号,基本能救回来。报告中还记了一个实用技巧:想另存成新文件名,在普通模式下敲:w 新文件名即可,不用退出重来。

6. 走完这八个实验之后:下一步该往哪儿学

八个实验做完,你手上会有这些东西:能在终端里自由活动、能写能编能调 C 程序、能读/proc看系统状态、能创建进程并发信号、能算调度指标。这是一个很扎实的起点,但离“会用 Linux 做开发”还有距离。接下来怎么走,我给一条按顺序来的路线,别跳步。

阶段学什么用什么检验自己
第一步Shell 脚本:变量、判断、循环、管道组合写个脚本自动备份目录、自动统计日志行数
第二步系统调用:open/read/write、stat、dup2不用printf,只用write把文件内容打到屏幕
第三步线程与同步:pthread_create、互斥锁、信号量两个线程同时累加一个计数器,用锁保证结果正确
第四步内存管理:分页、缺页、malloc背后的brk/mmap写个程序读/proc/self/status观察内存变化
第五步自己写一个小调度模拟器输入一组进程,输出 FCFS 和 RR 的各项指标对比

第一步之所以放 Shell 脚本,是因为它成本最低、见效最快。你在实验里已经敲了几百条命令,把它们串成脚本只是把命令写进文件加个执行权限的事,但那一步跨过去之后,重复劳动就能自动化了。第二步开始碰系统调用,你会突然发现printf背后就是write,ls背后就是stat加目录读取,之前那些黑匣子一个个被打开,这种感觉很上瘾。

第三步的线程同步,是这份报告里实验四的主题,也是新手最容易写错的地方。不加锁的计数器在单核上可能跑出正确结果,换到多核就崩,这种“我这儿明明是对的”的情况最容易让人怀疑自己。第四步和第五步属于把实验里“读懂代码”变成“自己写出来”,难度上了一个台阶,但做过前面三步之后并不会觉得突兀。

我自己刚开始学的时候,遇到过最大的问题是在 Windows 里写好代码再传进虚拟机,结果那段依赖fork的代码怎么都编译不过,折腾了大半天才反应过来是平台不对。从那以后我每次动手前都会先确认三件事:代码在哪个系统上编译、用的哪个编译器、依赖的头文件在这个平台上存不存在。这三问花不了半分钟,却能省掉一整晚的排查。

希望这份路线对你有用,也希望你别在第一个黑屏光标前停太久——敲下去,就开始了。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/28 2:09:11

Pygame游戏循环与Surface/Rect核心机制详解

简介&#xff1a;这份Pygame教程面向已掌握Python基础、希望入门2D游戏开发的读者&#xff0c;系统讲解游戏主循环、Surface对象、Event事件、Rect区域等核心概念&#xff0c;帮助读者从零搭建可运行的游戏程序。教程采用“知识点示例”的编写方式&#xff0c;覆盖Display显示模…

作者头像 李华
网站建设 2026/10/28 2:09:09

Ubuntu 20.04装IC617/Spectre/Calibre19避坑

简介&#xff1a;这份文档面向需要在 Linux 桌面环境中搭建集成电路设计与验证流程的工程师及高年级学生&#xff0c;聚焦 Ubuntu 20.04 上安装 Cadence IC617&#xff08;含 HotFix&#xff09;、Spectre18、Calibre19 与 XCELIUMMAIN_18 的完整实现过程。内容按系统准备、依赖…

作者头像 李华
网站建设 2026/10/28 2:07:53

基于SSM+SpringBoot的新能源汽车充电桩管理系统源码解析

简介&#xff1a;这套新能源汽车充电桩系统毕业设计资料面向高校计算机相关专业学生与课程设计开发者&#xff0c;围绕SSM/SpringBoot框架、B/S模式与MySQL数据库展开&#xff0c;覆盖首页、个人中心、用户管理、维修员管理、电桩类别管理、充电桩管理、充电桩报修与维修回复、…

作者头像 李华
网站建设 2026/10/28 2:07:35

基于LSTM与模糊综合评价的高校教学质量量化方案

简介&#xff1a;这份资源是2022年江苏省研究生数学建模科研创新实践大赛B题的完整赛题PDF&#xff0c;面向备战研究生数学建模竞赛的高校学生与指导教师&#xff0c;聚焦高校课堂教学质量数据的量化分析问题。赛题围绕专家评语量化、课程分类、教学团队授课质量排序、专家评价…

作者头像 李华
网站建设 2026/10/28 2:07:27

植物叶片图像识别:OpenCV轮廓处理全流程与抗干扰特征提取

简介&#xff1a;本资源是一份面向Python初学者与计算机视觉入门者的OpenCV图像处理实践教程&#xff0c;聚焦植物叶片识别这一典型形状分析任务&#xff0c;帮助读者掌握图像分割、轮廓检测与几何特征提取等核心技能。资源为单个Word文档&#xff08;.doc&#xff09;&#xf…

作者头像 李华
网站建设 2026/10/28 2:07:26

会议纪要哪个软件总结精准?7款主流工具统一环境实测与选型清单

一、先把痛点说清楚&#xff1a;为什么你导出的纪要靠反复听录音才能补全会开完了&#xff0c;录音还在手机里躺着。等到真要整理纪要&#xff0c;才发现转出来的文字是一大段缺少逻辑结构的流水账&#xff0c;谁说的、结论是什么、谁负责、什么时候交&#xff0c;全都得自己从…

作者头像 李华