diff --git a/src/filesys/file.h b/src/filesys/file.h index 43088db..cfa320a 100644 --- a/src/filesys/file.h +++ b/src/filesys/file.h @@ -4,6 +4,9 @@ #include "filesys/off_t.h" #include +/* The maximum length of a file name in PintOS. */ +#define FNAME_MAX_LEN 14 + struct inode; /* Opening and closing files. */ diff --git a/src/lib/kernel/list.c b/src/lib/kernel/list.c index ac3d55d..f8f7fbb 100644 --- a/src/lib/kernel/list.c +++ b/src/lib/kernel/list.c @@ -170,6 +170,9 @@ list_insert (struct list_elem *before, struct list_elem *elem) { ASSERT (is_interior (before) || is_tail (before)); ASSERT (elem != NULL); + // Sanity checks to prevent (some) loop lists + ASSERT (before != elem); + ASSERT (before->prev != elem); elem->prev = before->prev; elem->next = before; diff --git a/src/lib/user/syscall.c b/src/lib/user/syscall.c index a9c5bc8..b928b41 100644 --- a/src/lib/user/syscall.c +++ b/src/lib/user/syscall.c @@ -166,7 +166,7 @@ mkdir (const char *dir) } bool -readdir (int fd, char name[READDIR_MAX_LEN + 1]) +readdir (int fd, char name[FNAME_MAX_LEN + 1]) { return syscall2 (SYS_READDIR, fd, name); } diff --git a/src/lib/user/syscall.h b/src/lib/user/syscall.h index 32265bb..305c7af 100644 --- a/src/lib/user/syscall.h +++ b/src/lib/user/syscall.h @@ -3,6 +3,7 @@ #include #include +#include "../../filesys/file.h" /* Process identifier. */ typedef int pid_t; @@ -12,9 +13,6 @@ typedef int pid_t; typedef int mapid_t; #define MAP_FAILED ((mapid_t) -1) -/* Maximum characters in a filename written by readdir(). */ -#define READDIR_MAX_LEN 14 - /* Typical return values from main() and arguments to exit(). */ #define EXIT_SUCCESS 0 /* Successful execution. */ #define EXIT_FAILURE 1 /* Unsuccessful execution. */ @@ -41,7 +39,7 @@ void munmap (mapid_t); /* Task 4 only. */ bool chdir (const char *dir); bool mkdir (const char *dir); -bool readdir (int fd, char name[READDIR_MAX_LEN + 1]); +bool readdir (int fd, char name[FNAME_MAX_LEN + 1]); bool isdir (int fd); int inumber (int fd); diff --git a/src/threads/synch.c b/src/threads/synch.c index d706d19..c8bbd6d 100644 --- a/src/threads/synch.c +++ b/src/threads/synch.c @@ -341,6 +341,7 @@ lock_release (struct lock *lock) released, transfer the remaining orphaned donors to its donor list. */ if (max_donor != NULL) { + list_remove (&max_donor->donor_elem); while (!list_empty (&orphan_list)) list_push_back (&max_donor->donors_list, list_pop_front (&orphan_list)); } 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 6c8ef2d..f5b54b7 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 @@ -22,6 +23,10 @@ #include "threads/synch.h" #include "devices/timer.h" +/* Defines the native number of bytes processed by the processor + (for the purposes of alignment). */ +#define WORD_SIZE 4 + /* Keeps track of the position of pointers to user program arguments within a linked list. */ struct arg_elem @@ -30,155 +35,111 @@ struct arg_elem struct list_elem elem; }; +/* Holds the data required to be passed from a kernel thread to a thread + that executes process_start for the purpose of starting a user process. */ +struct process_start_data + { + char *cmd; /* Pointer to a copy of the command used to execute the process. + Allocated a page that must be freed by process_start. */ + char *cmd_saveptr; /* Value pointed to by 'saveptr' argument used by + successive calls to strtok_r to split 'cmd' into + tokens while maintaining state. */ + char file_name[FNAME_MAX_LEN + 1]; /* Name of the file of the process to + be started. */ + }; + static thread_func start_process NO_RETURN; static bool load (const char *cmdline, void (**eip) (void), void **esp); -/* Starts a new thread running a user program loaded from - FILENAME. The new thread may be scheduled (and may even exit) +/* Starts a new thread running a user program executed via + CMD. The new thread may be scheduled (and may even exit) before process_execute() returns. Returns the new process's thread id, or TID_ERROR if the thread cannot be created. */ tid_t -process_execute (const char *file_name) +process_execute (const char *cmd) { - char *fn_copy; + char *cmd_copy; tid_t tid; + + struct process_start_data *data = malloc (sizeof (struct process_start_data)); + if (data == NULL) + { + return TID_ERROR; + } - /* Make a copy of FILE_NAME. + /* Make a copy of command. Otherwise there's a race between the caller and load(). */ - fn_copy = palloc_get_page (0); - if (fn_copy == NULL) + cmd_copy = palloc_get_page (0); + if (cmd_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); + strlcpy (cmd_copy, cmd, PGSIZE); - /* Create a new thread to execute FILE_NAME. */ - tid = thread_create (file_name, PRI_DEFAULT, start_process, fn_copy); + /* Retrieve first argument of command, which is the file name + of the process. */ + char *file_name = strtok_r (cmd_copy, " ", &data->cmd_saveptr); + + /* Create a new thread to execute the command, by initializing + it running the function 'start_process' with the appropriate + arguments. For details of arguments, see 'start_process'. */ + data->cmd = cmd_copy; + strlcpy (data->file_name, file_name, FNAME_MAX_LEN + 1); + + tid = thread_create (file_name, PRI_DEFAULT, start_process, data); if (tid == TID_ERROR) - palloc_free_page (fn_copy); + palloc_free_page (cmd_copy); 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; -} +static bool install_page (void *upage, void *kpage, bool writable); +static bool process_init_stack (char *cmd_saveptr, void **esp, char *file_name); +static void *push_to_stack (void **esp, void *data, size_t data_size); +#define push_var_to_stack(esp, var) (push_to_stack (esp, &var, sizeof (var))) -/* A thread function that loads a user process and starts it - running. */ +/* Make the current thread execute 'cmd', passing in a copy of the + command string used for processing, the saveptr used by strtok_r + (in order to further tokenize the same command and retrieve its + arguments), as well as the name of the file being executed. This + involves loading the specified file and starting it running. */ static void -start_process (void *file_name_) +start_process (void *proc_start_data) { 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); + struct process_start_data *data = proc_start_data; /* Initialize interrupt frame and load executable. */ memset (&if_, 0, sizeof if_); if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; if_.cs = SEL_UCSEG; 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; + success = load (data->file_name, &if_.eip, &if_.esp); /* If load failed, quit. */ - palloc_free_page (file_name_); if (!success) - thread_exit (); + { + palloc_free_page (data->cmd); + goto fail; + } + + /* Initialize user process stack and free page used to store the + command that executed the process. */ + success = process_init_stack (data->cmd_saveptr, &if_.esp, data->file_name); + palloc_free_page (data->cmd); + + /* If stack initialization failed, free resources and quit. */ + if (!success) + { + process_exit (); + 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 @@ -188,6 +149,109 @@ start_process (void *file_name_) and jump to it. */ asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory"); NOT_REACHED (); + +/* If starting the process failed, free its common resources and exit. */ + fail: + free (data); + thread_exit (); +} + +/* Helper function that initializes the stack of a newly created + user process. Returns true if successful, false otherwise. */ +static bool +process_init_stack (char *cmd_saveptr, void **esp, char *file_name) + { + /* 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); + + char *arg = file_name; + int arg_count = 0; + while (arg != NULL) + { + push_to_stack (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", + thread_current ()->name); + return false; + } + + arg_elem->arg = *esp; + list_push_front (&arg_list, &arg_elem->elem); + + arg_count++; + arg = strtok_r (NULL, " ", &cmd_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) *esp % WORD_SIZE) * 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 that is contiguous within the + virtual address space (below the current address range). */ + if (PHYS_BASE - *esp + remaining_size > PGSIZE) + { + uint8_t *kpage = palloc_get_page (PAL_USER | PAL_ZERO); + if (!install_page (((uint8_t *) PHYS_BASE) - PGSIZE * 2, kpage, true)) + return false; + } + + /* Align stack pointer to word size before pushing argv elements for + performance. */ + *esp -= align_size; + + /* Push a null pointer sentinel inside argv. */ + char *null_sentinel = NULL; + push_var_to_stack (esp, null_sentinel); + + /* 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); + + push_var_to_stack(esp, arg_elem->arg); + + e = list_next (e); + free (arg_elem); + } + + /* Push pointer to the start of argv array. */ + char **argv = *esp; + push_var_to_stack(esp, argv); + + /* Push the number of arguments to the stack. */ + push_var_to_stack(esp, arg_count); + + /* Push fake return address (null pointer). */ + push_var_to_stack (esp, null_sentinel); + + return true; + } + +/* Helper function that pushes the first 'data_size' bytes stored + in the address '*data' into the stack given a pointer to the + stack pointer '**esp'. */ +static void * +push_to_stack (void **esp, void *data, size_t data_size) +{ + *esp -= data_size; + memcpy (*esp, data, data_size); + return *esp; } /* Waits for thread TID to die and returns its exit status. @@ -202,12 +266,39 @@ start_process (void *file_name_) 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. */ @@ -218,6 +309,49 @@ 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 + { + sema_up (&cur->result->sema); + lock_release (&cur->result->lock); + } + } + + /* Free child process results or signal parent's death. */ + 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 + { + sema_up (&result->sema); + lock_release (&result->lock); + } + } /* Destroy the current process's page directory and switch back to the kernel-only page directory. */ @@ -439,8 +573,6 @@ load (const char *file_name, void (**eip) (void), void **esp) /* load() helpers. */ -static bool install_page (void *upage, void *kpage, bool writable); - /* Checks whether PHDR describes a valid, loadable segment in FILE and returns true if so, false otherwise. */ static bool 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 */