Design
user-level API can be found in unistd.h
System call manual: https://www.cs.hmc.edu/cs134/os161-man/syscall/
I/O-Related Calls
- openfile.c
Here, we will build a openfile struct. It includes a vnode as abstract representation of the file, an offset of which point the file we are reading at / writing to (initialized to be 1), a reference count of how many pointers from filetable is pointing to this open file (initialized to be 1), a lock to protect the access to this struct which may be share across process. It also needs to include basic functions of openfile_create, openfile_destroy to allocate space, initialize the members, clean up the space and so on.
- filetable.c
We will implement a fixed size filetable struct. It is a list of openfile structs indexed by the file descriptor corresponding with that openfile. It needs to include funcitons like initialize the table with standard input, standart output and standard error. There should also be a clean up function, which frees up space and closes file descriptors that are still open.
- proc.c
Add filetable struct to be a member of proc struct.
- file_syscalls.c
This is where we implement the file-related system calls
file-related system calls
- open
Initialize an opentable struct of the file passed in and add it to the file descriptor attached on the current process. Then return the file discriptor.
- close
Get the openfile struct corresponding to the file using file discriptor. Check the reference count of the openfile. If it is 1, call openfile_destroy to clean up file descriptor, and set the pointer in the filetable struct coresponding to the file descriptor to null. If it is more than 1, decrement the reference count of the openfile and set the pointer in the filetable struct coresponding to the file descriptor to null. Returns 0 on success and -1 on error.
- read
Get the openfile struct corresponding to the file using file discriptor. Use vop_read to read data from file to uio and uiomove to put data to the buffer in userspace from uio. Finally, update the offset of the openfile Return the count of bytes read.
- write
Get the openfile struct corresponding to the file using file discriptor. Use uiomove to get data to the buffer in userspace to uio. Write the data from uio to the vnode. Update the offset of the openfile. Return the count of bytes written.
- lseek
Get the openfile struct corresponding to the file using file discriptor. Update the offset of the openfile according to the pos and whence, padding gap if needed. On success, lseek returns the new offset. On error, -1 is returned.
- dup2
Points the pointer at the index of newfd in filetable to the openfile pointed by oldfd, atomically close the file refered by newfd if needed. Increment refcount in the corresponding openfile. dup2 returns newfd. On error, -1 is returned.
- chdir
Using vfs_chdir to set the current working directory of the process to the new path. On success, chdir returns 0. On error, -1 is returned.
- __getcwd
Use vfs_getcwd to write the current working directory into an uio and uiomove to put data to the buffer in userspace from uio. On success, __getcwd returns the length of the data returned. On error, -1 is returned.
Process-Related Calls
- proc.c
Add global pid_array struct and add pid to proc struc
- pid.c
Adapt from the code in https://www.cs.hmc.edu/cs134/lessons/w05/l02/pids-fork-waitpid/. We will include structs for procinfo, pid_entry, and pid_array, and we will include functions for pid_alloc, pid_free, pid_to_info. WaitPID allows process to get the exit code of the other process. If it is not finished, this process needs to go to sleep to wait for it. Thus, we need a global lock with conditional variable to broadcast everytime a process exits to wake any process that may be waiting on it. These will be stored in procinfo. Most importantly we will also include an exit status member to procinfo.
- proc_syscalls.c
This is where we implement the process-related system calls
process-related system calls
- fork
Initialize a new process, including: 1) use as_copy to copy address space, 2) copy the file descriptor table, 3) initialize/copy other members, 4) call pid_alloc to allocate a pid for this process and store it in the pid member. At last, we should call enter_new_process to enter the new process. Assert if the table is full and could not give new ids. Return the child pid to parrents, return 0 to child.
- getpid (phase 2)
Pretty simple. Return the pid stored in the pid member of this process. We'll need to worry about how this pid is handled in other places.
- execv (phase 2)
We will do things similarly to the runprogram function: 1) use vfs_open to open program file, 2) call load_elf to load the executable file into address space, 3) call as_define_stack to define the user stack, 4) call enter_new_process.
(Also: we might need to fix run program to conform to our file descriptor system.)
- waitpid (phase 2)
Check if process has been exited. If it has not been exited, put calling process to sleep (atomically?) using cv_wait. Then we need to return the encoded exit status in the integer pointed to by status (which we can get by calling pid_to_info). We also need to call pid_free to free a zombie process' pid and set it to be free. Finally, we return the process id of the process we were waiting for.
- _exit (phase 1/2)
Cleanup filetable, and call thread_destroy. Call (atomically?) cv_signal to signal any processes that might be waiting for this process to exit.
(Also: we need to update kill_curthread to call _exit to kill the process that thread with fatal problem is linked with.)
Nice to have: deal with orphaned processes by having the kernel process inherit them.
(When logged in, completion status appears here.)