💭 写在前面
本篇博客将对 Pintos 的 Project1 项目的实现要求进行说明。建议阅读上篇铺垫内容并阅读 Pintos 手册,了解 Pintos 项目的 "游戏规则"。
0x00 进程终止信息(Process Termination Messages)
当用户程序被终止(terminated)时,内核会打印终止信息,输出形式如下所示:
Process Name: exit(exit status)\n
(具体参考 Pintos 手册 3.3.2)
❓ 思考:用户程序是如何终止的呢?
当 ELF 用户程序运行时,lib/user/entry.c 中的_start() 首先被调用:
void _start(int argc, char* argv) { exit (main (argc, argv)); }
- 在执行程序后,exit() 系统调用被调用。
- Pintos 只提供了 exit() 系统调用 API 接口,但 exit() 系统调用还没有实现。
我们如何得到一个进程的名称?
struct thread { /* 拥有者为 thread.c */ tid_t tid; enum thread_status status; char name[16];
用户程序又是如何终止的?lib/user/syscall.c 中 exit() 的函数调用流程:
- -> lib/user/syscall.c 中的 syscall1 (SYS_EXIT, status)
- -> userprog/syscall.c 中的 syscall_handler()
- -> threads/thread.c 中的 thread_exit()
- -> userprog/process.c 中的 process_exit()
(具体请参考25-29页)
0x01 参数传递(Argument Passing)
用户程序可以有多个参数:
根据 80x86 的调用惯例,解析参数并将其分配到内存中(参考 Pintos 手册 3.5)
假设参数的长度小于 4KB
- 测试程序使用少于 128 字节的参数。
/bin/ls -l foo bar 将被解析为 /bin/ls、-l、foo、bar
你可以在以下函数之后开始实现参数传递:
userprog/process.c : static bool setup_stack(void **esp)
请参考上一节的 "代码级流程" :
我们刚才说了, /bin/ls -l foo bar 将被解析为 /bin/ls、-l、foo、bar,将参数推到栈顶:
hex_dump() 的结果,这个函数对调试非常有用(在 src/lib/stdio.c 中):
在 userprog/process.c 中,有 setup_stack() ,它分配了一个最小的栈页(4KB)。
由于给定的代码只分配了栈页,我们需要在 setup_stack() 之后补上栈。
(参照 Pintos 手册中的 "3.5 80x86 调用约定" 来构成栈)
0x02 系统调用:基本实现(System Calls)
你需要实现下列系统调用(每个系统调用的要求在 Pintos 手册 3.3.4 中有描述):
实现:halt, exit, exec, wait, read(stdin), write(stdout)
📌 注意:Pintos exec 不同于 UNIX exec() !
两个新的系统调用(fibonacci, max_of_four_int),读和写是这个项目中的特殊情况。
与文件系统相关的系统调用不需要在本项目中实现:
选择实现:create, remove, open, filesize, read, write, seek, tell, close
但是,读和写至少应该做到能执行标准流的输入输出!
halt 函数
halt() // 通过调用 shutdown_power_off() 终止Pintos。
- 该函数调用 shutdown_power_off() 函数,终止 Pintos。
exit 函数
exit() // 终止当前用户程序,并将状态返回给内核
- 终止当前用户程序,并将状态返回给内核。
exec 函数
exec()
- 创建子进程
- 参考 userprog/process.c 中的 process_execute()
wait 函数
wait()
- wait() 系统调用要做的是礼貌地等待子进程,直到它完成其工作。
- 需要检查子线程的 ID 是否有效
- 当子线程寄了,你需要从子线程获取退出状态
- 为了防止程序在 wait() 返回之前就终止进程,你可以使用自旋锁与忙等待技术,或使用在threads/thread.c 中 thread_yield() 函数。
💡 神奇海螺:什么也不做。"忙等待技术" 就是代码什么也不做,只是不断地检查状况。
write 和 read 函数
write() read()
虽然没有完全实现,但至少可以从 STDIN 读,写到 STDOUT。
文件描述符:STDIN,STDOUT
- STDIN = 0,STDOUT = 1
使用下面的函数来实现 read(0)
- pintos/src/devices/input.c路径下的 uint8_t input_getc(void) 函数
使用下面的函数来实现 write(1)
- pintos/src/lib/kernel/console.c 路径下的 void putbuf(...) 函数
代码级流程
- 当 ELF 可执行程序(用户程序)完成后,exit() 系统调用 被调用。
- exit() 系统调用后,返回到 process_wait() 。(exit 调用完成后系统进入进程等待状态)
源代码(Source Code)
lib/user/syscall.h 和 lib/user/syscall.c
- 系统调用的 API 接口已经在 Pintos 代码中给好了
- 这意味着我们不需要为系统调用的 API 添加任何东西
userprog/syscall.h
- 这里只有一个 syscall_init() 的样板 ,在 Pintos 启动时注册系统调用中断
- 我们可以在这个文件中编写系统调用的原型
userprog/syscall.c
- 必须钦定 syscall_handler() 大手子去处理系统调用
- 如果已经做了参数传递,可以从 intr_frame* f 中获得系统调用号
- intr_frame* f 的 esp 成员指向系统调用号。(可以参考 lib/syscall-nr.h 来检查每个系统调用号)
- 然后你可以用 switch 语句对系统调用进行分类(分清这些系统调用到底是做什么的会写在这里)
0x03 系统调用:额外实现(Additional System Calls)
试着在 pintos 中写几个新的系统调用接口:
- 斐波那契数列的第 项:
int fibonacci(int n) // 返回斐波那契数列的第n项
- 返回四个整数的最大值:
int max_of_four_int(int a, int b, int c, int d) // 返回 a b c d 中的最大值
* 命名请使用 fibonacci 和 max_of_four_int ,不要使用其他名称!
- 编写用户级程序,使用新的系统调用
在 pintos/src/examples 中制作 additional.c (执行文件的名称必须是 "additional")
通过使用新的系统调用编写简单的例子,用法:./additional [num 1] [num 2] [num 3] [num 4]
功能:使用 [num 1] 作为参数打印 fibonacci 系统调用的结果;
使用 [num 1, 2, 3, 4] 作为参数打印 max_of_four_int 系统调用的结果。
你可以运行以下命令以检查你的程序是否正常工作:
pintos/src/userprog$ pintos --filesys-size=2 -p ../examples/additional -a additional -- -f -q run 'additional 10 20 62 40'
* additional 需要在 Pintos 里运行
📌 注意:为了编译新增加的用户程序 additional ,你需要修改 src/examples 中的 Makefile,参照 Makefile中其他用户程序的编写方式去修改。
源代码(Source Code)
lib/user/syscall.h
编写2个新的系统调用API的原型
lib/user/syscall.c
为 max_of_four_int() 定义新的 syscall4() 函数(lib/user/syscall.c)
定义 fibonacci() 和 max_of_four_int() 的系统调用 API
lib/syscall-nr.h
为 2 个新的系统调用添加系统调用号
userprog/syscall.h
编写 2 个新的系统调用的原型
userprog/syscall.c
定义 fibonacci() 和 max_of_four_int() 系统调用
这些系统调用到底是做什么的,会写在这里。
0x04 用户内存访问(Accessing User Memory)
用户程序可以传递一个无效的指针。
- 空指针,如 tests/userprog/open-null.c 中的 open (NULL)
- 未映射的虚拟内存
- 指向内核地址空间的指针
无效的指针(Invalid pointers)必须在不损害内核或其他运行进程的前提下被拒绝。
- 它可以通过 2 种方式实现
- 验证用户提供的指针的有效性(validity of a user-provided pointer),然后取消对它的引用。
- 只检查一个用户指针是否指向 PHYS_BASE 下面,然后取消对它的引用。如果该指针无效,将导致缺页异常(page fault)。你可以通过修改 userprog/exception.c 中的 page_fault() 代码来处理它。
* 参考 Pintos 手册 3.1.5
- 可以利用 userprog/pagedir.c 和 threads/vaddr.h 中的函数验证用户提供的指针的有效性
- 利用 pagedir_get_page() 函数检查未映射的虚拟内存
- 利用 is_user_vaddr() 和 is_kernel_vaddr() 函数检查指向内核地址空间的指针
- 使用这些函数来验证给定指针的有效性
0x05 实现的先后顺序(建议)
在你实现核心功能之前,你无法看到结果。
强烈建议先阅读/理解上面说的内容和 Pintos 手册,设计好结构,然后开始编写。
Step1:参数传递:实现后,可用 hex_dump() 函数检查结果。
请参考代码级流程
可用 src/userprog/process.c 的 load() 检查 load() 的参数
如果你想在执行 process_wait() 之前检查 dump values,可暂时把 process_wait() 改成无限循环,以阻止进程,便于测试检查。 ( 等后面完成 process_wait() 的实现后再说)
Step2:用户内存访问:保护用户内存访问不受系统调用的影响。
参考 src/threads/vaddr.h
建议实现检查给定地址有效性的函数。
Step3:系统调用处理程序:实现 syscall_handler() 函数来处理系统调用。
src/userprog/syscall.c 的 syscall_handler() 函数
检查 syscall.c 中 syscall_handler() 的参数 struct intr_frame(struct intr_frame 在 src/threads/interrupt.h 中)
Step4:系统调用实现:首先实现完 exec()、exit()、write()、read(),再实现其它的。
将需要进行同步化 (可以选择使用忙等待)
当 syscall_handler 以非正常方式终止时,退出状态为 -1 。
Step5:额外实现:最后再实现 fibonacci(),max_of_four_int()
修改以下内容:
src/lib/syscall-nr.h
src/lib/syscall.h
src/lib/syscall.c
* 可参考 src/tests/userprog 路径下的源代码,Refer to Pintos manual 3.2
🚩 评分(Evaluation):
- 在这个项目中,76 个测试中有 21 个将计入评分。
- 总分是 100 分,包括 80 分的测试案例和20分的文档。
- 每一个额外的系统调用的实现都会有额外的2.5分。(实现中的 fibonacci() 和max_of_four_int() 有5分)※ 这将在开发部分(80%)计算,所以总分将是 4 分(5*80%)。
- 将使用 Pinots 提供的评分脚本(make grade 或 make check in src/userprog)。
- 在评分之后,请参考 src/userprog/build 中的 '成绩' 和 '结果'文件( '成绩' 文件只有在你使用 make grade 时才会被创建)。
- 测试用例被分为功能测试和稳健性测试,请参考以下内容,根据测试类型检查每个测试案例的要点:
- pintos/src/tests/userprog/Rubric.functional.
- pintos/src/tests/userprog/Rubric.robustnesss
- 功能性和稳健性分别占总分的50% (... SeeMore)
* Refer to Pintos manual 1.2.1
如果你看到 src/tests/userprog/Grading,功能测试集占总分的 35%,健壮性测试集占总分的25%.
📃 评分方式如下:
(剩余的 20%是留给项目报告的,占总分的20%)
📌 [ 笔者 ] 王亦优 📃 [ 更新 ] 2022.9.24 ❌ [ 勘误 ] /* 暂无 */ 📜 [ 声明 ] 由于作者水平有限,本文有错误和不准确之处在所难免, 本人也很想知道这些错误,恳望读者批评指正!
📜 参考资料 Remzi H. Arpaci-Dusseau and Andrea C. Arpaci-Dusseau, Operating Systems: Three Easy Pieces A. Silberschatz, P. Galvin, and G. Gagne, Operating System Concepts, 9th Edition, John Wiley & Sons, Inc., 2014, ISBN 978-1-118-09375-7. Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. . 百度百科[EB/OL]. []. https://baike.baidu.com/. |