diff options
Diffstat (limited to 'kernel/old/syscalls.c')
-rw-r--r-- | kernel/old/syscalls.c | 803 |
1 files changed, 803 insertions, 0 deletions
diff --git a/kernel/old/syscalls.c b/kernel/old/syscalls.c new file mode 100644 index 0000000..92a0a23 --- /dev/null +++ b/kernel/old/syscalls.c @@ -0,0 +1,803 @@ +/** +** @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, false); + 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); +} |