diff --git a/src/threads/thread.c b/src/threads/thread.c index 7102ad2..457f7b9 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -659,6 +659,8 @@ init_thread (struct thread *t, const char *name, int nice, int priority, t->recent_cpu = recent_cpu; t->priority = t->base_priority; + t->exit_status = -1; + old_level = intr_disable (); list_push_back (&all_list, &t->allelem); intr_set_level (old_level); diff --git a/src/threads/thread.h b/src/threads/thread.h index c7cc364..1c05030 100644 --- a/src/threads/thread.h +++ b/src/threads/thread.h @@ -111,6 +111,7 @@ struct thread /* Shared between thread.c and synch.c. */ struct list_elem elem; /* List element. */ + int exit_status; /* Exit Status: 0 = successful exit. */ #ifdef USERPROG /* Owned by userprog/process.c. */ diff --git a/src/userprog/process.c b/src/userprog/process.c index 49b9bd5..6c8ef2d 100644 --- a/src/userprog/process.c +++ b/src/userprog/process.c @@ -14,9 +14,21 @@ #include "threads/flags.h" #include "threads/init.h" #include "threads/interrupt.h" +#include "threads/synch.h" #include "threads/palloc.h" +#include "threads/malloc.h" #include "threads/thread.h" #include "threads/vaddr.h" +#include "threads/synch.h" +#include "devices/timer.h" + +/* Keeps track of the position of pointers to user program arguments + within a linked list. */ +struct arg_elem + { + char* arg; + struct list_elem elem; + }; static thread_func start_process NO_RETURN; static bool load (const char *cmdline, void (**eip) (void), void **esp); @@ -36,6 +48,9 @@ process_execute (const char *file_name) fn_copy = palloc_get_page (0); if (fn_copy == NULL) return TID_ERROR; + + /* Imposing implicit limit that the command line arguments + including the user program name fit within a single page. */ strlcpy (fn_copy, file_name, PGSIZE); /* Create a new thread to execute FILE_NAME. */ @@ -45,15 +60,34 @@ process_execute (const char *file_name) return tid; } +static void * +push_to_stack (void **esp, void *data, size_t data_size) +{ + *esp -= data_size; + memcpy (*esp, data, data_size); + return *esp; +} + /* A thread function that loads a user process and starts it running. */ static void start_process (void *file_name_) { - char *file_name = file_name_; struct intr_frame if_; bool success; + /* Retrieve first argument of command, which is the file name + of the process. */ + char *saveptr; + char *arg = strtok_r (file_name_, " ", &saveptr); + + char file_name[15]; + strlcpy (file_name, arg, 15); + + /* TODO: Move naming of thread to process_execute, so start + tokenizing there. */ + strlcpy (thread_current ()->name, file_name, 15); + /* Initialize interrupt frame and load executable. */ memset (&if_, 0, sizeof if_); if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; @@ -61,8 +95,88 @@ start_process (void *file_name_) if_.eflags = FLAG_IF | FLAG_MBS; success = load (file_name, &if_.eip, &if_.esp); + /* Load command line argument *data* to user process stack. + This can't cause overflow due to enforcing that the size of + command line input must fit in a page. Also keep track + of pointers to the argument data within a linked list. */ + struct list arg_list; + list_init (&arg_list); + + int arg_count = 0; + while (arg != NULL) + { + push_to_stack (&if_.esp, arg, (strlen (arg) + 1) * sizeof (char)); + + struct arg_elem *arg_elem = malloc (sizeof (struct arg_elem)); + if (arg_elem == NULL) + { + printf("ERROR: Couldn't allocate argument pointer memory for %s!\n", + file_name); + palloc_free_page (file_name_); + if (success) process_exit (); + thread_exit (); + } + + arg_elem->arg = if_.esp; + list_push_front (&arg_list, &arg_elem->elem); + + arg_count++; + arg = strtok_r (NULL, " ", &saveptr); + } + + /* Calculate the remaining number of bytes that need to be written + to the user process stack in order to check for possible overflow. */ + size_t align_size = ((unsigned int) if_.esp % 4) * sizeof (uint8_t); + size_t argv_data_size = (arg_count + 1) * sizeof (char *); + size_t argv_size = sizeof (char **); + size_t argc_size = sizeof (int); + size_t return_addr_size = sizeof (void *); + size_t remaining_size = align_size + argv_data_size + argv_size + argc_size + + return_addr_size; + + /* If pushing the rest of the data required for the stack would cause + overflow, allocate an extra page. */ + if (PHYS_BASE - if_.esp + remaining_size > PGSIZE) + { + /* TODO: Allocate an extra page for the rest of the process stack. */ + } + + /* Align stack pointer to word size before pushing argv elements for + performance. */ + if_.esp -= align_size; + + /* Push a null pointer sentinel inside argv. */ + if_.esp -= sizeof (char *); + *(char *) if_.esp = 0; + + /* Push pointer to the process file name to the stack. */ + char **argv; + + /* Push pointers to process arguments from argument linked list */ + struct list_elem *e = list_begin (&arg_list); + struct list_elem *tail = list_tail (&arg_list); + while (e != tail) + { + struct arg_elem *arg_elem = list_entry (e, struct arg_elem, elem); + + argv = push_to_stack (&if_.esp, &arg_elem->arg, sizeof (arg_elem->arg)); + + e = list_next (e); + free (arg_elem); + } + + /* Push pointer to the start of argv array. */ + push_to_stack (&if_.esp, &argv, sizeof(argv)); + + /* Push the number of arguments to the stack. */ + push_to_stack (&if_.esp, &arg_count, sizeof (arg_count)); + + /* Push fake return address (null pointer). */ + if_.esp -= sizeof (char *); + *(char *) if_.esp = 0; + /* If load failed, quit. */ - palloc_free_page (file_name); + palloc_free_page (file_name_); if (!success) thread_exit (); @@ -88,7 +202,12 @@ start_process (void *file_name_) int process_wait (tid_t child_tid UNUSED) { - return -1; + /* As a temporary wait, waiting will just put the thread to sleep for one + second (TIMER_FREQ = 100 ticks ~ 1 second). */ + /* TODO: Implement process_wait () correctly. Remove the next line. */ + timer_sleep (TIMER_FREQ); + + return 0; /* TODO: Change this too */ } /* Free the current process's resources. */ @@ -98,6 +217,8 @@ process_exit (void) struct thread *cur = thread_current (); uint32_t *pd; + printf ("%s: exit(%d)\n", cur->name, cur->exit_status); + /* Destroy the current process's page directory and switch back to the kernel-only page directory. */ pd = cur->pagedir; diff --git a/src/userprog/syscall.c b/src/userprog/syscall.c index 370c89b..1dbe73b 100644 --- a/src/userprog/syscall.c +++ b/src/userprog/syscall.c @@ -1,11 +1,68 @@ #include "userprog/syscall.h" -#include -#include +#include "devices/shutdown.h" +#include "devices/input.h" +#include "threads/vaddr.h" #include "threads/interrupt.h" #include "threads/thread.h" +#include "userprog/process.h" +#include +#include static void syscall_handler (struct intr_frame *); +/* A syscall_function is a function that receives up to 3 arguments, the + arguments to the functions are either ints or pointers taking up to 32 bits + in size. */ +typedef uintptr_t (*syscall_function) (uintptr_t, uintptr_t, uintptr_t); + +/* System call function prototypes */ +static void syscall_halt (void); +static void syscall_exit (int status); +static pid_t syscall_exec (const char *cmd_line); +static int syscall_wait (pid_t pid); +static bool syscall_create (const char *file, unsigned initial_size); +static bool syscall_remove (const char *file); +static int syscall_open (const char *file); +static int syscall_filesize (int fd); +static int syscall_read (int fd, void *buffer, unsigned size); +static int syscall_write (int fd, const void *buffer, unsigned size); +static void syscall_seek (int fd, unsigned position); +static unsigned syscall_tell (int fd); +static void syscall_close (int fd); + +static void *validate_user_pointer (const void *ptr, size_t size); + +/* A struct defining a syscall_function pointer along with its arity. */ +typedef struct + { + syscall_function function; /* Function pointer. */ + int arity; /* Number of arguments of the function. */ + } syscall_arguments; + +/* A look-up table mapping numbers to system call functions with their number of + arguments. */ +static const syscall_arguments syscall_lookup[] = +{ + [SYS_HALT] = {(syscall_function) syscall_halt, 0}, + [SYS_EXIT] = {(syscall_function) syscall_exit, 1}, + [SYS_EXEC] = {(syscall_function) syscall_exec, 1}, + [SYS_WAIT] = {(syscall_function) syscall_wait, 1}, + [SYS_CREATE] = {(syscall_function) syscall_create, 2}, + [SYS_REMOVE] = {(syscall_function) syscall_remove, 1}, + [SYS_OPEN] = {(syscall_function) syscall_open, 1}, + [SYS_FILESIZE] = {(syscall_function) syscall_filesize, 1}, + [SYS_READ] = {(syscall_function) syscall_read, 3}, + [SYS_WRITE] = {(syscall_function) syscall_write, 3}, + [SYS_SEEK] = {(syscall_function) syscall_seek, 2}, + [SYS_TELL] = {(syscall_function) syscall_tell, 1}, + [SYS_CLOSE] = {(syscall_function) syscall_close, 1}, +}; + +/* The number of syscall functions (i.e, number of elements) within the + syscall_lookup table. */ +static const int LOOKUP_SIZE + = sizeof (syscall_lookup) / sizeof (syscall_arguments); + void syscall_init (void) { @@ -13,8 +70,165 @@ syscall_init (void) } static void -syscall_handler (struct intr_frame *f UNUSED) +syscall_handler (struct intr_frame *f) { - printf ("system call!\n"); + /* First, read the system call number from the stack. */ + validate_user_pointer (f->esp, 1); + unsigned syscall_number = *(int *) f->esp; + + /* Ensures the number corresponds to a system call that can be handled. */ + if (syscall_number >= LOOKUP_SIZE) + thread_exit (); + + syscall_arguments syscall = syscall_lookup[syscall_number]; + + /* Next, read and copy the arguments from the stack pointer. */ + validate_user_pointer (f->esp + sizeof (uintptr_t), + syscall.arity * sizeof (uintptr_t)); + uintptr_t args[3] = {0}; + for (int i=0; i < syscall.arity; i++) + args[i] = *(uintptr_t *) (f->esp + sizeof (uintptr_t) * (i + 1)); + + /* Call the function that handles this system call with the arguments. When + there is a return value it is stored in f->eax. */ + f->eax = syscall.function (args[0], args[1], args[2]); +} + +static void +syscall_halt (void) +{ + shutdown_power_off (); +} + +static void +syscall_exit (int status) +{ + /* Sets exit_status of the thread to status. thread_exit () will call + process_exit () if user programs are allowed. */ + thread_current ()->exit_status = status; thread_exit (); } + +static pid_t +syscall_exec (const char *cmd_line UNUSED) +{ + //TODO + return 0; +} + +static int +syscall_wait (pid_t pid) +{ + return process_wait (pid); +} + +static bool +syscall_create (const char *file UNUSED, unsigned initial_size UNUSED) +{ + //TODO + return 0; +} + +static bool +syscall_remove (const char *file UNUSED) +{ + //TODO + return 0; +} + +static int +syscall_open (const char *file UNUSED) +{ + //TODO + return 0; +} + +static int +syscall_filesize (int fd UNUSED) +{ + //TODO + return 0; +} + +static int +syscall_read (int fd, void *buffer, unsigned size) +{ + /* Only console (fd = 0) or other files, not including STDOUT, (fd > 1) are + allowed. */ + if (fd < 0 && fd != STDOUT_FILENO) + return -1; + + validate_user_pointer (buffer, size); + + if (fd == STDIN_FILENO) + { + /* Reading from the console. */ + char *write_buffer = buffer; + for (int i = 0; i < size; i++) + write_buffer[i] = input_getc (); + + return size; + } + else + { + /* Reading from a file. */ + return 0; // TODO: Implement Write to Files + } +} + +static int +syscall_write (int fd, const void *buffer, unsigned size) +{ + /* Only console (fd = 1) or other files, not including STDIN, (fd > 1) are + allowed. */ + if (fd <= 0) + return 0; + + validate_user_pointer (buffer, size); + + if (fd == STDOUT_FILENO) + { + /* Writing to the console. */ + putbuf (buffer, size); + return size; + } + else + { + /* Writing to a file. */ + return 0; // TODO: Implement Write to Files + } +} + +static void +syscall_seek (int fd UNUSED, unsigned position UNUSED) +{ + //TODO +} + +static unsigned +syscall_tell (int fd UNUSED) +{ + //TODO + return 0; +} + +static void +syscall_close (int fd UNUSED) +{ + //TODO +} + +/* Validates if a block of memory starting at PTR and of size SIZE bytes is + fully contained within user virtual memory. Kills the thread (by calling + thread_exit) if the memory is invalid. Otherwise, returns the PTR given. + If the size is 0, the function does no checks and returns PTR.*/ +static void * +validate_user_pointer (const void *ptr, size_t size) +{ + if (size > 0 && (ptr == NULL || + !is_user_vaddr (ptr) || + !is_user_vaddr (ptr + size - 1))) + thread_exit (); + + return ptr; +} diff --git a/src/userprog/syscall.h b/src/userprog/syscall.h index 9059096..702a6c7 100644 --- a/src/userprog/syscall.h +++ b/src/userprog/syscall.h @@ -1,6 +1,8 @@ #ifndef USERPROG_SYSCALL_H #define USERPROG_SYSCALL_H +typedef int pid_t; + void syscall_init (void); #endif /* userprog/syscall.h */