Note: This design was reformatted as the original student-submitted Markdown did not render well.
System Calls High-Level Designs
This document will describe our approaches to implementing the various system calls for HW4.
- Before discussing our implementations, it's important to explain how we will represent our the kernel's filetable and openfile.
Filetable
The filetable is the kernel's way to manage all current open files. We will represent this as an array of size OPEN_MAX. OPEN_MAX is OS/161's global variable that represents the maximum number of open files we can have at any time, which is 128. This array will contain pointers to vnodes, where each index represents the a file descriptor.
Open Files
We will represent an open file as a struct containing the following fields:
- vnode pointer
- array of bools describing what kind of permissions this file has
- file offset
- reference count
- lock
open
-
open will take the following arguments
- path (str)
- flags (array of modes describing file permissions)
-
ret: (int) file descriptor
-
Implementation:
- We will take the path and flags provided by the user and copy it to the Kernel utilizing copyin within a buffer.
- Once the user data is validated and safe, we utilize vfs_lookup() to locate the correct file
- Then, we utilize vfs_open() to properly open the file, returning a pointer to a vnode
- We then perform a linear search on our file table to find the lowest available file descriptor
- We assign update the lowest available file descriptor to point to the open file struct
- We then update any necessary fields, and then return the file descriptor to the user using copyout
read
-
read will take the following arguments:
- file descriptor (int): the file descriptor to read from
- buffer (void*): a pointer to the buffer
- count (size_t): the number of bytes to read
-
ret: (int) number of bytes read, -1 on failure
-
Implementation:
- We will begin by checking if the file descriptor is valid.
- we will then copy the user buffer into a kernel buffer using copyin().
- then we will call VOP_read() on the vnode to read the number of bytes. requested into the kernel buffer.
- We will then update the file's offset based on how many bytes we read.
- lastly, we will return the number of bytes read.
write
-
write will take the following arguments
- fd (int)
- buf (const void)
- count (size_t)
-
ret: (int) number of bytes written
-
Implementation:
- We will take the file descriptor, buffer, and count provided by the user and validate it by utilizing copyin within a buffer
- Since our filetable is indexed by file descriptors, we find the openfile struct indexed by the given file descriptor
- Before we write, we check the permissions array of this openfile struct to verify if we are allowed to write to this file
- If we are allowed to write, then we begin to write the data using vfs_write() beginning at the offset specified within the openfile field
- We determine the difference between the previous offset and the new offset. This represents the number of bytes written.
- We then return this value to the user utilizing copyout.
lseek
-
lseek will take the following arguments:
- file descriptor (int): a file descriptor to write to
- offset (off_t): the offset to seek to
- whence (int): lets us know if the offset is relative to the start current position or end
-
ret: (off_t) new file offset, -1 on failure
-
Implementation:
- We first check if the file descriptor is valid and we can seek
- We then calculate the new file offset using whence as a point of reference
- we then update the file's offset value in our array of files
- Lastly, we return the new file offset
close
-
close will take the following arguments:
- fd (int)
-
ret: (int) errcode
-
Implementation:
- We will take the file descriptor provided by the user and validate it by utilizing copyin within a buffer
- Since our filetable is indexed by file descriptors, we find the openfile struct indexed by the given file descriptor
- We then call vfs_close() on the vnode that is pointed to by that openfile struct
- We decrement the refcount field within the openfile struct
- If the refcount is 0 after decrementing, we call vfs_remove() on that specific vnode
- We then remove that openfile struct from the filetable array and free any associated memory with it
- If this happens without error, we return 0 to the user utilizing copyout.
dup2
-
dup2 will take the following arguments:
- oldfd (int): a file descriptor source
- newfd (off_t): a file descriptor source we want to duplicate to
-
ret: (int) new file descriptor, -1 on failure
-
Implementation:
- We first check if both file descriptors are valid
- If newfd is open, we will close it
- We will then copy the file object from oldfd to newfd in our file array
- we will then return the newfd
chdir
-
chdir will take the following arguments:
- path (str)
-
ret: (int) errcode
-
Implementation:
- We will take the path string provided by the user and utilize copyin to pass it to the kernel within a buffer
- We will leverage vfs_lookup() to retrieve the vnode that points to the specified directory the user desires
- We will use vfs_setcurdir() passing in the vnode we retrieved from vfs_lookup()
- If there are no errors, we then return 0
__getcwd
-
__getcwd will take the following arguments:
- buffer (void*): a buffer to store the fworking directory path
- size (size_t): the size of the buffer
-
ret: (int) number of bytes coppied from buffer, -1 on failure
-
Implementation:
- First we will itterate through all the vnodes from the current working directory up to the root
- This will build the path that we will store in the kernel
- We will then use copyout() to copy to the user buffer
- We will make sure that the buffer size is ok and return an error if not
- Lastly, we will return the number of bytes written to the buffer
fork
-
fork will take the following arguments:
- fork will take no arguments
-
ret : (int) PID of the child
-
Implementation:
- We duplicate the UIO_SYSSPACE memory block of the current process
_exit
-
_exit will take the following arguments:
- status (int): the status code for the process
-
ret: There is no return
-
Implementation:
- We will begin by marking the current process as exited by storing the exit code for any parent process to retrieve.
- Next we will close all the file descriptors for the process and release all the resources.
- If a parent is waiting, we will notify the parent.
- Lastly, we will call thread_exit() to terminate the thead and remove it from the system.
getpid
-
getpid will take the following arguments:
- getpid will take no arguments
-
ret: (int) PID of the calling process
-
Implementation:
- We simply return the PID field from the current process' struct.
waitpid
-
waitpid will take the following arguments:
- pid (pid_t): this is the process ID of the child process that we are waiting for.
- wstatus (int*): this is a pointer to the the exit status of the child
- options (int): this will usually be 0
-
ret: (int) this is the PID of the child that was terminated, -1 on failure
-
Implementation:
- First, we will check if pid is a valid child of the current process
- Next, if the child has exited, we will return its exit status and remove the child proccess from the array of proccess's.
- If the child has not exited, we will block the calling process and put the child to sleep until it exists.
- Once it wakes up, we will copy the child's exit status to the user buffer usying copyout()
- lastly, we will return the child's PID
execv
-
execv will take the following arguments:
- path (string): This is the file path of the program we want to execute
- argv (array): this is the argument for the new program
-
ret: (int) 1 if success and -1 if failure
-
Implementation:
- We will begin by copying the path and argument from the user to the kernel using copyin().
- Next, we will check if the arguments are valid and make sure the file we want to execute exists.
- We will then destroy the current process's address and create a new one.
- We then will load the executable file into a new address using load_elf().
- We will set up the new program with the arguments and environment.
- Lastly, we will enter the user in the new program by transfering the entry point into the executable.
(When logged in, completion status appears here.)