kern/kernel/syscalls.c

804 lines
17 KiB
C
Raw Normal View History

2025-03-25 17:36:52 -04:00
/**
** @file syscalls.c
**
** @author CSCI-452 class of 20245
**
** @brief System call implementations
*/
#define KERNEL_SRC
2025-03-25 17:36:52 -04:00
#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)
2025-03-25 17:36:52 -04:00
#else
#define SYSCALL_ENTER(x) /* */
2025-03-25 17:36:52 -04:00
#endif /* TRACING_SYSCALLS */
2025-03-25 17:36:52 -04:00
#if TRACING_SYSRETS
#define SYSCALL_EXIT(x) \
do { \
cio_printf("<-- %s %08x\n", __func__, (uint32_t)(x)); \
return; \
} while (0)
2025-03-25 17:36:52 -04:00
#else
#define SYSCALL_EXIT(x) return
#endif /* TRACING_SYSRETS */
2025-03-25 17:36:52 -04:00
/*
** 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)
2025-03-25 17:36:52 -04:00
/*
** 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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// retrieve the exit status of this process
pcb->exit_status = (int32_t)ARG(pcb, 1);
2025-03-25 17:36:52 -04:00
// 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);
2025-03-25 17:36:52 -04:00
// pick a new winner
dispatch();
SYSCALL_EXIT(0);
2025-03-25 17:36:52 -04:00
}
/**
** 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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
/*
** 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);
2025-03-25 17:36:52 -04:00
if (target == pcb->pid) {
2025-03-25 17:36:52 -04:00
RET(pcb) = E_BAD_PARAM;
SYSCALL_EXIT(E_BAD_PARAM);
2025-03-25 17:36:52 -04:00
}
// Good. Now, figure out what we're looking for.
pcb_t *child = NULL;
if (target != 0) {
2025-03-25 17:36:52 -04:00
// we're looking for a specific child
child = pcb_find_pid(target);
2025-03-25 17:36:52 -04:00
if (child != NULL) {
2025-03-25 17:36:52 -04:00
// found the process; is it one of our children:
if (child->parent != pcb) {
2025-03-25 17:36:52 -04:00
// NO, so we can't wait for it
RET(pcb) = E_BAD_PARAM;
SYSCALL_EXIT(E_BAD_PARAM);
2025-03-25 17:36:52 -04:00
}
// yes! is this one ready to be collected?
if (child->state != STATE_ZOMBIE) {
2025-03-25 17:36:52 -04:00
// 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);
2025-03-25 17:36:52 -04:00
}
} 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) {
2025-03-25 17:36:52 -04:00
// found one!
found = true;
// has it already exited?
if (curr->state == STATE_ZOMBIE) {
2025-03-25 17:36:52 -04:00
// yes, so we're done here
child = curr;
break;
}
}
}
if (!found) {
2025-03-25 17:36:52 -04:00
// got through the loop without finding a child!
RET(pcb) = E_NO_CHILDREN;
SYSCALL_EXIT(E_NO_CHILDREN);
2025-03-25 17:36:52 -04:00
}
}
/*
** 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) {
2025-03-25 17:36:52 -04:00
// no - mark the parent as "Waiting"
pcb->state = STATE_WAITING;
assert(pcb_queue_insert(waiting, pcb) == SUCCESS);
2025-03-25 17:36:52 -04:00
// select a new current process
dispatch();
SYSCALL_EXIT((uint32_t)current);
2025-03-25 17:36:52 -04:00
}
// 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);
2025-03-25 17:36:52 -04:00
// if stat is NULL, the parent doesn't want the status
if (stat != NULL) {
2025-03-25 17:36:52 -04:00
// ********************************************************
// ** 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);
2025-03-25 17:36:52 -04:00
SYSCALL_EXIT(RET(pcb));
2025-03-25 17:36:52 -04:00
}
/**
** 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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// Make sure there's room for another process!
pcb_t *new;
if (pcb_alloc(&new) != SUCCESS || new == NULL) {
2025-03-25 17:36:52 -04:00
RET(pcb) = E_NO_PROCS;
SYSCALL_EXIT(RET(pcb));
2025-03-25 17:36:52 -04:00
}
// duplicate the memory image of the parent
int status = user_duplicate(new, pcb);
if (status != SUCCESS) {
pcb_free(new);
2025-03-25 17:36:52 -04:00
RET(pcb) = status;
SYSCALL_EXIT(status);
2025-03-25 17:36:52 -04:00
}
// 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);
2025-03-25 17:36:52 -04:00
SYSCALL_EXIT(new->pid);
2025-03-25 17:36:52 -04:00
}
/**
** 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);
2025-03-25 17:36:52 -04:00
uint_t what = ARG(pcb, 1);
const char **args = (const char **)ARG(pcb, 2);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// locate the requested program
prog_t *prog = user_locate(what);
if (prog == NULL) {
2025-03-25 17:36:52 -04:00
RET(pcb) = E_NOT_FOUND;
SYSCALL_EXIT(E_NOT_FOUND);
2025-03-25 17:36:52 -04:00
}
// we have located the program, but before we can load it,
// we need to clean up the existing VM hierarchy
vm_free(pcb->pdir);
2025-03-25 17:36:52 -04:00
pcb->pdir = NULL;
// "load" it and set up the VM tables for this process
2025-03-31 12:41:04 -04:00
int status = user_load(prog, pcb, args, false);
if (status != SUCCESS) {
2025-03-25 17:36:52 -04:00
RET(pcb) = status;
SYSCALL_EXIT(status);
2025-03-25 17:36:52 -04:00
}
/*
** 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);
2025-03-25 17:36:52 -04:00
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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// grab the arguments
uint_t chan = ARG(pcb, 1);
char *buf = (char *)ARG(pcb, 2);
uint_t len = ARG(pcb, 3);
2025-03-25 17:36:52 -04:00
// if the buffer is of length 0, we're done!
if (len == 0) {
2025-03-25 17:36:52 -04:00
RET(pcb) = 0;
SYSCALL_EXIT(0);
2025-03-25 17:36:52 -04:00
}
// try to get the next character(s)
int n = 0;
if (chan == CHAN_CIO) {
2025-03-25 17:36:52 -04:00
// console input is non-blocking
if (cio_input_queue() < 1) {
2025-03-25 17:36:52 -04:00
RET(pcb) = 0;
SYSCALL_EXIT(0);
2025-03-25 17:36:52 -04:00
}
// at least one character
n = cio_gets(buf, len);
2025-03-25 17:36:52 -04:00
RET(pcb) = n;
SYSCALL_EXIT(n);
2025-03-25 17:36:52 -04:00
} else if (chan == CHAN_SIO) {
2025-03-25 17:36:52 -04:00
// SIO input is blocking, so if there are no characters
// available, we'll block this process
n = sio_read(buf, len);
2025-03-25 17:36:52 -04:00
RET(pcb) = n;
SYSCALL_EXIT(n);
2025-03-25 17:36:52 -04:00
}
// bad channel code
RET(pcb) = E_BAD_PARAM;
SYSCALL_EXIT(E_BAD_PARAM);
2025-03-25 17:36:52 -04:00
}
/**
** 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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// grab the parameters
uint_t chan = ARG(pcb, 1);
char *buf = (char *)ARG(pcb, 2);
uint_t length = ARG(pcb, 3);
2025-03-25 17:36:52 -04:00
// 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);
2025-03-25 17:36:52 -04:00
} else if (chan == CHAN_SIO) {
sio_write(buf, length);
2025-03-25 17:36:52 -04:00
} else {
rval = E_BAD_CHAN;
}
}
RET(pcb) = rval;
SYSCALL_EXIT(rval);
2025-03-25 17:36:52 -04:00
}
/**
** sys_getpid - returns the PID of the calling process
**
** Implements:
** uint_t getpid( void );
*/
SYSIMPL(getpid)
{
2025-03-25 17:36:52 -04:00
// sanity check!
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// 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)
{
2025-03-25 17:36:52 -04:00
// sanity check!
assert(pcb != NULL);
assert(pcb->parent != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// return the time
RET(pcb) = pcb->parent->pid;
}
/**
** sys_gettime - returns the current system time
**
** Implements:
** uint32_t gettime( void );
*/
SYSIMPL(gettime)
{
2025-03-25 17:36:52 -04:00
// sanity check!
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// return the time
RET(pcb) = system_time;
}
/**
** sys_getprio - the scheduling priority of the calling process
**
** Implements:
** int getprio( void );
*/
SYSIMPL(getprio)
{
2025-03-25 17:36:52 -04:00
// sanity check!
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// return the time
RET(pcb) = pcb->priority;
}
/**
** sys_setprio - sets the scheduling priority of the calling process
**
** Implements:
** int setprio( int new );
*/
SYSIMPL(setprio)
{
2025-03-25 17:36:52 -04:00
// sanity check!
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// remember the old priority
int old = pcb->priority;
// set the priority
pcb->priority = ARG(pcb, 1);
2025-03-25 17:36:52 -04:00
// 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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// who is the victim?
uint_t pid = ARG(pcb, 1);
2025-03-25 17:36:52 -04:00
// if it's this process, convert this into a call to exit()
if (pid == pcb->pid) {
2025-03-25 17:36:52 -04:00
pcb->exit_status = EXIT_KILLED;
pcb_zombify(pcb);
2025-03-25 17:36:52 -04:00
dispatch();
SYSCALL_EXIT(EXIT_KILLED);
2025-03-25 17:36:52 -04:00
}
// must be a valid "ordinary user" PID
// QUESTION: what if it's the idle process?
if (pid < FIRST_USER_PID) {
2025-03-25 17:36:52 -04:00
RET(pcb) = E_FAILURE;
SYSCALL_EXIT(E_FAILURE);
2025-03-25 17:36:52 -04:00
}
// OK, this is an acceptable victim; see if it exists
pcb_t *victim = pcb_find_pid(pid);
if (victim == NULL) {
2025-03-25 17:36:52 -04:00
// nope!
RET(pcb) = E_NOT_FOUND;
SYSCALL_EXIT(E_NOT_FOUND);
2025-03-25 17:36:52 -04:00
}
// must have a state that is possible
assert(victim->state >= FIRST_VIABLE && victim->state < N_STATES);
2025-03-25 17:36:52 -04:00
// how we perform the kill depends on the victim's state
int32_t status = SUCCESS;
switch (victim->state) {
case STATE_KILLED: // FALL THROUGH
2025-03-25 17:36:52 -04:00
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
2025-03-25 17:36:52 -04:00
// 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);
2025-03-25 17:36:52 -04:00
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);
2025-03-25 17:36:52 -04:00
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);
2025-03-25 17:36:52 -04:00
}
SYSCALL_EXIT(status);
2025-03-25 17:36:52 -04:00
}
/**
** 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)
{
2025-03-25 17:36:52 -04:00
// sanity check
assert(pcb != NULL);
2025-03-25 17:36:52 -04:00
SYSCALL_ENTER(pcb->pid);
2025-03-25 17:36:52 -04:00
// get the desired duration
uint_t length = ARG(pcb, 1);
2025-03-25 17:36:52 -04:00
if (length == 0) {
2025-03-25 17:36:52 -04:00
// just yield the CPU
// sleep duration is 0
RET(pcb) = 0;
// back on the ready queue
schedule(pcb);
2025-03-25 17:36:52 -04:00
} else {
// sleep for a while
pcb->wakeup = system_time + length;
if (pcb_queue_insert(sleeping, pcb) != SUCCESS) {
2025-03-25 17:36:52 -04:00
// something strange is happening
WARNING("sleep pcb insert failed");
2025-03-25 17:36:52 -04:00
// if this is the current process, report an error
if (current == pcb) {
2025-03-25 17:36:52 -04:00
RET(pcb) = -1;
}
// return without dispatching a new process
return;
}
}
// only dispatch if the current process called us
if (pcb == current) {
2025-03-25 17:36:52 -04:00
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
2025-03-25 17:36:52 -04:00
};
/**
** 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)
{
2025-03-25 17:36:52 -04:00
// keep the compiler happy
(void)vector;
(void)code;
2025-03-25 17:36:52 -04:00
// sanity check!
assert(current != NULL);
assert(current->context != NULL);
2025-03-25 17:36:52 -04:00
// retrieve the syscall code
int num = REG(current, eax);
2025-03-25 17:36:52 -04:00
#if TRACING_SYSCALLS
cio_printf("** --> SYS pid %u code %u\n", current->pid, num);
2025-03-25 17:36:52 -04:00
#endif
// validate it
if (num < 0 || num >= N_SYSCALLS) {
2025-03-25 17:36:52 -04:00
// bad syscall number
// could kill it, but we'll just force it to exit
num = SYS_exit;
ARG(current, 1) = EXIT_BAD_SYSCALL;
2025-03-25 17:36:52 -04:00
}
// call the handler
syscalls[num](current);
2025-03-25 17:36:52 -04:00
#if TRACING_SYSCALLS
cio_printf("** <-- SYS pid %u ret %u\n", current->pid, RET(current));
2025-03-25 17:36:52 -04:00
#endif
// tell the PIC we're done
outb(PIC1_CMD, PIC_EOI);
2025-03-25 17:36:52 -04:00
}
/*
** PUBLIC FUNCTIONS
*/
/**
** Name: sys_init
**
** Syscall module initialization routine
**
** Dependencies:
** Must be called after cio_init()
*/
void sys_init(void)
{
2025-03-25 17:36:52 -04:00
#if TRACING_INIT
cio_puts(" Sys");
2025-03-25 17:36:52 -04:00
#endif
// install the second-stage ISR
install_isr(VEC_SYSCALL, sys_isr);
2025-03-25 17:36:52 -04:00
}