CSCI3150 HW01 · 2026T1 · Shell 分级提示
课程:CSCI3150 Introduction to Operating Systems · 2026T1 本份:Assignment One,按实现单元逐步挑战 定位:自己完成管道 Shell;卡住时只打开下一层提示,不提供完整代码或可直接拼接的伪代码。
先确认这次要做什么
依据 HW01 题目(本地课件) p.1 和教师代码包 Readme。这是 HW01,不是 Lab02。截止时间为 2026-10-13 23:59。
- 在
simple-execute.c的shell_execute(char **args, int argc)中支持 0–3 个管道,即最多 4 条命令。每个管道连接相邻命令的 stdout 和 stdin。 - 题目保证每个
|前后至少有一个空格;ls|不在测试范围。无管道命令与独立EXIT必须仍能工作。 - 父子进程都要关闭无用管道描述符;启动全部管道命令后才等待,并在下一次提示符前等完每个孩子。
- 可以在指定文件内添加辅助函数与头文件;不要修改
simple-shell.c或Makefile。只提交simple-execute.c,注释中列实际成员姓名与 SID;最多三人一组,一名成员提交一份。 - 题目要求使用 fork、pipe、execvp,以及 dup 或 dup2;用 wait 或 waitpid 等待。要在课程要求环境编译运行。原题这一页没有给出完整环境规格,不能仅凭本机编译就断言评分环境通过。
本页不包含已完成作答、姓名或学号。下面的练习与测试方法不是隐藏评分用例,也不是本轮已执行的测试记录。
建议怎么推进
先读懂接口、保住单命令行为,再挑战两个命令,最后扩到三、四个命令。每完成一个单元先测试,再决定看下一层提示。三级提示嵌套收起,不会一次性展开整套实现。
1 · 输入接口与计数
目标:搞清调用者传入的参数数组,以及返回值怎样影响外层循环。
提示 1 · 切入点
先读教师 main 中调用执行函数的几行,再读 get_line_args 的返回位置。不要从变量名 argc 猜约定。
提示 2 · 局部路线
在自己的草稿中记录每个槽位的地址是否为空、非空时的字符串、传入计数;不要用字符串格式打印空指针。用同一条短命令手画一份表。
提示 3 · 下一步与自检
教师解析器把末尾 NULL 槽计入返回计数。检查你的循环是否会把这个槽当成字符串;另看 main 怎样使用 shell_execute 的负返回值,不要把它与子进程退出状态混为一谈。
2 · 保住无管道与独立 EXIT
目标:改动后仍可运行原先单条命令,并按约定退出输入循环。
提示 1 · 切入点
先给“运行外部命令”和“结束这个 Shell”分配不同责任。哪一种需要查找可执行程序?
提示 2 · 局部路线
对照教师原函数,标注哪些语句只应由父进程执行、哪些只应由子进程执行;思考 exec 成功后旧代码还会不会继续。
提示 3 · 下一步与自检
用 echo EXIT 与独立 EXIT 分开验证。遇到不存在的命令时,观察是否意外出现第二个 Shell;它能帮你定位子进程失败路径是否回到了输入循环。
3 · 命令边界与参数段
目标:让每段命令只收到自己的程序名与参数,不把管道符作为普通选项传进去。
提示 1 · 切入点
目前教师解析器只做空白拆词。拿含一个独立管道符的输入,在纸上圈出属于同一个程序的词。
提示 2 · 局部路线
查看 execvp 对参数向量结尾的约定,再为每一段标注起点和终点。先设计你怎样表示这些边界,不急着创建孩子。
提示 3 · 下一步与自检
用只打印段信息的调试版本检查:每个原始非分隔词恰好归入一段,分隔符不落入任何段,最后一段也有合法结尾。你仍需要自己决定怎样在指定文件内组织这些信息。
4 · 理解管道与描述符
目标:让两个命令的数据通过管道传递,而不是都直接输出到终端。
提示 1 · 切入点
先画两个进程的标准输入和标准输出,只画数据应该流向哪里。哪些流应保持继承的连接?
提示 2 · 局部路线
把“数组下标”“文件描述符整数”“内核管道对象”分三层画。pipe 返回的端点编号不能由你假设为固定值。
提示 3 · 下一步与自检
单独挑一个需要改变的标准流,核对 dup 或 dup2 的源、目标语义;复制完成后原编号还是否占有引用?画完一个端点再继续,不先写全套首中末分支。
5 · 从两个命令扩到四个
目标:覆盖题目允许的 0–3 管道,不把双命令特例复制成许多互不一致的分支。
提示 1 · 切入点
先数相邻命令之间有多少条数据连接。单命令的退化情况也要画出来。
提示 2 · 局部路线
只选一个中间命令,分别追问它从哪里读、往哪里写。不要同时展开所有命令的实现。
提示 3 · 下一步与自检
为你自己的表示法检查首段、末段是否会访问不存在的邻居;再用同一检查办法审视只有一段的情况。孩子启动数量也应与命令段数量相符。
6 · 关闭引用与 EOF
目标:数据处理结束后消费者能结束,不因无用端点引用一直等待。
提示 1 · 切入点
先解释“现在没有数据”和“以后不会再有数据”有什么不同;消费者靠哪个条件知道输入结束?
提示 2 · 局部路线
只追踪一条管道:为父亲和每个孩子列出仍持有的读写端引用。fork 后继承的引用也要计入,不能只记计划使用的端点。
提示 3 · 下一步与自检
若消费者已读完却仍卡住,逐个排查是否还有不打算写入的进程持有写端。修改整数变量不等于关闭描述符;用你的引用表检查清理,不凭“这个进程不会写”推断 EOF。
7 · 启动与等待
目标:遵守全部启动后再等待、下次提示符前等完全部孩子的要求。
提示 1 · 切入点
给管道缓冲区想象一个有限容量。如果生产者还没等到消费者启动就把它写满,会怎样?
提示 2 · 局部路线
在纸上给父进程画时间线,标出孩子创建与等待的位置;再把一个很慢的命令放在不同阶段,找可能提前返回提示符的路径。
提示 3 · 下一步与自检
检查你记录的是实际成功创建的孩子,而不是计划数量。等待返回值和 status 的含义不同;调试时记录已经回收的 PID,确认没有少等或重复把失败当完成。
8 · 失败后的恢复与提交自查
目标:失败不污染下一条输入,交付范围符合题目。错误处理是工程自检,不是额外杜撰的评分项。
提示 1 · 切入点
挑一个系统调用失败点,停在该行,列出到此已经拥有的资源;不要一次写完所有失败分支。
提示 2 · 局部路线
选 execvp 失败做第一轮检查:此时运行失败路径的是谁?结束它后,父进程应怎样继续自己的交互职责?
提示 3 · 下一步与自检
再为你选择的另一个失败点检查“哪些端点已打开、哪些孩子确实存在”。提交前用原教师文件重新编译,确认只交指定源文件、占位署名已换为实际成员信息。本提示页不会代替你提交。
9 · 用观察证据定位卡点
目标:不靠反复加sleep猜顺序,也不靠看过完整解析才会排错。
提示 1 · 先缩小问题
找一条你自己的最短失败输入,记录症状属于没有输出、输出错误、输出正确却不结束,还是提前出现提示符。每次只改变一个输入因素,观察症状是否随之变化。
提示 2 · 分清对象
在草稿上分别写进程表与描述符表:每个PID正在执行什么阶段;每个进程的哪个编号引用哪个端点。只选卡住的那根连接追踪,不急着修改全部关闭语句。
提示 3 · 用反事实检查解释
假设你怀疑某一处多余引用,先预测它若不再存在,哪个等待条件会改变,再在自己的实现中验证。若预测与实际不符,回到引用表补漏,而不是继续随机加入close。调试输出应与命令stdout分开,以免干扰下游程序的数据。
追加自查 · 先判断反例是否真的覆盖了你的假设
如果一个短输入运行正常,问自己:它是否真的让相邻程序重叠执行,还是数据太少,恰好掩盖了等待顺序问题?如果一次输出正确,问自己:所有参与者都结束了吗,还是只有某一段输出先到了?把“数据正确”“完整结束”“能够继续接收下一条输入”分开观察。
再挑一个你自己的失败场景,画出失败前已经成功建立的资源。哪些动作尚未发生,哪些引用已经交给另一个进程?不要只按正常路径的行数倒推清理范围。这里不给固定关闭表或实现代码,目的是让你能用自己的状态图解释下一次尝试。
自己运行的测试场景
以下输入供你在自己的实现里验证。先在课程环境的普通 Shell 中观察命令本身,再在 SimpleShell 输入同样的命令并比较结果、退出与提示符时机。不要输入 $$$ 前缀。每组完成后用独立 EXIT 退出;教师读取循环未正确处理 EOF,不能把关闭输入当作此框架的正常退出方式。
先按教师 Readme 用 make 和 ./SimpleShell 启动。测试时保留自己的作答副本,不覆盖已有完成文件。
| 想检查什么 | 可输入的命令 | 观察什么 |
|---|---|---|
| 无管道与恢复 | echo hello,再输入 echo again |
每条只执行一次,下一次输入仍可用 |
| 独立控制命令 | 先 echo EXIT,最后独立 EXIT |
普通参数与退出控制是否区分 |
| 一条管道 | seq 1 10 | wc -l |
与普通 Shell 比较输出与结束行为 |
| 两条管道 | seq 1 10 | tail -n 4 | wc -l |
消费者需要输入结束时会不会卡住 |
| 三条管道 | seq 1 20 | head -n 12 | tail -n 5 | wc -l |
中间阶段能否同时接收和发送 |
| 空输出 | seq 1 10 | grep NO_MATCH | wc -l |
没有匹配内容时仍能正常结束 |
| 较大有限输出 | seq 1 200000 | wc -l |
小输入偶然成功之外,是否存在启动/等待阻塞 |
| 失败后继续 | no_such_command_hw01,再输入 echo alive |
报错后是否多出 Shell、失去下一次输入 |
表中三条基础管道来自教师 Readme,其余是辅助自测,不是隐藏测试。表格中的命令可以复制;页面用表格格式转义管道符,显示值仍是普通 |。
若某条命令卡住,在另一终端检查进程关系,定位究竟是谁在读、写或等孩子;只处理这次测试启动的进程,不要用宽泛的进程名批量终止。进阶时可以在课程 Linux 环境观察系统调用和描述符,但先用自己的引用表解释现象。
想清楚后再对照
来源与证据
- HW01 题目(本地课件) p.1:范围、截止、分组、接口与提交要求。
- 教师原始代码包(本地资料):Readme、simple-shell.c、simple-execute.c;只读核对接口,没有把现有完成作答放入提示页。
- HW01 教程(本地课件) p.2–4:参数传递与进程调用背景。具体接口约定以教师源码为准。
- 原始文件保留本地;这里是练习引导,不是提交文件,也不代表这些自测已在你的当前实现上运行通过。