diff --git a/src/threads/thread.c b/src/threads/thread.c index 457f7b9..fd239ee 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -1,5 +1,6 @@ #include "threads/thread.h" #include +#include #include #include #include @@ -9,12 +10,14 @@ #include "threads/flags.h" #include "threads/interrupt.h" #include "threads/intr-stubs.h" +#include "threads/malloc.h" #include "threads/palloc.h" #include "threads/switch.h" #include "threads/synch.h" #include "threads/vaddr.h" #ifdef USERPROG #include "userprog/process.h" +#include "userprog/syscall.h" #endif /* Random value for struct thread's `magic' member. @@ -68,6 +71,7 @@ static void kernel_thread (thread_func *, void *aux); static void idle (void *aux UNUSED); static struct thread *running_thread (void); static struct thread *next_thread_to_run (void); +static void init_process_result (struct thread *t); static void init_thread (struct thread *, const char *name, int nice, int priority, fp32_t recent_cpu); static bool is_thread (struct thread *) UNUSED; @@ -110,6 +114,7 @@ thread_init (void) initial_thread_recent_cpu); initial_thread->status = THREAD_RUNNING; initial_thread->tid = allocate_tid (); + initial_thread->result = NULL; /* Main thread cannot be waited for. */ } /* Starts preemptive thread scheduling by enabling interrupts. @@ -236,6 +241,11 @@ thread_create (const char *name, int priority, struct thread *parent_thread = thread_current (); init_thread (t, name, parent_thread->nice, priority, parent_thread->recent_cpu); tid = t->tid = allocate_tid (); + init_process_result (t); + + #ifdef USERPROG + hash_init (&t->open_files, fd_hash, fd_less, NULL); + #endif /* Prepare thread for first run by initializing its stack. Do this atomically so intermediate values for the 'stack' @@ -259,6 +269,10 @@ thread_create (const char *name, int priority, intr_set_level (old_level); + /* No need to synchronise child_results since it is only ever accessed by one + thread. By the nature of increasing TIDs, this list is ordered. */ + list_push_back (&parent_thread->child_results, &t->result->elem); + /* Add to run queue. */ thread_unblock (t); @@ -632,6 +646,18 @@ is_thread (struct thread *t) return t != NULL && t->magic == THREAD_MAGIC; } +/* Allocate and initialise a process result for given thread. */ +static void +init_process_result (struct thread *t) +{ + struct process_result *result = malloc (sizeof (struct process_result)); + result->tid = t->tid; + result->exit_status = t->exit_status; + lock_init (&result->lock); + sema_init (&result->sema, 0); + t->result = result; +} + /* Does basic initialization of T as a blocked thread named NAME. */ static void @@ -660,6 +686,7 @@ init_thread (struct thread *t, const char *name, int nice, int priority, t->priority = t->base_priority; t->exit_status = -1; + list_init (&t->child_results); old_level = intr_disable (); list_push_back (&all_list, &t->allelem); diff --git a/src/threads/thread.h b/src/threads/thread.h index 1c05030..33a5485 100644 --- a/src/threads/thread.h +++ b/src/threads/thread.h @@ -2,8 +2,11 @@ #define THREADS_THREAD_H #include +#include #include #include +#include +#include "threads/synch.h" #include "threads/fixed-point.h" /* States in a thread's life cycle. */ @@ -29,6 +32,18 @@ typedef int tid_t; #define NICE_DEFAULT 0 /* Default niceness. */ #define NICE_MAX 20 /* Highest niceness. */ +/* A process result, synchronised between parent and child. */ +struct process_result +{ + tid_t tid; /* The tid of the child process. */ + int exit_status; /* The exit status of the child process. Initially set + to -1, then to exit_status when child dies. */ + struct lock lock; /* Lock the exit_status and sema. */ + struct semaphore sema; /* Semaphore to signal the parent that the exit_status + has been set. */ + struct list_elem elem; /* List element for the parent's children list. */ +}; + /* A kernel thread or user process. Each thread structure is stored in its own 4 kB page. The @@ -108,6 +123,12 @@ struct thread int nice; /* Nice value for this thread */ fp32_t recent_cpu; /* Amount of time this process received */ + /* Process wait properties. */ + struct process_result *result; /* Result of the process. */ + struct list child_results; /* List of children's of this thread + process results. */ + struct file *exec_file; /* Thread's currently running file */ + /* Shared between thread.c and synch.c. */ struct list_elem elem; /* List element. */ @@ -116,6 +137,7 @@ struct thread #ifdef USERPROG /* Owned by userprog/process.c. */ uint32_t *pagedir; /* Page directory. */ + struct hash open_files; /* Hash Table of FD -> Struct File */ #endif /* Owned by thread.c. */ diff --git a/src/userprog/process.c b/src/userprog/process.c index 7e211c4..7d61607 100644 --- a/src/userprog/process.c +++ b/src/userprog/process.c @@ -1,6 +1,7 @@ #include "userprog/process.h" #include #include +#include #include #include #include @@ -136,6 +137,10 @@ start_process (void *proc_start_data) goto fail; } + struct file *exec_file = filesys_open (file_name); + thread_current ()->exec_file = exec_file; + file_deny_write (exec_file); + /* Start the user process by simulating a return from an interrupt, implemented by intr_exit (in threads/intr-stubs.S). Because intr_exit takes all of its @@ -261,12 +266,39 @@ push_to_stack (void **esp, void *data, size_t data_size) int process_wait (tid_t child_tid UNUSED) { - /* 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 */ + struct process_result *child_result = NULL; + struct list_elem *e; + struct thread *cur = thread_current (); + for (e = list_begin (&cur->child_results); + e != list_end (&cur->child_results); e = list_next (e)) + { + struct process_result *result + = list_entry (e, struct process_result, elem); + if (result->tid == child_tid) + { + child_result = result; + break; + } + /* List is ordered, allowing us to break early. */ + else if (result->tid > child_tid) + break; + } + if (child_result == NULL) + return -1; + /* Wait for child to die. */ + sema_down (&child_result->sema); + /* We need lock release in process_exit, so we need to acquire (and possibly + wait) for it here to ensure we don't free the lock memory before it is + released in process_exit. */ + lock_acquire (&child_result->lock); + /* To prevent waiting for child twice, remove it from the list. + No need to use lock since this is the only thread with access to + the struct process_result now. */ + list_remove (&child_result->elem); + int exit_status = child_result->exit_status; + lock_release (&child_result->lock); + free (child_result); + return exit_status; } /* Free the current process's resources. */ @@ -277,6 +309,48 @@ process_exit (void) uint32_t *pd; printf ("%s: exit(%d)\n", cur->name, cur->exit_status); + file_close (cur->exec_file); + + /* Update process result. */ + if (cur->result != NULL) + { + lock_acquire (&cur->result->lock); + cur->result->exit_status = cur->exit_status; + /* Parent has died, child has to free the struct process_result * */ + if (sema_try_down (&cur->result->sema)) + { + lock_release (&cur->result->lock); + free (cur->result); + } + /* Parent is still alive and will be the one to free the + struct process_result *, and may be waiting so call sema_up */ + else + { + lock_release (&cur->result->lock); + sema_up (&cur->result->sema); + } + } + + struct list_elem *e; + for (e = list_begin (&cur->child_results); + e != list_end (&cur->child_results); e = list_next (e)) + { + struct process_result *result + = list_entry (e, struct process_result, elem); + lock_acquire (&result->lock); + /* Child has died (and was not waited for). Free the result. */ + if (sema_try_down (&result->sema)) + { + lock_release (&result->lock); + free (result); + } + /* Child is still alive, signal via sema that parent has died. */ + else + { + lock_release (&result->lock); + sema_up (&result->sema); + } + } /* Destroy the current process's page directory and switch back to the kernel-only page directory. */ diff --git a/src/userprog/syscall.c b/src/userprog/syscall.c index 1dbe73b..ccb02f3 100644 --- a/src/userprog/syscall.c +++ b/src/userprog/syscall.c @@ -1,13 +1,28 @@ #include "userprog/syscall.h" #include "devices/shutdown.h" #include "devices/input.h" +#include "filesys/file.h" +#include "filesys/filesys.h" #include "threads/vaddr.h" #include "threads/interrupt.h" +#include "threads/malloc.h" #include "threads/thread.h" +#include "threads/synch.h" #include "userprog/process.h" +#include "userprog/pagedir.h" #include #include +static struct lock filesys_lock; +static unsigned fd_counter = MIN_USER_FD; + +struct open_file + { + int fd; /* File Descriptor / Identifier */ + struct file *file; /* Pointer to the associated file */ + struct hash_elem elem; /* elem for a hash table */ + }; + static void syscall_handler (struct intr_frame *); /* A syscall_function is a function that receives up to 3 arguments, the @@ -30,6 +45,7 @@ static void syscall_seek (int fd, unsigned position); static unsigned syscall_tell (int fd); static void syscall_close (int fd); +static struct open_file *fd_get_file (int fd); static void *validate_user_pointer (const void *ptr, size_t size); /* A struct defining a syscall_function pointer along with its arity. */ @@ -63,12 +79,19 @@ static const syscall_arguments syscall_lookup[] = static const int LOOKUP_SIZE = sizeof (syscall_lookup) / sizeof (syscall_arguments); + +/* Initialises the syscall handling system, as well as a global lock to + synchronise all file access between processes. */ void syscall_init (void) { intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); + lock_init (&filesys_lock); } +/* Function that takes a interrupt frame containing a syscall and its args. + Validates the arguments and pointers before calling the relevant + high-level system call function, storing its output (if any) in f->eax */ static void syscall_handler (struct intr_frame *f) { @@ -94,6 +117,8 @@ syscall_handler (struct intr_frame *f) f->eax = syscall.function (args[0], args[1], args[2]); } +/* Called upon a "halt" syscall, resulting in a complete shutdown of the + process, via shutdown_power_off (); */ static void syscall_halt (void) { @@ -109,53 +134,120 @@ syscall_exit (int status) thread_exit (); } +/* Executes a given command with the relevant args, by calling process_execute. + Acquires the filesystem lock as process_execute accesses the file system. + Returns PID for the process that is running the CMD_LINE + */ static pid_t -syscall_exec (const char *cmd_line UNUSED) +syscall_exec (const char *cmd_line) { - //TODO - return 0; + validate_user_pointer (cmd_line, 1); + + lock_acquire (&filesys_lock); + pid_t pid = process_execute(cmd_line); + lock_release (&filesys_lock); + + return pid; } +/* Handles the syscall of wait. Effectively a wrapper for process_wait as the + necessary validation and such all happens in process_wait anyway. */ static int syscall_wait (pid_t pid) { return process_wait (pid); } +/* Handles the syscall for file creation. First validates the user file + pointer. Acquires the file system lock to prevent synchronisation issues, + and then uses FILESYS_CREATE to create the file, returning the same status */ static bool syscall_create (const char *file UNUSED, unsigned initial_size UNUSED) { - //TODO - return 0; + validate_user_pointer (file, 1); + + lock_acquire (&filesys_lock); + bool status = filesys_create (file, initial_size); + lock_release (&filesys_lock); + + return status; } +/* Handles the syscall for file removal. First validates the user file pointer. + Acquires the file system lock to prevent synchronisation issues, and then + uses FILESYS_REMOVE to remove the file, returning the same success status */ static bool -syscall_remove (const char *file UNUSED) +syscall_remove (const char *file) { - //TODO - return 0; + validate_user_pointer (file, 1); + + lock_acquire (&filesys_lock); + bool status = filesys_remove (file); + lock_release (&filesys_lock); + + return status; } +/* Handles the syscall for opening a file connection. First, validates the file + pointer. Then it acquires a lock for the file system, in order to open the + connection without synchronisation issues. It then maps a new fd to this file + in the hash table before returning the fd. */ static int -syscall_open (const char *file UNUSED) +syscall_open (const char *file) { - //TODO - return 0; + validate_user_pointer (file, 1); + + lock_acquire (&filesys_lock); + struct file *ptr = filesys_open (file); + lock_release (&filesys_lock); + if (ptr == NULL) + return -1; + + /* Allocate space for a struct representing a mapping from an FD to a struct + file. */ + struct open_file *file_info + = (struct open_file*) malloc (sizeof (struct open_file)); + if (file_info == NULL) + return -1; + + /* Populate the above struct, with a unique FD and the current open file */ + file_info->fd = fd_counter++; + file_info->file = ptr; + + /* Add the new FD->file mapping to the hashtable for the current thread */ + hash_insert (&thread_current ()->open_files, &file_info->elem); + + /* Return the new FD */ + return file_info->fd; } +/* Handles the syscall for getting a file's size. Converts a provided FD into + the asssociated file struct. Acquire the lock for the filesystem and use + FILE_LENGTH to calculate the length for return. */ static int -syscall_filesize (int fd UNUSED) +syscall_filesize (int fd) { - //TODO - return 0; + struct open_file *file_info = fd_get_file (fd); + if (file_info == NULL) + return -1; + + lock_acquire (&filesys_lock); + int bytes = file_length (file_info->file); + lock_release (&filesys_lock); + + return bytes; } +/* Handles the syscall for reading SIZE bytes from a file referenced by FD. + If the FD references the console, use input_getc (), otherwise convert the + FD to its associated file struct, acquire the filesystem lock, read up to + SIZE bytes and then return the number of bytes read.*/ 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) + if (fd < 0 || fd == STDOUT_FILENO) return -1; validate_user_pointer (buffer, size); @@ -172,10 +264,21 @@ syscall_read (int fd, void *buffer, unsigned size) else { /* Reading from a file. */ - return 0; // TODO: Implement Write to Files + struct open_file *file_info = fd_get_file (fd); + if (file_info == NULL) + return -1; + + lock_acquire (&filesys_lock); + int bytes_written = file_read (file_info->file, buffer, size); + lock_release (&filesys_lock); + return bytes_written; } } +/* Handles the syscall for writing SIZE bytes to a file referenced by FD. + If the FD references the console, use put_buf (), otherwise convert the + FD to its associated file struct, acquire the filesystem lock, write up to + SIZE bytes and then return the number of bytes written.*/ static int syscall_write (int fd, const void *buffer, unsigned size) { @@ -195,27 +298,106 @@ syscall_write (int fd, const void *buffer, unsigned size) else { /* Writing to a file. */ - return 0; // TODO: Implement Write to Files + struct open_file *file_info = fd_get_file (fd); + if (file_info == NULL) + return 0; + + lock_acquire (&filesys_lock); + int bytes = file_write (file_info->file, buffer, size); + lock_release (&filesys_lock); + + return bytes; } } +/* Handles the syscall for seeking to POSITION bytes in a file referenced by + FD. Converts the FD to its associated file struct, acquires the filesystem + lock and then uses file_seek to adjust the cursor to a specific position in + the file.*/ static void -syscall_seek (int fd UNUSED, unsigned position UNUSED) +syscall_seek (int fd, unsigned position) { - //TODO + struct open_file *file_info = fd_get_file (fd); + if (file_info != NULL) + { + lock_acquire (&filesys_lock); + file_seek (file_info->file, position); + lock_release (&filesys_lock); + } } +/* Handles the syscall for returning the next byte in a file referenced by + FD. Converts the FD to its associated file struct, acquires the filesystem + lock and then uses file_tell to read the next byte.*/ static unsigned -syscall_tell (int fd UNUSED) +syscall_tell (int fd) { - //TODO - return 0; + struct open_file *file_info = fd_get_file (fd); + if (file_info == NULL) + return 0; + + lock_acquire (&filesys_lock); + unsigned pos = file_tell (file_info->file); + lock_release (&filesys_lock); + + return pos; } +/* Handles the syscall for closing a connection to a file. Converts the FD to + its associated file struct. If it exists, it removes it from the hash table, + acquires the filesystem lock, and uses file_close to close the connection.*/ static void -syscall_close (int fd UNUSED) +syscall_close (int fd) { - //TODO + struct open_file *file_info = fd_get_file (fd); + if (file_info != NULL) + { + hash_delete (&thread_current ()->open_files, &file_info->elem); + + lock_acquire (&filesys_lock); + file_close (file_info->file); + lock_release (&filesys_lock); + free (file_info); + } +} + +/* Hashing function needed for the open_file table. Returns a hash for an entry, + based on its FD. */ +unsigned +fd_hash (const struct hash_elem *element, void *aux UNUSED) +{ + return hash_int (hash_entry (element, struct open_file, elem)->fd); +} + +/* Comparator function for the open_file table. Compares two entries based on + the FDs. */ +bool +fd_less (const struct hash_elem *a_, const struct hash_elem *b_, + void *aux UNUSED) +{ + struct open_file *a = hash_entry (a_, struct open_file, elem); + struct open_file *b = hash_entry (b_, struct open_file, elem); + + return a->fd < b->fd; +} + +/* Gets a file from its descriptor (FD number). If there is no file with the fd + FD it returns NULL. */ +static struct open_file * +fd_get_file (int fd) +{ + /* We have to set up a fake open_file in order to be able to search the hash + table. See hash.h. */ + struct open_file fake_file_info; + fake_file_info.fd = fd; + + struct hash_elem *e + = hash_find (&thread_current ()->open_files, &fake_file_info.elem); + + if (e == NULL) + return NULL; + + return hash_entry (e, struct open_file, elem); } /* Validates if a block of memory starting at PTR and of size SIZE bytes is @@ -227,8 +409,9 @@ validate_user_pointer (const void *ptr, size_t size) { if (size > 0 && (ptr == NULL || !is_user_vaddr (ptr) || - !is_user_vaddr (ptr + size - 1))) + !is_user_vaddr (ptr + size - 1) || + pagedir_get_page (thread_current()->pagedir, ptr) == NULL)) thread_exit (); - return ptr; + return (void *) ptr; } diff --git a/src/userprog/syscall.h b/src/userprog/syscall.h index 702a6c7..0f9288b 100644 --- a/src/userprog/syscall.h +++ b/src/userprog/syscall.h @@ -1,8 +1,15 @@ #ifndef USERPROG_SYSCALL_H #define USERPROG_SYSCALL_H +#include + +#define MIN_USER_FD 2 + typedef int pid_t; void syscall_init (void); +unsigned fd_hash (const struct hash_elem *element, void *aux); +bool fd_less (const struct hash_elem *a, const struct hash_elem *b, void *aux); + #endif /* userprog/syscall.h */