CS 134

Note: This design was reformatted as the original student-submitted Markdown did not render well.

Basic File and Process System Calls

A file struct stores:

  • file_node - a pointer to a vnode
  • file_offset - the offset into that file, as an int.

For interacting with files, a process contains the following data:

  • proc_ft - its file table, an array of file structs. This has OPEN_MAX entries (128 by default). This is initialized with indexes 0, 1, and 2 as STDIN, STDOUT, and STDERR.
  • proc_cwd - a current working directory for the process, represented by a vnode.

Open file actions

int sys_read(int filehandle, userptr_t buf, size_t size, ssize_t* ret );

  • buf -> const void *
  • Initializes a new uio struct using uio_uinit with the given buffer and the offset from that entry in the file table. Then calls vop_read with the vnode and the uio struct. Puts the actual number of characters read into ret.

int sys_write(int filehandle, userptr_t buf, size_t size, ssize_t* ret);

  • buf -> const void *
  • Initializes a new uio struct using uio_uinit with the given buffer and the offset from that entry in the file table. Then calls vop_write with the vnode and the uio struct. Puts the actual number of characters written into ret.

int lseek(int filehandle, off_t pos, int code, off_t* ret);

  • First, checks if the file is seekable and throws the ESPIPE error if it is not.
  • Otherwise, sets the entry in the file table's offset to the correct value given by pos and code, then puts this value into ret.

File descriptor actions

int sys_open(userptr_t filename, int flags, int mode, int* retval);

  • filename -> const char *
  • Calls vfs_open to get a vnode for the specified file, creates a new file struct in the first open spot of the file table with that vnode and an offset of 0.

int sys_close(int filehandle);

  • Calls vfs_close on the vnode of the file at the index in the file table specified by filehandle. Then deletes that entry of the file table.

int sys_dup2(int filehandle, int newhandle);

  • First, checks whether there is already an entry in the file table at index newhandle—if there is, closes that file.
  • Next, calls the macro VOP_INCREF on the given vnode to notify there is another reference to it.
  • Finally, creates a new file struct at index newhandle, with the same vnode pointer and offset.

Directory actions

int sys_chdir(const_userptr_t path)

  • path -> const char*
  • Calls vfs_chdir with the given path.

int sys___getcwd(userptr_t buf, size_t size)

  • buf -> char*
  • Initializes a new uio struct with the given buffer and buffer size. Then calls vfs_getcwd to write the current directory into that buffer.

Process manipulation

int sys_fork(pid_t *ret)

  • When a process is forked, the new process will have to copy the file table. It also must call VOP_INCREF on every vnode in the file table, as there is now another reference to them.

void sys__exit(int code) (early implementation)

  • When a process exits, it must call vfs_close on every vnode in its file table.

Advanced Process System Calls

There is a global structure pid_array where each entry stores:

  • Whether a process id is currently free
  • The generation of a process id
  • A lock and cv used for waiting on threads to finish
  • A bool for whether a thread has finished yet
  • A return code for threads that have finished.
  • The full process struct (including the name, file table, and cwd)

int sys_getpid(pid_t *ret)

  • Finds the pointer to the process from curthread. Gets the process ID for that process, then puts it in ret.

int execv(userptr_t prog, userptr_t args)

  • prog -> const char *
  • args -> char * const *
  • First, takes in the process name from userspace, opens the file with that name and verifies that it's an executable program.
  • Then, creates a new address space.
  • Finally, loads the ELF executable, closes the file, defines the stack for the program, takes the given args and puts them onto this new stack, and uses enter_new_process (called with a pointer to these arguments on the stack) to enter user mode running that program.

int waitpid(pid_t pid, userptr_t returncode, int flags, pid_t* ret)

  • returncode -> int *
  • Looks in the process table to see if the process has finished.
  • If the process has finished, looks at its result.
  • If the process has not finished, waits on a condition variable also located in the process table until the process finishes.
  • Finally, once we've taken the return code from the finished child process, we can remove it from the pid table.

void _exit(int code)

  • When a process exits, we want to set the boolean flag representing finished to true in the process table, set our return code in that pid table entry, and broadcast on the threads' wait condition variable.
  • Finally, if this thread's parent has finished, remove this from the pid table.

All functions with a return type of int will return the applicable error code—or 0 if the function worked correctly. (Notably, functions like sys__exit do not return and cannot return errors.)

Can two different user-level processes find themselves running a system call at the same time? (Whatever conclusion you reach, your conclusions in matters such as these belong in your design document.)

Two different processes could both make syscalls at the same time, as they could be executing on different threads.

(When logged in, completion status appears here.)