axf-os161 / kern / syscall / proc_syscalls.c
proc_syscalls.c
Raw
#include <types.h>
#include <proc_syscalls.h>
#include <syscall.h>
#include <lib.h>
#include <limits.h>
#include <types.h>
#include <kern/errno.h>
#include <machine/trapframe.h>
#include <thread.h>
#include <synch.h>
#include <addrspace.h>
#include <proc.h>
#include <current.h>
#include <spl.h>
#include <pid.h>
#include <kern/wait.h>
#include <copyinout.h>
#include <vfs.h>
#include <kern/fcntl.h>

static void thread_fork_start(void *tf, unsigned long vmspace);

/*
 * Implementation of fork system call
 * arguments:
 * tf: trapframe to copy
 * err: pointer to error return value
 */
pid_t
sys_fork(struct trapframe *tf, int* err)
{
    *err = 0;
    struct trapframe *new_tf;
    struct addrspace *addr_space;

    // Clone the current process
    struct proc *new_proc = proc_clone(err);
    if (*err) {
        *err = ENOMEM;
        return -1;
    }

    // Allocate for new trapframe
    new_tf = kmalloc(sizeof(struct trapframe));
    if(new_tf == NULL)
    {
        *err = ENOMEM;
        return -1;
    }

    // Copy original tf to new tf
    memmove(new_tf, tf, sizeof(struct trapframe));

    // Allocate for address space
    addr_space = kmalloc(sizeof(struct addrspace));
    if (addr_space == NULL) {
        kfree(new_tf);
        *err = ENOMEM;
        return -1;
    }

    // Copy over address spaces
    struct addrspace *old_as = proc_getas();
	int return_as = as_copy(old_as, &addr_space);
	if (return_as) {
        kfree(new_tf);
        kfree(addr_space);
        *err = ENOMEM;
		return -1;
	}
    
    // Fork thread into created process
    int return_thread = thread_fork("Child Fork", new_proc,
			        thread_fork_start, (void *) new_tf, (unsigned long) addr_space);
    if (return_thread) {
        kfree(new_tf);
        as_destroy(addr_space);
        *err = ENOMEM;
        return -1;
    }

    return new_proc->pid;
}

/*
 * Initial instructions to newly forked process
 */
static void
thread_fork_start(void *tf, unsigned long vmspace)
{
    int spl = splhigh();
    
    // Unused for now
    (void)vmspace;

    // Create trapframe on local stack, free the malloc'ed one
	struct trapframe child_frame = *(struct trapframe*) tf;
    kfree(tf);

	// change the register values
	child_frame.tf_v0 = 0;
	child_frame.tf_a3 = 0;
	child_frame.tf_epc += 4;

	mips_usermode(&child_frame);

    splx(spl);

	return;
}

/*
 * Implementation of getpid system call
 */
pid_t
sys_getpid(void)
{
    pid_t pid = curproc->pid;
    return pid;
}

/*
 * Kernel-Accessibly helper function for the sys_waitpid system call
 */
int
wait_pid(pid_t pid, int* status, int options)
{
    if(pid == curproc->pid)
    {
        return EINVAL;
    }

    if (options != 0 && options != WNOHANG)
    {
        return EINVAL;
    }
    if (status == NULL)
    {
        return EFAULT;
    }

    if(pid <= INVALID_PID)
    {
        return ENOSYS;
    }

    lock_acquire(pid_list_lock);

    pid_t parent_pid = curproc->pid;

    // Check that process is child before waiting
    if(!pid_is_child(pid, parent_pid))
    {
        lock_release(pid_list_lock);
        return ECHILD;
    }

    struct pid_node * parent = pid_get_node(curproc->pid);

    if(parent == NULL)
    {
        lock_release(pid_list_lock);
        return ESRCH;
    }

    struct pid_node *child = pid_get_node(pid);

    if(child == NULL)
    {
        lock_release(pid_list_lock);
        return EINVAL;
    }

    // wait on condition variable if child is not yet finished
    if(child->finished == false)
    {
        cv_wait(child->pid_cv, pid_list_lock);
        KASSERT(child->finished);
    }

    *status = child->exit_status;

    pid_remove_child(pid, curproc->pid);

    lock_release(pid_list_lock);

    pid_remove(pid);

    return 0;
}

/*
 * Implementation of wait_pid system call
 * arguments:
 * pid: process to wait on to exit
 * status: process exit status
 * options: options to vary waitpid behaviour
 * err: pointer to error return value
 */
