Files
pintos_22/src/userprog/syscall.c

599 lines
20 KiB
C

#include "userprog/syscall.h"
#include "userprog/exception.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 "vm/page.h"
#include "vm/mmap.h"
#include <stdio.h>
#include <syscall-nr.h>
#define MAX_SYSCALL_ARGS 3
#define EXIT_FAILURE -1
#define MMAP_FAILURE -1
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
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 mapid_t syscall_mmap (int fd, void *addr);
static void syscall_munmap (mapid_t mapping);
static struct open_file *fd_get_file (int fd);
static void validate_user_pointer (const void *start, size_t size, bool write);
static void validate_user_string (const char *str);
/* A struct defining a syscall_function pointer along with its arity. */
struct syscall_arguments
{
syscall_function function; /* Function pointer. */
int arity; /* Number of arguments of the function. */
};
/* A look-up table mapping numbers to system call functions with their number of
arguments. */
static const struct 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},
[SYS_MMAP] = {(syscall_function) syscall_mmap, 2},
[SYS_MUNMAP] = {(syscall_function) syscall_munmap, 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 (struct 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 an 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)
{
/* First, read the system call number from the stack. */
validate_user_pointer (f->esp, sizeof (uintptr_t), false);
uintptr_t syscall_number = *(int *) f->esp;
/* Ensures the number corresponds to a system call that can be handled. */
if (syscall_number >= LOOKUP_SIZE)
syscall_exit (EXIT_FAILURE);
struct 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), false);
uintptr_t args[MAX_SYSCALL_ARGS] = {0};
for (int i = 0; i < syscall.arity && i < MAX_SYSCALL_ARGS; 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]);
}
/* Called upon a "halt" syscall, resulting in a complete shutdown of the
process, via shutdown_power_off (); */
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 ()->result->exit_status = status;
thread_exit ();
}
/* Executes a given command with the relevant args, by calling process_execute.
Returns PID for the process that is running the CMD_LINE. */
static pid_t
syscall_exec (const char *cmd_line)
{
/* Validate the user string before executing the process. */
validate_user_string (cmd_line);
return process_execute (cmd_line); /* Returns the PID of the new process */
}
/* 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); /* Returns the exit status of the waited process */
}
/* 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, unsigned initial_size)
{
/* Validate the user string before creating the file. */
validate_user_string (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
bool status = filesys_create (file, initial_size);
lock_release (&filesys_lock);
/* Return the status of the file creation. */
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)
{
/* Validate the user string before removing the file. */
validate_user_string (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
bool status = filesys_remove (file);
lock_release (&filesys_lock);
/* Return the status of the file removal. */
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)
{
/* Validate the user string before opening the file. */
validate_user_string (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
struct file *ptr = filesys_open (file);
lock_release (&filesys_lock);
/* If the file could not be opened, return failure. */
if (ptr == NULL)
return EXIT_FAILURE;
/* 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)
{
/* If we could not allocate memory for the file_info struct, close the
file and return failure. */
file_close (ptr);
return EXIT_FAILURE;
}
/* Populate the above struct, with a unique FD and the current open file */
file_info->fd = thread_current ()->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)
{
/* Try to get the file from the FD. If it does not exist, return failure. */
struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL)
return EXIT_FAILURE;
/* Acquire the file system lock to prevent any race conditions. */
lock_acquire (&filesys_lock);
int bytes = file_length (file_info->file);
lock_release (&filesys_lock);
/* Return the number of bytes in the file. */
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 < STDIN_FILENO || fd == STDOUT_FILENO)
return EXIT_FAILURE;
/* Validate the user buffer for the provided size before reading. */
validate_user_pointer (buffer, size, true);
if (fd == STDIN_FILENO)
{
/* Reading from the console. */
char *write_buffer = buffer;
for (unsigned i = 0; i < size; i++)
write_buffer[i] = input_getc ();
/* In case of console, read is always (eventually) successful. So return
the size for the number of bytes read. */
return size;
}
else
{
/* Reading from a file. */
/* Find the file from the FD. If it does not exist, return failure. */
struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL)
return EXIT_FAILURE;
/* Acquire the file system lock to prevent race-conditions. */
lock_acquire (&filesys_lock);
int bytes_written = file_read (file_info->file, buffer, size);
lock_release (&filesys_lock);
/* Return the number of bytes read. */
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)
{
/* Only console (fd = 1) or other files, not including STDIN, (fd > 1) are
allowed. */
if (fd <= 0)
return 0;
/* Validate the user buffer for the provided size before writing. */
validate_user_pointer (buffer, size, false);
if (fd == STDOUT_FILENO)
{
/* Writing to the console. */
putbuf (buffer, size);
/* In case of console, write is always successful. So return the size for
the number of bytes written. */
return size;
}
else
{
/* Writing to a file. */
/* Find the file from the FD. If it does not exist, return failure. */
struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL)
return 0;
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
int bytes = file_write (file_info->file, buffer, size);
lock_release (&filesys_lock);
/* Return the number of bytes written. */
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, unsigned position)
{
/* Find the file from the FD. If it does not exist, do nothing. */
struct open_file *file_info = fd_get_file (fd);
if (file_info != NULL)
file_seek (file_info->file, position);
}
/* 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)
{
/* Find the file from the FD. If it does not exist, return 0. */
struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL)
return 0;
unsigned pos = file_tell (file_info->file);
/* Return the current position in the file. */
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)
{
/* Find the file from the FD. If it does not exist, do nothing. */
struct open_file *file_info = fd_get_file (fd);
if (file_info != NULL)
{
/* File exists */
/* First, remove the file from the hash table of open files. */
hash_delete (&thread_current ()->open_files, &file_info->elem);
/* Then, close the file, acquiring the file system lock to prevent race
conditions. */
lock_acquire (&filesys_lock);
file_close (file_info->file);
lock_release (&filesys_lock);
/* Free the memory allocated for the file_info struct. */
free (file_info);
}
}
/* Handles the syscall for memory mapping a file. */
static mapid_t
syscall_mmap (int fd, void *addr)
{
/* Ensure the FD is for a file in the filesystem (not STDIN or STDOUT). */
if (fd == STDOUT_FILENO || fd == STDIN_FILENO)
return MMAP_FAILURE;
/* Validate that there is a file associated with the given FD. */
struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL)
return MMAP_FAILURE;
/* Ensure that the address is page-aligned and it's neither NULL nor zero. */
if (addr == 0 || addr == NULL || pg_ofs (addr) != 0)
return MMAP_FAILURE;
/* Reopen the file to obtain a separate and independent reference to the file
for the mapping. */
struct file *file = file_reopen (file_info->file);
if (file == NULL)
return MMAP_FAILURE;
/* Get the size of the file. Mmap fails if the file is empty. */
off_t file_size = file_length (file);
if (file_size == 0)
return MMAP_FAILURE;
/* Check and ensure that there is enough space in the user virtual memory to
hold the entire file. */
for (off_t ofs = 0; ofs < file_size; ofs += PGSIZE)
{
if (page_get (addr + ofs) != NULL)
return MMAP_FAILURE;
}
/* Map the file data into the user virtual memory starting from addr. */
for (off_t ofs = 0; ofs < file_size; ofs += PGSIZE)
{
off_t read_bytes = file_size - ofs < PGSIZE ? file_size - ofs : PGSIZE;
off_t zero_bytes = PGSIZE - read_bytes;
if (page_insert (file, ofs, addr + ofs, read_bytes, zero_bytes, true,
PAGE_FILE) == NULL)
return MMAP_FAILURE;
}
/* Create a new mapping for the file. */
struct mmap_entry *mmap = mmap_insert (file, addr);
if (mmap == NULL)
return MMAP_FAILURE;
return mmap->mapping;
}
/* Handles the syscall for unmapping a memory mapped file.
Pre: mapping is a valid mapping identifier returned by mmap syscall. */
static void
syscall_munmap (mapid_t mapping)
{
/* Get the mmap entry from the mapping identifier. */
struct mmap_entry *mmap = mmap_get (mapping);
/* Delete the mmap entry from the hash table. */
hash_delete (&thread_current ()->mmap_files, &mmap->elem);
/* Unmap the mmap entry: free the pages and write back to the file if
necessary. NOTE. freeing and cleaning up is also handled by mmap_unmap. */
mmap_unmap (mmap);
}
/* 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)
{
/* We use the FD as the hash value. This is because the FD is incremented
sequentially and is therefore unique for each file. It positively affects
the performance of the hash table: 1. It is unique so no need to call
expensive hash functions. 2. It being sequential means that the hash table
is more likely to be weight balanced. */
return 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;
}
/* Function to clean up an open file entry. Closes the file and frees the
associated memory. */
void
fd_cleanup (struct hash_elem *e, void *aux UNUSED)
{
struct open_file *file_info = hash_entry (e, struct open_file, elem);
lock_acquire (&filesys_lock);
file_close (file_info->file);
lock_release (&filesys_lock);
free (file_info);
}
/* 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 START and of size SIZE bytes is
fully contained within user virtual memory. Kills the thread (by exiting with
failure) if the memory is invalid. Otherwise, returns (nothing) normally.
If the size is 0, the function does no checks and returns the given ptr. */
static void
validate_user_pointer (const void *start, size_t size, bool write)
{
/* If the size is 0, we do not need to check anything. */
if (size == 0)
return;
const void *end = start + size - 1;
/* Check if the start and end pointers are valid user virtual addresses. */
if (start == NULL || !is_user_vaddr (start) || !is_user_vaddr (end))
syscall_exit (EXIT_FAILURE);
/* We no longer check if the memory is mapped to physical memory. This is
because the data may not necessarily be there at the time of the syscall,
but it may be lazily loaded later. In such case, we try to preload the
page. If that fails, we exit the thread. */
for (void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE)
if (pagedir_get_page (thread_current ()->pagedir, ptr) == NULL &&
!try_fetch_page (ptr, write))
syscall_exit (EXIT_FAILURE);
}
/* Validates if a string is fully contained within user virtual memory. Kills
the thread (by exiting with failure) if the memory is invalid. Otherwise,
returns (nothing) normally. */
static void
validate_user_string (const char *str)
{
/* Check if the string pointer is a valid user virtual address. */
if (str == NULL || !is_user_vaddr (str))
syscall_exit (EXIT_FAILURE);
/* Calculate the offset of the string within the (first) page. */
size_t offset = (uintptr_t) str % PGSIZE;
/* We move page by page, checking if the page is mapped to physical memory. */
for (;;)
{
void *page = pg_round_down (str);
/* If we reach addresses that are not mapped to physical memory before the
end of the string, the thread is terminated. */
if (!is_user_vaddr(page) ||
(pagedir_get_page (thread_current ()->pagedir, page) == NULL &&
!try_fetch_page (page, false)))
syscall_exit (EXIT_FAILURE);
while (offset < PGSIZE)
{
if (*str == '\0')
return; /* We reached the end of the string without issues. */
str++;
offset++;
}
offset = 0; /* Next page will start at the beginning. */
}
}