首页 / 资讯中心 / 文章详情

MIT 6.S081 util 实验篇(lab1):sleep (easy)

MIT 6.S081 util 实验篇(lab1):sleep (easy) ★ FEATURED ARTICLE
sleep (easy)实验目标本实验是 6.S081 的热身关目的不在难度而是先把手感建立起来熟悉 xv6 实验环境——git 分支怎么切、内核怎么构建、怎么运行、怎么调试、怎么评分。写出第一个用户程序sleep体会用户程序如何通过系统调用向内核请求服务。跑通评分脚本建立写代码 → 改 Makefile → 编译 → 测试这一整套开发闭环。后续所有实验都建立在这套环境之上所以这一关虽简单但不建议跳过——把工具链跑顺后面才不会被环境问题卡住。前置知识动手前先把环境和几个核心概念理清楚。1. 实验仓库结构仓库里几个文件/目录后续会反复出现路径作用kernel/操作系统内核空间源码负责底层硬件管理与系统功能实现user/用户空间应用程序源码运行在内核提供的环境之上我们写的sleep.c就放这里Makefile构建脚本用来编译出 xv6 内核与所有用户程序镜像grade-lab-xxx评分脚本完成某个小实验或整个 lab 后运行它即可打分2. 常用命令速查切换到第一个实验分支utilgitcheckout util开发与运行相关makeqemu# 构建并启动 xv6# 退出 xv6先按 Ctrla再按 xmakegrade# 给整个 lab 打分makeGRADEFLAGSsleep grade# 只测 sleep 这一个子任务util lab 中GDB 双终端调试# 终端 1以单核 gdb 方式启动 qemumakeCPUS1qemu-gdb# 终端 2连上 gdbriscv64-unknown-elf-gdb kernel/kernel3. 系统调用与 sleep 的含义sleep 实验的核心是让你第一次亲手调用一个系统调用。这一点在 xv6 手册里有明确定位。xv6 handoutChapter 1: Operating system interfaces“xv6 provides the basic interfaces—system calls, pages, files, and directories—that Unix provides.”手册系统调用一览表Figure 1-1中对 sleep 的描述是sleep(n): Pause for n clock ticks.也就是说sleep本质是一个系统调用用户程序调用它内核让当前进程挂起n个时钟滴答tick后再被唤醒。参数单位是 tickqemu 下时钟约 100Hz所以sleep 10大致暂停零点几秒实际观感因人而异把数字调大效果更明显。关键概念用户程序自己没有暂停自己的权限——能不能睡、睡多久必须由内核来裁决。这正是“系统调用 用户态请求内核服务的唯一合法入口”这个 OS 核心思想的第一个具象例子。实现思路顺着写一个命令行工具并让它真正能用的思路只需两步在user/下新建sleep.c程序需要接收一个参数要睡的 tick 数所以先检查argc是否为 2不对就打印用法并exit(1)。参数通过argv[1]传入它是字符串需要用atoi转成整数后传给sleep()。正常执行完调用exit(0)表示成功退出。在Makefile的UPROGS里注册新写的用户程序不会被自动编译进内核镜像必须手动把$U/_sleep加进UPROGS列表否则sleep命令在 xv6 里根本不存在。这一步虽小却揭示了 xv6 一个贯穿全程的套路任何用户程序 / 系统调用都要在 Makefile 里登记才能被构建。后面 labs 加新程序、新系统调用时这个动作会反复出现。代码实现user/sleep.c—— 用户态 sleep 命令/* * user/sleep.c */#includekernel/types.h#includeuser/user.hintmain(intargc,char*argv[]){if(argc!2){printf(usage: sleep ticks\n);exit(1);}sleep(atoi(argv[1]));exit(0);}几点说明sleep()和exit()的函数声明都在user/user.h里它们最终会触发ecall陷入内核所以必须包含user/user.h。argc ! 2的检查对应命令行sleep ticks恰好一个参数参数个数不对就视为用法错误返回非零退出码1。atoi(argv[1])把字符串形式的 tick 数转成整数exit(0)表示程序正常结束。Makefile—— 把程序编进内核镜像/* * Makefile */UPROGS\ $U/_cat\ $U/_echo\ $U/_forktest\ $U/_grep\ $U/_init\ $U/_kill\ $U/_ln\ $U/_ls\ $U/_mkdir\ $U/_rm\ $U/_sh\ $U/_stressfs\ $U/_usertests\ $U/_grind\ $U/_wc\ $U/_zombie\ $U/_sleep\ # 添加 $U/_sleep\验证方式一手动在 xv6 里测makeqemu# 启动 xv6进入 shell 后执行数字大一点暂停更明显sleep10观察程序是否真的暂停了相应时间再正常回到 shell 提示符。方式二用评分脚本测在 Linux 终端直接运行./grade-lab-utilsleep应看到三个子测试全部通过Test sleep, no argumentssleep, no arguments: OK(1.6s)Test sleep, returnssleep, returns: OK(0.3s)Test sleep, makes syscallsleep, makes syscall: OK(0.9s)三个测试分别验证了无参数时的健壮性、执行后能正常返回、以及确实发起了sleep系统调用。踩坑python 找不到如果运行脚本报/usr/bin/env: python: No such file or directory原因是系统里只有python3没有python。把grade-lab-util文件开头的#!/usr/bin/env python改成#!/usr/bin/env python3保存后重新执行./grade-lab-util sleep即可。后续想测 util 里的其他小实验把命令末尾的实验名换掉就行。复盘本实验解决了什么表面上只是写了 20 行代码实现一个sleep命令实际上它帮你完成了三件基础设施级别的事跑通了工具链——git 分支、make 构建、qemu 运行、gdb 调试、grade 评分这一整套环境从此不再是黑盒。建立了系统调用的第一手直觉——用户程序sleep()→ 陷入内核 → 内核让进程挂起这是你第一次亲手走完用户态请求内核服务的完整动作。记住了 xv6 的登记套路——新用户程序要在Makefile的UPROGS里注册这个动作后面会不断重复。收获什么是系统调用能讲清用户态程序没有直接操作硬件/调度进程的权限必须通过ecall之类指令陷入内核由内核代劳并返回结果——sleep 就是一个最小的例子。用户态与内核态的边界为什么sleep不能由用户程序自己while空转实现因为那样会霸占 CPU真正的挂起必须由内核从调度层面剥夺其运行权。这个问题体现了你是否对特权级和调度有基本理解。argc / argv 与程序退出码argc ! 2的用法校验、exit(0)表示成功、exit(1)表示错误是命令行工具的基本素养也是面试中 shell / 进程模型相关问题的常客。
阅读完成 · 觉得有帮助?
咨询建站