pid_t
sys_waitpid(pid_t pid, userptr_t status, int options, int* err)
{
    *err = 0;
    int return_status;

    if (pid > __PID_MAX || pid < 2) {
        *err = ESRCH;
        return -1;
    }

    if (options != 0) {
        *err = EINVAL;
        return -1;
    }

    // bulk of work done in kernel-accessible wait_pid function
    *err = wait_pid(pid, &return_status, options);
    if(*err)
    {
        return -1;
    }

    if(status != NULL)
    {
        *err = copyout(&return_status, status, sizeof(int));
    }

	return pid;
}

/*
 * Implementation of exit system call
 * arguments:
 * status: exit code to report
 */
__DEAD void
sys_exit(int status)
{
    lock_acquire(pid_list_lock);
    
    struct proc *proc_to_exit = curproc;
    KASSERT(proc_to_exit != kproc);

    struct pid_node *cur_pid = pid_get_node(proc_to_exit->pid);
    KASSERT(cur_pid != NULL);
    
    cur_pid->finished = true;
    cur_pid->exit_status = status;

    // Dissociate Children
    while(cur_pid->child_list != NULL)
    {
        struct pid_node *child_pid = pid_get_node(cur_pid->child_list->pid);

        if(child_pid == NULL)
        {
            break;
        }

        if(child_pid->finished)
        {
            pid_remove_child(child_pid->pid, cur_pid->pid);
            pid_remove(child_pid->pid);
        }
        cur_pid->child_list = cur_pid->child_list->next;
    }

    if(cur_pid->parent_pid != INVALID_PID) // Has parent
    {
        cv_broadcast(cur_pid->pid_cv, pid_list_lock);
    }
    else // Has no parent
    {
        lock_release(pid_list_lock);
        pid_remove(proc_to_exit->pid);
    }

    lock_release(pid_list_lock);

    // remove thread from exiting proc
    KASSERT(curthread->t_proc == proc_to_exit);
    proc_remthread(curthread);
    proc_addthread(kproc, curthread);
    KASSERT(threadarray_num(&proc_to_exit->p_threads) == 0);

    proc_destroy(proc_to_exit);

    // exit process thread
    thread_exit();

    // exit kernel thread
    thread_exit();
}

/*
 * Implementation of execv system call
 * arguments:
 * program: program to replace currently executing program
 * args: arguments to pass to program
 * err: pointer to error return value
 */
