summaryrefslogtreecommitdiff
path: root/kernel/syscalls.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/syscalls.c')
-rw-r--r--kernel/syscalls.c829
1 files changed, 829 insertions, 0 deletions
diff --git a/kernel/syscalls.c b/kernel/syscalls.c
new file mode 100644
index 0000000..7176cda
--- /dev/null
+++ b/kernel/syscalls.c
@@ -0,0 +1,829 @@
+/**
+** @file syscalls.c
+**
+** @author CSCI-452 class of 20245
+**
+** @brief System call implementations
+*/
+
+#define KERNEL_SRC
+
+#include <common.h>
+
+#include <cio.h>
+#include <clock.h>
+#include <procs.h>
+#include <sio.h>
+#include <syscalls.h>
+#include <user.h>
+#include <vm.h>
+#include <x86/pic.h>
+
+/*
+** PRIVATE DEFINITIONS
+*/
+
+/*
+** Macros to simplify tracing a bit
+**
+** TRACING_SYSCALLS and TRACING_SYSRETS are defined in debug.h,
+** controlled by the TRACE ** macro. If not tracing these, SYSCALL_ENTER
+** is a no-op, and SYSCALL_EXIT just does a return.
+*/
+
+#if TRACING_SYSCALLS
+
+#define SYSCALL_ENTER(x) do { \
+ cio_printf( "--> %s, pid %08x", __func__, (uint32_t) (x) ); \
+ } while(0)
+
+#else
+
+#define SYSCALL_ENTER(x) /* */
+
+#endif /* TRACING_SYSCALLS */
+
+#if TRACING_SYSRETS
+
+#define SYSCALL_EXIT(x) do { \
+ cio_printf( "<-- %s %08x\n", __func__, (uint32_t) (x) ); \
+ return; \
+ } while(0)
+
+#else
+
+#define SYSCALL_EXIT(x) return
+
+#endif /* TRACING_SYSRETS */
+
+/*
+** PRIVATE DATA TYPES
+*/
+
+/*
+** PUBLIC GLOBAL VARIABLES
+*/
+
+/*
+** IMPLEMENTATION FUNCTIONS
+*/
+
+// a macro to simplify syscall entry point specification
+// we don't declare these static because we may want to call
+// some of them from other parts of the kernel
+#define SYSIMPL(x) void sys_##x( pcb_t * pcb )
+
+/*
+** Second-level syscall handlers
+**
+** All have this prototype:
+**
+** static void sys_NAME( pcb_t *pcb );
+**
+** where the parameter 'pcb' is a pointer to the PCB of the process
+** making the system call.
+**
+** Values being returned to the user are placed into the EAX
+** field in the context save area for that process.
+*/
+
+/**
+** sys_exit - terminate the calling process
+**
+** Implements:
+** void exit( int32_t status );
+**
+** Does not return
+*/
+SYSIMPL(exit) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // retrieve the exit status of this process
+ pcb->exit_status = (int32_t) ARG(pcb,1);
+
+ // now, we need to do the following:
+ // reparent any children of this process and wake up init if need be
+ // find this process' parent and wake it up if it's waiting
+
+ pcb_zombify( pcb );
+
+ // pick a new winner
+ dispatch();
+
+ SYSCALL_EXIT( 0 );
+}
+
+/**
+** sys_waitpid - wait for a child process to terminate
+**
+** Implements:
+** int waitpid( uint_t pid, int32_t *status );
+**
+** Blocks the calling process until the specified child (or any child)
+** of the caller terminates. Intrinsic return is the PID of the child that
+** terminated, or an error code; on success, returns the child's termination
+** status via 'status' if that pointer is non-NULL.
+*/
+SYSIMPL(waitpid) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ /*
+ ** We need to do two things here: (1) find out whether or
+ ** not this process has any children in the system, and (2)
+ ** find out whether the desired child (or any child, if the
+ ** target PID is 0) has terminated.
+ **
+ ** To do this, we loop until we find a the requested PID or
+ ** a Zombie child process, or have gone through all of the
+ ** slots in the process table.
+ **
+ ** If the target PID is 0, we don't care which child process
+ ** we reap here; there could be several, but we only need to
+ ** find one.
+ */
+
+ // verify that we aren't looking for ourselves!
+ uint_t target = ARG(pcb,1);
+
+ if( target == pcb->pid ) {
+ RET(pcb) = E_BAD_PARAM;
+ SYSCALL_EXIT( E_BAD_PARAM );
+ }
+
+ // Good. Now, figure out what we're looking for.
+
+ pcb_t *child = NULL;
+
+ if( target != 0 ) {
+
+ // we're looking for a specific child
+ child = pcb_find_pid( target );
+
+ if( child != NULL ) {
+
+ // found the process; is it one of our children:
+ if( child->parent != pcb ) {
+ // NO, so we can't wait for it
+ RET(pcb) = E_BAD_PARAM;
+ SYSCALL_EXIT( E_BAD_PARAM );
+ }
+
+ // yes! is this one ready to be collected?
+ if( child->state != STATE_ZOMBIE ) {
+ // no, so we'll have to block for now
+ child = NULL;
+ }
+
+ } else {
+
+ // no such child
+ RET(pcb) = E_BAD_PARAM;
+ SYSCALL_EXIT( E_BAD_PARAM );
+
+ }
+
+ } else {
+
+ // looking for any child
+
+ // we need to find a process that is our child
+ // and has already exited
+
+ child = NULL;
+ bool_t found = false;
+
+ // unfortunately, we can't stop at the first child,
+ // so we need to do the iteration ourselves
+ register pcb_t *curr = ptable;
+
+ for( int i = 0; i < N_PROCS; ++i, ++curr ) {
+
+ if( curr->parent == pcb ) {
+
+ // found one!
+ found = true;
+
+ // has it already exited?
+ if( curr->state == STATE_ZOMBIE ) {
+ // yes, so we're done here
+ child = curr;
+ break;
+ }
+ }
+ }
+
+ if( !found ) {
+ // got through the loop without finding a child!
+ RET(pcb) = E_NO_CHILDREN;
+ SYSCALL_EXIT( E_NO_CHILDREN );
+ }
+
+ }
+
+ /*
+ ** At this point, one of these situations is true:
+ **
+ ** * we are looking for a specific child and found it
+ ** * we are looking for any child and found one
+ **
+ ** Either way, 'child' will be non-NULL if the selected
+ ** process has already become a Zombie. If that's the
+ ** case, we collect its status and clean it up; otherwise,
+ ** we block this process.
+ */
+
+ // did we find one to collect?
+ if( child == NULL ) {
+
+ // no - mark the parent as "Waiting"
+ pcb->state = STATE_WAITING;
+ assert( pcb_queue_insert(waiting,pcb) == SUCCESS );
+
+ // select a new current process
+ dispatch();
+ SYSCALL_EXIT( (uint32_t) current );
+ }
+
+ // found a Zombie; collect its information and clean it up
+ RET(pcb) = child->pid;
+
+ // get "status" pointer from parent
+ int32_t *stat = (int32_t *) ARG(pcb,2);
+
+ // if stat is NULL, the parent doesn't want the status
+ if( stat != NULL ) {
+ // ********************************************************
+ // ** Potential VM issue here! This code assigns the exit
+ // ** status into a variable in the parent's address space.
+ // ** This works in the baseline because we aren't using
+ // ** any type of memory protection. If address space
+ // ** separation is implemented, this code will very likely
+ // ** STOP WORKING, and will need to be fixed.
+ // ********************************************************
+ *stat = child->exit_status;
+ }
+
+ // clean up the child
+ pcb_cleanup( child );
+
+ SYSCALL_EXIT( RET(pcb) );
+}
+
+/**
+** sys_fork - create a new process
+**
+** Implements:
+** int fork( void );
+**
+** Creates a new process that is a duplicate of the calling process.
+** Returns the child's PID to the parent, and 0 to the child, on success;
+** else, returns an error code to the parent.
+*/
+SYSIMPL(fork) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // Make sure there's room for another process!
+ pcb_t *new;
+ if( pcb_alloc(&new) != SUCCESS || new == NULL ) {
+ RET(pcb) = E_NO_PROCS;
+ SYSCALL_EXIT( RET(pcb) );
+ }
+
+ // duplicate the memory image of the parent
+ int status = user_duplicate( new, pcb );
+ if( status != SUCCESS ) {
+ pcb_free( new );
+ RET(pcb) = status;
+ SYSCALL_EXIT( status );
+ }
+
+ // Set the child's identity.
+ new->pid = next_pid++;
+ new->parent = pcb;
+ new->state = STATE_NEW;
+
+ // replicate other things inherited from the parent
+ new->priority = pcb->priority;
+
+ // Set the return values for the two processes.
+ RET(pcb) = new->pid;
+ RET(new) = 0;
+
+ // Schedule the child, and let the parent continue.
+ schedule( new );
+
+ SYSCALL_EXIT( new->pid );
+}
+
+/**
+** sys_exec - replace the memory image of a process
+**
+** Implements:
+** void exec( uint_t what, char **args );
+**
+** Replaces the memory image of the calling process with that of the
+** indicated program.
+**
+** Returns only on failure.
+*/
+SYSIMPL(exec)
+{
+ // sanity check
+ assert( pcb != NULL );
+
+ uint_t what = ARG(pcb,1);
+ const char **args = (const char **) ARG(pcb,2);
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // locate the requested program
+ prog_t *prog = user_locate( what );
+ if( prog == NULL ) {
+ RET(pcb) = E_NOT_FOUND;
+ SYSCALL_EXIT( E_NOT_FOUND );
+ }
+
+ // we have located the program, but before we can load it,
+ // we need to clean up the existing VM hierarchy
+ vm_free( pcb->pdir );
+ pcb->pdir = NULL;
+
+ // "load" it and set up the VM tables for this process
+ int status = user_load( prog, pcb, args );
+ if( status != SUCCESS ) {
+ RET(pcb) = status;
+ SYSCALL_EXIT( status );
+ }
+
+ /*
+ ** Decision:
+ ** (A) schedule this process and dispatch another,
+ ** (B) let this one continue in its current time slice
+ ** (C) reset this one's time slice and let it continue
+ **
+ ** We choose option A.
+ **
+ ** If scheduling the process fails, the exec() has failed. However,
+ ** all trace of the old process is gone by now, so we can't return
+ ** an error status to it.
+ */
+
+ schedule( pcb );
+
+ dispatch();
+}
+
+/**
+** sys_read - read into a buffer from an input channel
+**
+** Implements:
+** int read( uint_t chan, void *buffer, uint_t length );
+**
+** Reads up to 'length' bytes from 'chan' into 'buffer'. Returns the
+** count of bytes actually transferred.
+*/
+SYSIMPL(read) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // grab the arguments
+ uint_t chan = ARG(pcb,1);
+ char *buf = (char *) ARG(pcb,2);
+ uint_t len = ARG(pcb,3);
+
+ // if the buffer is of length 0, we're done!
+ if( len == 0 ) {
+ RET(pcb) = 0;
+ SYSCALL_EXIT( 0 );
+ }
+
+ // try to get the next character(s)
+ int n = 0;
+
+ if( chan == CHAN_CIO ) {
+
+ // console input is non-blocking
+ if( cio_input_queue() < 1 ) {
+ RET(pcb) = 0;
+ SYSCALL_EXIT( 0 );
+ }
+ // at least one character
+ n = cio_gets( buf, len );
+ RET(pcb) = n;
+ SYSCALL_EXIT( n );
+
+ } else if( chan == CHAN_SIO ) {
+
+ // SIO input is blocking, so if there are no characters
+ // available, we'll block this process
+ n = sio_read( buf, len );
+ RET(pcb) = n;
+ SYSCALL_EXIT( n );
+
+ }
+
+ // bad channel code
+ RET(pcb) = E_BAD_PARAM;
+ SYSCALL_EXIT( E_BAD_PARAM );
+}
+
+/**
+** sys_write - write from a buffer to an output channel
+**
+** Implements:
+** int write( uint_t chan, const void *buffer, uint_t length );
+**
+** Writes 'length' bytes from 'buffer' to 'chan'. Returns the
+** count of bytes actually transferred.
+*/
+SYSIMPL(write) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // grab the parameters
+ uint_t chan = ARG(pcb,1);
+ char *buf = (char *) ARG(pcb,2);
+ uint_t length = ARG(pcb,3);
+
+ // this is almost insanely simple, but it does separate the
+ // low-level device access fromm the higher-level syscall implementation
+
+ // assume we write the indicated amount
+ int rval = length;
+
+ // simplest case
+ if( length >= 0 ) {
+
+ if( chan == CHAN_CIO ) {
+
+ cio_write( buf, length );
+
+ } else if( chan == CHAN_SIO ) {
+
+ sio_write( buf, length );
+
+ } else {
+
+ rval = E_BAD_CHAN;
+
+ }
+
+ }
+
+ RET(pcb) = rval;
+
+ SYSCALL_EXIT( rval );
+}
+
+/**
+** sys_getpid - returns the PID of the calling process
+**
+** Implements:
+** uint_t getpid( void );
+*/
+SYSIMPL(getpid) {
+
+ // sanity check!
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // return the time
+ RET(pcb) = pcb->pid;
+}
+
+/**
+** sys_getppid - returns the PID of the parent of the calling process
+**
+** Implements:
+** uint_t getppid( void );
+*/
+SYSIMPL(getppid) {
+
+ // sanity check!
+ assert( pcb != NULL );
+ assert( pcb->parent != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // return the time
+ RET(pcb) = pcb->parent->pid;
+}
+
+/**
+** sys_gettime - returns the current system time
+**
+** Implements:
+** uint32_t gettime( void );
+*/
+SYSIMPL(gettime) {
+
+ // sanity check!
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // return the time
+ RET(pcb) = system_time;
+}
+
+/**
+** sys_getprio - the scheduling priority of the calling process
+**
+** Implements:
+** int getprio( void );
+*/
+SYSIMPL(getprio) {
+
+ // sanity check!
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // return the time
+ RET(pcb) = pcb->priority;
+}
+
+/**
+** sys_setprio - sets the scheduling priority of the calling process
+**
+** Implements:
+** int setprio( int new );
+*/
+SYSIMPL(setprio) {
+
+ // sanity check!
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // remember the old priority
+ int old = pcb->priority;
+
+ // set the priority
+ pcb->priority = ARG(pcb,1);
+
+ // return the old value
+ RET(pcb) = old;
+}
+
+/**
+** sys_kill - terminate a process with extreme prejudice
+**
+** Implements:
+** int32_t kill( uint_t pid );
+**
+** Marks the specified process (or the calling process, if PID is 0)
+** as "killed". Returns 0 on success, else an error code.
+*/
+SYSIMPL(kill) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // who is the victim?
+ uint_t pid = ARG(pcb,1);
+
+ // if it's this process, convert this into a call to exit()
+ if( pid == pcb->pid ) {
+ pcb->exit_status = EXIT_KILLED;
+ pcb_zombify( pcb );
+ dispatch();
+ SYSCALL_EXIT( EXIT_KILLED );
+ }
+
+ // must be a valid "ordinary user" PID
+ // QUESTION: what if it's the idle process?
+ if( pid < FIRST_USER_PID ) {
+ RET(pcb) = E_FAILURE;
+ SYSCALL_EXIT( E_FAILURE );
+ }
+
+ // OK, this is an acceptable victim; see if it exists
+ pcb_t *victim = pcb_find_pid( pid );
+ if( victim == NULL ) {
+ // nope!
+ RET(pcb) = E_NOT_FOUND;
+ SYSCALL_EXIT( E_NOT_FOUND );
+ }
+
+ // must have a state that is possible
+ assert( victim->state >= FIRST_VIABLE && victim->state < N_STATES );
+
+ // how we perform the kill depends on the victim's state
+ int32_t status = SUCCESS;
+
+ switch( victim->state ) {
+
+ case STATE_KILLED: // FALL THROUGH
+ case STATE_ZOMBIE:
+ // you can't kill it if it's already dead
+ RET(pcb) = SUCCESS;
+ break;
+
+ case STATE_READY: // FALL THROUGH
+ case STATE_SLEEPING: // FALL THROUGH
+ case STATE_BLOCKED: // FALL THROUGH
+ // here, the process is on a queue somewhere; mark
+ // it as "killed", and let the scheduler deal with it
+ victim->state = STATE_KILLED;
+ RET(pcb) = SUCCESS;
+ break;
+
+ case STATE_RUNNING:
+ // we have met the enemy, and it is us!
+ pcb->exit_status = EXIT_KILLED;
+ pcb_zombify( pcb );
+ status = EXIT_KILLED;
+ // we need a new current process
+ dispatch();
+ break;
+
+ case STATE_WAITING:
+ // similar to the 'running' state, but we don't need
+ // to dispatch a new process
+ victim->exit_status = EXIT_KILLED;
+ status = pcb_queue_remove_this( waiting, victim );
+ pcb_zombify( victim );
+ RET(pcb) = status;
+ break;
+
+ default:
+ // this is a really bad potential problem - we have an
+ // unexpected or bogus process state, but we didn't
+ // catch that earlier.
+ sprint( b256, "*** kill(): victim %d, odd state %d\n",
+ victim->pid, victim->state );
+ PANIC( 0, b256 );
+ }
+
+ SYSCALL_EXIT( status );
+}
+
+
+/**
+** sys_sleep - put the calling process to sleep for some length of time
+**
+** Implements:
+** uint_t sleep( uint_t ms );
+**
+** Puts the calling process to sleep for 'ms' milliseconds (or just yields
+** the CPU if 'ms' is 0). ** Returns the time the process spent sleeping.
+*/
+SYSIMPL(sleep) {
+
+ // sanity check
+ assert( pcb != NULL );
+
+ SYSCALL_ENTER( pcb->pid );
+
+ // get the desired duration
+ uint_t length = ARG( pcb, 1 );
+
+ if( length == 0 ) {
+
+ // just yield the CPU
+ // sleep duration is 0
+ RET(pcb) = 0;
+
+ // back on the ready queue
+ schedule( pcb );
+
+ } else {
+
+ // sleep for a while
+ pcb->wakeup = system_time + length;
+
+ if( pcb_queue_insert(sleeping,pcb) != SUCCESS ) {
+ // something strange is happening
+ WARNING( "sleep pcb insert failed" );
+ // if this is the current process, report an error
+ if( current == pcb ) {
+ RET(pcb) = -1;
+ }
+ // return without dispatching a new process
+ return;
+ }
+ }
+
+ // only dispatch if the current process called us
+ if( pcb == current ) {
+ current = NULL;
+ dispatch();
+ }
+}
+
+/*
+** PRIVATE FUNCTIONS GLOBAL VARIABLES
+*/
+
+/*
+** The system call jump table
+**
+** Initialized using designated initializers to ensure the entries
+** are correct even if the syscall code values should happen to change.
+** This also makes it easy to add new system call entries, as their
+** position in the initialization list is irrelevant.
+*/
+
+static void (* const syscalls[N_SYSCALLS])( pcb_t * ) = {
+ [ SYS_exit ] = sys_exit,
+ [ SYS_waitpid ] = sys_waitpid,
+ [ SYS_fork ] = sys_fork,
+ [ SYS_exec ] = sys_exec,
+ [ SYS_read ] = sys_read,
+ [ SYS_write ] = sys_write,
+ [ SYS_getpid ] = sys_getpid,
+ [ SYS_getppid ] = sys_getppid,
+ [ SYS_gettime ] = sys_gettime,
+ [ SYS_getprio ] = sys_getprio,
+ [ SYS_setprio ] = sys_setprio,
+ [ SYS_kill ] = sys_kill,
+ [ SYS_sleep ] = sys_sleep
+};
+
+/**
+** Name: sys_isr
+**
+** System call ISR
+**
+** @param vector Vector number for this interrupt
+** @param code Error code (0 for this interrupt)
+*/
+static void sys_isr( int vector, int code ) {
+
+ // keep the compiler happy
+ (void) vector;
+ (void) code;
+
+ // sanity check!
+ assert( current != NULL );
+ assert( current->context != NULL );
+
+ // retrieve the syscall code
+ int num = REG( current, eax );
+
+#if TRACING_SYSCALLS
+ cio_printf( "** --> SYS pid %u code %u\n", current->pid, num );
+#endif
+
+ // validate it
+ if( num < 0 || num >= N_SYSCALLS ) {
+ // bad syscall number
+ // could kill it, but we'll just force it to exit
+ num = SYS_exit;
+ ARG(current,1) = EXIT_BAD_SYSCALL;
+ }
+
+ // call the handler
+ syscalls[num]( current );
+
+#if TRACING_SYSCALLS
+ cio_printf( "** <-- SYS pid %u ret %u\n", current->pid, RET(current) );
+#endif
+
+ // tell the PIC we're done
+ outb( PIC1_CMD, PIC_EOI );
+}
+
+/*
+** PUBLIC FUNCTIONS
+*/
+
+/**
+** Name: sys_init
+**
+** Syscall module initialization routine
+**
+** Dependencies:
+** Must be called after cio_init()
+*/
+void sys_init( void ) {
+
+#if TRACING_INIT
+ cio_puts( " Sys" );
+#endif
+
+ // install the second-stage ISR
+ install_isr( VEC_SYSCALL, sys_isr );
+}