Skip to content

util:用小程序摸清 xv6 的用户接口 ​

版本:util,提交 db9a9d8b7d82f44a61ce4e3ff165ac755a2aa862。本年任务是 sleep、sixfive、memdump、find、find 的 exec 扩展;不是旧年的 pingpong、primes、xargs。先读进程,再在 user/echo.c 和 user/ls.c 中观察风格。

sleep:一个程序如何进入镜像 ​

创建 user/sleep.c,包含 xv6 自己的类型和用户接口头文件。参数先检查数量,再把字符串转换成 ticks,最后调用 pause()。用户命令叫 sleep,内核接口却叫 pause;sys_pause 在 kernel/sysproc.c。将程序添加到 Makefile 的 UPROGS 后,构建才会把它装入 fs.img。

Ticks 是内核时钟中断计数,不应默认等于秒。任务验收重点是正确使用接口和参数,而不是编写宿主机的计时器。分别测试缺参数、0、普通正数。输出错误时使用文件描述符 2,成功时保持安静,退出状态清楚区分成功和失败。

sixfive:文本解析需要状态 ​

输入是由指定分隔符切开的 token,只有完全由十进制数字构成的 token 才能作为数;能被 5 或 6 整除的数需要输出。xv6 不能被拆出 6;/6, 可以识别出 6。实现时维护“是否已有字符、当前 token 是否仍合法、数值或余数”三个状态,遇到分隔符才结束 token。文件结束也要结束最后一个 token,不能依赖最后恰好有换行。

教学状态机:

text
开始一个 token:empty=true,valid=true
读到数字:更新数值,empty=false
读到非数字且不是分隔符:valid=false,empty=false
读到分隔符或 EOF:
    若非空且合法且满足整除条件,输出
    重置 token 状态

若担心长数字溢出,可以同时保存 %5 与 %6 余数,但仍需要保存可输出的字符或明确定义长度上限。不要为避免溢出而静默截断 token,否则会把不同输入变成同一个数。额外用 5x 6 11 30、无末尾换行、连续分隔符、多文件参数测试。

memdump:字节步长与指针步长 ​

memdump(fmt, data) 根据格式读取连续内存。i/h/c/p 分别消费 4/2/1/8 字节;s 消费一个 8 字节指针槽,再解引用指向的字符串;S 直接读取当前位置的字符串,消费其字符和终止零字节。这是“槽里保存地址”与“槽里保存内容”的区别。

例子:数据字节为 34 12 41 00,先按 h 读得到十进制 4660,再按 c 读得到 A。若先把 data 转成 int* 后做 data++,步长会变成 4 字节;应以 char* 或显式字节偏移维护当前位置。还要留意对齐:把任意字节地址强转成宽类型指针,可能产生未对齐访问;将字节复制到对齐的局部变量再解释,是更容易说明的方式。

s 与 S 是最容易写错的分支。前者打印完成后仍只前进一个指针宽度,不能前进所指字符串的长度。不要把本实验输出示例中的具体地址写死,因为链接布局会变化。

find 与 find -exec:目录是结构化数据 ​

参考 ls.c,先 open,再 fstat 判断类型。对目录逐条读 struct dirent,跳过空项以及 .、..,拼接路径后递归。固定宽度的目录名不一定自动带终止零;把名称复制到自己有空间的缓冲区后再进行字符串操作。每次递归都检查路径长度,返回前关闭 fd。

-exec 将找到的路径作为命令参数追加到 argv,并在末尾放空指针。fork 子进程执行 exec,失败后打印错误并 exit;父进程 wait 后继续搜索。把 exec 放在遍历者自身中会使整个 find 被替换,只处理第一个匹配项。

验收与反例 ​

先运行单项:make GRADEFLAGS=sleep grade;最后执行 make grade,并在 xv6 中跑 sh < findtest.sh。额外建立两层同名文件,确认既能递归也不会循环;让 exec 执行不存在命令,确认 find 能继续且没有僵尸积累。

自测:为什么 read 返回 0 之后仍可能需要输出一个数?

最后一个 token 可能没有尾随分隔符。EOF 本身是边界,必须把尚未完成的合法 token 交给与分隔符相同的结束逻辑处理。

来源:2025 util 官方任务。这些任务的教学价值分别是接口、状态机、内存表示、目录遍历和进程组合,而不只是写五个小命令。