OS161 System Calls Implementation Notes
Author: 咳嗽di小鱼 Date: October 30, 2011 Category: Sum Up
这篇文章的目的是记录CS350 Assignment2中, 我编写各种System Calls时所采用的思路. 实际coding的时候, 同一种System Call的实现方式很可能不止一种, 但殊途同归.
注1: 文章顺序和实际coding顺序并不一定一致, 请参考Assignment中的Strategy部分.
注2: 如果没有仔细读过code和Assignment...这里很有些东西你可能读的似懂非懂.
欢迎在评论中提问...
General Tips
- 别管写什么function, 第一步永远是检查parameter是否有效!!!
(比如, pointer是不是NULL, string是不是空, etc) - 不要放过任何一个warning...需要explicit cast的时候, 千万不要偷懒.
- function一般使用parameter中的pointer进行value return, 正常的return用来return errno.
- 开始coding之前, 除了读Assignment, 读code, 还要好好读Assignment Hint! 解答了很多FAQ
- 注意在header file里用#ifndef, 保证header不会出现重复include
- kmalloc了的东西...在destroy的时候一定要kfree. 不然A3里, 你会发现各种memory leak
(Create某些data structure的中间, 如果出现error, 则需要首先free已经allocated的内存, 然后返回error) - 善用spl解决mutex问题. 当然, 首先你要明白什么样的操作需要mutex!
我会边写这个Note边添加Tips...
Part 1, Play with the files and console
在OS161中, 所有应用程序在打开, 关闭, 读取, 写入一个文件的时候, 都是通过一个file descriptor [ID]来标识某一个文件的. 需要注意的是, file descriptor [ID]和vnode是完全两个东西.
每个thread都有单独一套file descriptors [ID], 但两个thread的两个不同的file descriptor [ID]如果标识的是同一个文件, 则共用一个vnode.
(这里所说的file descriptors实际上只是一个int, 是代表一个file descriptor的ID, 而实际上file descriptor要保存很多内容. 后边继续)
简单来说, 我们需要给每一个thread都增加一个, 记录所有opened file的机制. 我们暂称之为file table.
file table可以就是个简单的array, 但为了让file table的operation和thread以及syscall相互独立, 减少重复code并减少bug机会, 我强烈建议大家把file table相关的所有operations都写成独立的functions, 放在单独的文件里.
file table & file descriptor
file table其实很简单, 一个array外加一个size_t记录array的长度, 就ok了. (这里说的所有array都是struct array, OS161中自带的kernel array lib)
file descriptor中需要记录以下几个东西,
- vnode pointer
- permission flags
- the current offset in file
- probably need something else, depends on your implementation. (like a reference counter, etc.)
然后就是操作file table的各种function, 比如
- create file table
- destroy file table
- duplicate file table (is one of the most important part of
execvfork, but not nessesary for now) - add/get/delete a file descriptor
- may need extra functions or helpers for convenience
至此基本没有难点 (就类似写个小"class").
之后要把file table添加到thread里, 并且在thread_fork中进行initialization.
Stdin & Stdout 也是FILE!!!
这是Assignment在"Implementing file system calls"中特别提到的一点.
在建立一个新的process时候, 不止要initialization一个file table, 而且还要把table的0, 1, 2分别设置成standard input, standard output和standard error, 不然printf就不work哦, 哈哈哈.
注: OS161中stdout和stderr是一样的.
知道了怎么Open, Close就简单了
open步骤 1. 创建file descriptor 2. vfs_open打开文件 (获取vnode) 3. 往file descriptor中存需要的数据 4. 把file descriptor加到file table中, 并取得ID 5. return ID给user program
如何用vfs_open打开文件, 请参考runprogram.c
Read和Write差在细节
有file descriptor就有vnode...如何用VOP_READ配合vnode和uio读文件, 请参看loadelf.c
注意offset怎么算!!!!!
考验你读code读够不够细致的时候到了...好好读vnode.h(的comment)吧.
Write和Read差在uio的配置, 和用VOP_WRITE.
请细读uio.h(的comment)= =...都是comment有用, code看不懂影响不是很大.
Part 2, Get the process organized
fork, getpid, waitpid, _exit...
注: 按我的理解, OS161所有的thread都是process, 只不过有parent和child之分.
这部分中, PID的管理逻辑是关键, 主要解决几个问题.
- 如何让一个process wait在一个PID上, 并在exit的时候wake up所有在当前PID waiting的process
- 如何防止deadlock (P1 wait on P2, and P2 wait on P1)
- 如何保存exitcode. 并明确, 何时可以安全的删除这个exitcode的记录
- 如何重复使用已经空闲的PID 除此之外, fork还要考虑其他问题, 后边再说.
Table解决一切问题...
和file descriptor类似, PID也需要构建一个table. 区别在于, process table必须是global的! 也就是说, 整个系统只有一个process table.
然后考虑, 我们需要为每个PID/process保存哪些信息, 才能解决上边列举的几个问题.
process table相关的function (仅供参考),
- create process table (use in thread_bootstrap)
- destroy process table (use in thread_shutdown)
- assign and save a PID for new process (use in thread_fork)
- mark a PID as inactive and save the exitcode (use when _exit is called)
- you may also want to add other things here. Like the actual implementation of waitpid, wakeup waitings, release a exited PID or any other PID related functions. But its all depends on your implementation decision.
Wait&Wakeup的钥匙 - A magical memory address
第一个Assignment写完, 对于thread_sleep和thread_wakeup应该都很熟悉了.
两个function都consume一个pointer作为钥匙. 只要sleep的thread和wakeup的thread用的是同一个钥匙, 睡着的所有thread就能被唤醒.
这在implement waitpid和_exit的时候是很实用的.
fork意味着, 一模一样
这估计是这次Assignment中最难的部分.
fork的作用是生成一个和当前thread完全一样的thread. 说具体点就是,
- CPU的每个register值一模一样
- address space中的memory一模一样
- 打开的file一模一样 (duplicate a file table)
- 其他一个thread中可能包含的部分
- 只有PID不一样!!!
fork system call要点:
- consume一个东西, trapframe. 这里保存着user program进入privilege mode之前, 所有register的数据.
- 用memcpy把trapframe复制一份给新thread用
- 调用thread_fork的时候, 用复制的trapframe和md_forkentry作为parameter.
thread_fork要点:
- thread_fork要想办法知道, 是不是被fork system call调用的
- 如果是, 需要复制address space
- 如果是, 需要duplicate file table. 否则, 创建新的
md_forkentry要点:
- trapframe要复制到kernel stack上 (太简单了以至于有时候想不到用)
- fork里复制的trapframe要free掉
- 在trapframe里设置return value, (pc貌似也要increment...回忆不起来当时increment的原因了)
- address space需要activate
Follow这些要点, 应该可以比较顺利的搞定fork. 但我仍推荐先把fork和md_forkentry的工作原理搞明白, 再开始.
(如果写完了还没明白, 为什么要把md_forkentry pass给thread_fork, 那你绝对是奇葩)
到这里, A2中最难的部分过去了...
runprogram和execv, 我不确定有没有时间在下周一之前总结出来.
这两个大同小异, runprogram写好, execv只是稍微多点东西.
exception handling是打酱油...
大家加油
感谢格雷格
感谢格雷戈
感谢greg
请问为什么 说“所有array都是struct array, OS161中自带的kernel array lib”?
这里的filetable声明为一个结构体指针数组可以吗?
我的意思是, 所有我文中提到的"array", 都是"kernel/include/array.h"这个array, 而不是[]这个array.
是的 就想请教这个 为什么需要用array.h里的array而不能用[]?
使用array.h里的array可以规范数据存储, 访问, 删除的方式, 帮助你管理Array的内存使用, 省时省力省bug. 用[], 必要时size不可变, 还涉及kmalloc的内存管理问题, 平添很多不必要的麻烦.
嗯 有道理 多谢:)
麻烦问一下,做fork拷贝addrspace的时候,会调用到as_copy方法,as_copy之中又会调as_create,问题是如何这个as_create开辟出来的地址空间是属于用户空间,还是内核空间?
as_create是创建一个address space, 代表的是process运行时可以使用的内存. 是用户空间. 但as这个strcutre本身是kmalloc出来的, 存在在内核空间里.
你好,请问在实现fork的过程中,需要使用同步原语来控制同步吗?
我记得, fork过程中应该是splhigh的吧?