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额外安装的大型软件装第三方工具时常见/lostfound非正常关机后的碎片恢复断电重启后可能会看到路径分隔符也别搞混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 hellops -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 27RR时间片 4P1 → 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现象是程序看着执行完了但终端一直停在那儿不出现新的命令提示符得按CtrlC才回来或者用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背后就是writels背后就是stat加目录读取之前那些黑匣子一个个被打开这种感觉很上瘾。第三步的线程同步是这份报告里实验四的主题也是新手最容易写错的地方。不加锁的计数器在单核上可能跑出正确结果换到多核就崩这种“我这儿明明是对的”的情况最容易让人怀疑自己。第四步和第五步属于把实验里“读懂代码”变成“自己写出来”难度上了一个台阶但做过前面三步之后并不会觉得突兀。我自己刚开始学的时候遇到过最大的问题是在 Windows 里写好代码再传进虚拟机结果那段依赖fork的代码怎么都编译不过折腾了大半天才反应过来是平台不对。从那以后我每次动手前都会先确认三件事代码在哪个系统上编译、用的哪个编译器、依赖的头文件在这个平台上存不存在。这三问花不了半分钟却能省掉一整晚的排查。希望这份路线对你有用也希望你别在第一个黑屏光标前停太久——敲下去就开始了。本文还有配套的精品资源点击获取