diff options
Diffstat (limited to 'kernel/syscalls.c')
-rw-r--r-- | kernel/syscalls.c | 829 |
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 ); +} |