Note: This design was reformatted as the original student-submitted Markdown did not render well.
Phase 1
Overview
In Phase 1, we will implement the following system calls in OS/161:
File-related system calls: open, read, write, lseek, close, dup2, chdir, __getcwd Process-related system calls: fork, _exit (preliminary implementation without PID support), get_pid, execv, waitpid, _exit
File Related Functions
All the file related functions will be implemented file_syscall.c
open
Function Definition
open: Takes in two arguments, const char *filename, and int flags. Opens the file, device, or another kernel object specified by the filename argument. Flags specifies the filename permissions (O_RDONLY, O_WRONLY, O_RDWR).
New Implementation
- Need a file descriptor table implementation (fixed size array), which we implement in filetable.
- Call vfs_open() - open this file and give me back a vnode pointer (as well as a number to indicate if the method has been successful or not)
- Need to also call copyin/copyout to pass the filename from the user space to the kernel space
- Create open file struct (define in openfile.c) which will have the following: pointer to vnode, file offset position (position 0 initially), reference count (only one process is using it at the moment), permissions (readonly, writeonly), and fd
- Find the next free fd number for the open file and assign the fd table entry to point to this file.
Some further notes
- struct open file is allocated dynamically (via kmallloc)
- The file descriptor table entries point to open files
- Struct vnode refers to an open file and that's it, we need to determine the file offset, file descriptor, and the reference count
read & write
Both the read & write calls will use the vnode interface provided by the VFS to read from and write to the file associated with the file descriptor. Their implementations are very similar.
read
Function Definition
read: ssize_t read(int fd, void *buf, size_t buflen); Reads buflen bytes from file specified by fd into buf. Position in file is advanced by the number of bytes read.
- Search up fd number in the processes' file descriptor table
- Check that the file is open
- Check that the file has read permissions
- Initialize struct uio, passing in buf and buflen
- Look at code in loadelf function for uio initialization (src, destination)
- Note that VOP_read already calls copyin and copyout for read/write
- ERROR_CODE CHECKING
Some further notes 1. Acquire lock to make sure that the offset doesn't change 2. Hold the lock for the entire duration of the read
write
Function Definition
write: Writes buflen bytes to file specified by fd from the data in buf. Position in file is advanced by the number of bytes read. Position in file is advanced by the number of bytes written.
ssize_t write(int fd, const void *buf, size_t nbytes);
- Search up fd number in the processes' file descriptor table
- Check that the file is open
- Check that the file has write permissions
- Initialize struct uio, passing in buf and buflen
- Look at code in loadelf function for uio initialization (src, destination)
- Note that VOP_write already calls copyin and copyout for read/write
- ERROR_CODE CHECKING
Some further notes - Acquire lock to make sure that the offset doesn't change - Hold the lock for the entire duration of the write
Function Definition
lseek: This call will adjust the current position in the file for a given file descriptor, allowing processes to move around within files.
lseek(int fd, off_t pos, int whence);
- Search up fd number in the processes' file descriptor table
- Check that the file is open
- Copy in pos and whence
- Advance file position based on whence/pos
Some further notes
- Acquire lock because lseek is an atomic system call.
close: Closes file handle fd.
int close(int fd);
- Search up fd number in the processes' file descriptor table
- Remove fd number from table
- Decrement reference count on the corresponding vnode.
dup2: This call duplicates one file descriptor into another, allowing for redirection of input/output streams. We will need to carefully manage the reference counts of the file objects involved.
- Add a case statement for SYS_dup2
- dup2 takes two arguments,
oldfdandnewfd - Initialize a
struct openfilepointer - Check the validity of
oldfd, ensure it is open and valid then return - If
newfdis the same asoldfdthen we don't do anything and returnnewfd - If
newfdis open, we use sys_close to close it - Duplicate
oldfdtonewfd: Pointnewfdto the same openfile structure asoldfd, then increment the reference count of the openfile - return
newfd
chdir: This call changes the current working directory of the process. We will update the process's working directory vnode accordingly.
- Add a case statement for SYS_chdir
- Use
copyinstrto transfer the pathname string from user space to a kernel buffer (copyinout.c). - Use
vfs_chdir(pathname)to update the current working directory - Make sure to handle error appropriately, e.g. ENOENT means that the file directory does not exist
__getcwd: This call retrieves the current working directory, which we will return as a string to the calling process.
- Add a case statement for SYS__getcwd
- For the parameters of the function we need a buffer, the size of the buffer and an integer to return the number of bytes written to the buffer
- We want to copy the buffer information to the kernel space. So first we initialize a
struct iovec. This points to the user buffer and its size, ensuring the kernel knows where to write. - We also want to initialize a
struct uiowhich manages the data transfer. - In the function we will use
vfs_getcwd. This retrieves the current working directory and copies it into the buffer.
Process Related Functions
We will implement the following functions in FILL IN
fork:
pid_t fork(void)
- Use
as_copy()from addrspace.h to duplicate the calling processes' address space. - Duplicate file descriptor table
- Initialize the struct proc, with the name of the child process, lock, p_numthreads, the address space that we get from as_copy, and the current working directory.
- Then we can call
thread_fork()to create a new child process, some sort of handler - Take parent's trapframe, copy onto kernel heap, handoff to a child starter function, pass on that to the kernel heap trapframe, copy out of heap to stack, free from the kernel heap, then call enter_forked process
- The new thread will start executing in the function
enter_forked_processwhich properly sets up the child's register state. - Return 1 to parent, Return 0 to child.
_exit:
_exit(int exitcode)
- Use
sysclose()to close all the files in the file descriptor table for the calling process - Dispose of the address space using
as_destroy()(which is subject to change) - Call
thread_exit()
Phase 2
Table of
pid_alloc -> need to call it What do we want the pid system to be for? Programs can talk about other processes. To be able to call waitpid on the thread
main thing to store in pid info: has it exited (bool), if so, what is the exit code?
big global lock, big condition variable, when everyone terminates, wake everybody up (wchan stuff)
getpid: returns the process id of the calling/current process
pid_t getpid(void)
- We will copy the proc code given to us in Week 5/Lesson2/PIDS, fork, exit, waitpid in kern/include/proc.h/proc.c
- We already have access to the pid via the function
pid_alloc(). - Modify fork function to call
pid_alloc()to get a new PID for the forked process - struct procinfo will have the following member variables:
- pid
- bool if process has exited
- exit code
- Assign the pid variable to be the value from
pid_alloc(). - Return the pid variable for the process
execv: Run new program (1st argument) with arguments (2nd argument)
int execv(const char *program, char **args)
- Copy the program name and arguments from the user space into the kernel space by using
copyin() - Use
as_destroy()to destroy the calling processes' address space - Use
as_create()to create a new address space. - We load the new program into memory but using
load_elf()- 2 arguments: vnode, entrypoint
- vnode: look at what runprogram does
- entrypoint is the new address space returned from
as_create()
- Copy arguments into new address space
waitpid
pid_t waitpid(pid_t pid, int *status, int options)
- Call
pid_to_info- Go through the PID array
- Each element in the array has the following: pid_entry (which has a pointer to the process itself), and the last_allocated PID
- Find the process with the matching pid
- The calling process needs to wait (go to sleep on a wait channel) until the process it is waiting on finishes. We can achieve this through a global lock and a condition variable.
_exit
- Set the exit status in
procinfo, marking the process as exited. - Use
sysclose()to close all the files in the file descriptor table for the calling process - Dispose of the address space using
as_destroy()(which is subject to change) - If the process has a parent process, signal the parent process that it can retrieve the exit status using
waitpid() - Call
thread_exit()
(When logged in, completion status appears here.)