int
sys_execv(userptr_t program, userptr_t args, int* err) {
    *err = 0;

    if (program == NULL || args == NULL) {
        *err = EFAULT;
        return -1;
    }

    char *filename = kmalloc(PATH_MAX);
    struct vnode* v;

    int instr_return = copyinstr(program, filename, PATH_MAX, NULL);
    if (instr_return) {
        *err = EFAULT;
        kfree(filename);
        return -1;
    }

    instr_return = vfs_open(filename, O_RDONLY, 0, &v);
    if (instr_return) {
        *err = instr_return;
        kfree(filename);
        return -1;
    }

    // Start with 4K size
    int arg_size = PAGE_SIZE;

    size_t data_length = 0;
    size_t arg_len = 0;
    int argCount = 0;
    userptr_t cur_arg;
    userptr_t shifted_args = args;
    char *argv;
ALLOC_ARGS:

    data_length = 0;
    arg_len = 0;
    argCount = 0;
    shifted_args = args;
    argv = kmalloc(arg_size);

    if(argv==NULL)
    {
        *err = ENOMEM;
        kfree(filename);
        return 1;
    }
    
    while(true)
    {
        // Get address of an argument
        instr_return = copyin(shifted_args, &cur_arg, sizeof(userptr_t));
        if (instr_return) {
            *err = EFAULT;
            vfs_close(v);
            kfree(argv);
            kfree(filename);
            return -1;
        }

        // if argument null, reached end of arguments
        if (cur_arg == NULL) {
			break;
		}
    
        // Copy argument contents into argument list argv
        instr_return = copyinstr(cur_arg, argv + data_length, arg_size - data_length, &arg_len);
        if(instr_return == ENAMETOOLONG)
        {
            // If 4K was not enough, try again with 64K
            kfree(argv);
            arg_size = ARG_MAX;
            goto ALLOC_ARGS;
        }
        else if (instr_return) {
            *err = EFAULT;
            vfs_close(v);
            kfree(argv);
            kfree(filename);
            return - 1;
        }

        // Update argument count and apply necessary shifts for next read
        argCount++;
        data_length += arg_len;
        shifted_args += sizeof(userptr_t);
    }

    vaddr_t entryAddr,stackAddr;
    struct addrspace *vm_backup;
    struct addrspace *vm_new;

    // Create a new address space for program
    vm_new = as_create();
    if (vm_new == NULL) {
        vfs_close(v);
        *err = ENOMEM;
        kfree(argv);
        kfree(filename);
        return -1;
    }

    /*
     * Create a backup of original address space to restore if needed 
     * and set current proc's address space to newly created one
     */
    spinlock_acquire(&curproc->p_lock);
    vm_backup = curproc->p_addrspace;
    curproc->p_addrspace = vm_new;
    spinlock_release(&curproc->p_lock);
    
    as_activate();

    // Load elf pointed to by program pointer
    instr_return = load_elf(v, &entryAddr);
    if (instr_return) {
        // Restore original address space and cleanup before exiting
        spinlock_acquire(&curproc->p_lock);
        curproc->p_addrspace = vm_backup;
        spinlock_release(&curproc->p_lock);
        vfs_close(v);     
        as_destroy(vm_new);        
        *err = instr_return;
        kfree(filename);
        return -1;
    }

    vfs_close(v);

    instr_return = as_define_stack(vm_new, &stackAddr);
    if (instr_return) {
        // Restore original address space and cleanup before exiting
        spinlock_acquire(&curproc->p_lock);
        curproc->p_addrspace = vm_backup;
        spinlock_release(&curproc->p_lock);
        as_destroy(vm_new);        
        *err = instr_return;
        kfree(filename);
        return -1;
    }

    /*
    * stackAddr initialized by DUMBVM to 0x80000000, marking the top of the kuseg user segment
    * Above the user stack, there will be a data segment and an argument segment containing arg data and pointers
    * Want to shift stackAddr down below these segments and populate these areas of the stack before passing to usermode
    * 0x80000000
    * 0x7fffffff┌────────────┐
    *           │            │ kuseg
    *           │            │
    *           │Data Segment│
    *           │            │
    *           │            │
    *           │            │
    *           │            │
    *           ├────────────┤
    *           │Arg Segment │
    *           │            │
    *           │            │
    *           │            │
    *           │            │
    *           │            │
    *  stackAddr├────────────┤
    *           │            │
    *           │            │
    *           │            │
    *           │            │
    *           └────────────┘
    */

    // Use stack pointer as starting address to determine address locations of data and args
    // Adjust stack pointer depending on data size and mark bottom of data segment, data segment stored above args
    stackAddr -= data_length;
    // Ensure that stack address is aligned
    stackAddr -= (stackAddr & (sizeof(void *) - 1));
    userptr_t user_stack_data_vaddr = (userptr_t)stackAddr;

    // Move stack down to make space for args and mark bottom of arg segment
    stackAddr -=(argCount + 1) * sizeof(userptr_t);
    userptr_t user_stack_args_vaddr = (userptr_t)stackAddr;

    for(size_t offset = 0; offset < data_length; offset += arg_len)
    {
        cur_arg =  user_stack_data_vaddr + offset;

        // copy out adress to argument to userspace
        instr_return = copyout(&cur_arg, user_stack_args_vaddr, sizeof(cur_arg));
        if (instr_return)
        {
            spinlock_acquire(&curproc->p_lock);
            curproc->p_addrspace = vm_backup;
            spinlock_release(&curproc->p_lock);
            as_destroy(vm_new);        
            *err = instr_return;
            kfree(filename);
            return -1;
        }

        // copy out argument to userspace
        instr_return = copyoutstr(argv + offset, cur_arg, data_length - offset , &arg_len);
        if (instr_return)
        {
            spinlock_acquire(&curproc->p_lock);
            curproc->p_addrspace = vm_backup;
            spinlock_release(&curproc->p_lock);
            as_destroy(vm_new);        
            *err = instr_return;
            kfree(filename);
            return -1;
        }
        // Advance args vaddr pointer by size of written argument
        user_stack_args_vaddr += sizeof(cur_arg);
    }

    // Add null entry to end of arg list
    cur_arg = NULL;
    instr_return = copyout(&cur_arg, user_stack_args_vaddr, sizeof(cur_arg));
    if (instr_return)
    {
        spinlock_acquire(&curproc->p_lock);
        curproc->p_addrspace = vm_backup;
        spinlock_release(&curproc->p_lock);
        as_destroy(vm_new);        
        *err = instr_return;
        kfree(filename);
        return -1;
    }

    // Backup no longer needed, cannot allow execv to fail after this
    if(vm_backup)
    {
        as_destroy(vm_backup);
    }

    // Update current thread's name based on input
    kfree(curthread->t_name);
    curthread->t_name = kstrdup(filename);
    kfree(filename);
	
	enter_new_process(argCount, (userptr_t)stackAddr, NULL, stackAddr, entryAddr);

    panic("returned from new process");
	
	*err = EINVAL;
	return -1;
}