754 lines
24 KiB
C
754 lines
24 KiB
C
#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 "vm/frame.h"
|
|
#include "vm/page.h"
|
|
#include "vm/mmap.h"
|
|
#include <stdio.h>
|
|
#include <stdbool.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_ptr (const void *start, size_t size,
|
|
bool write);
|
|
static void validate_and_pin_user_ptr (const void *start, size_t size,
|
|
bool write);
|
|
static void validate_and_pin_user_str (const char *ptr);
|
|
static void unpin_user_ptr (const void *start, size_t size);
|
|
|
|
static void unpin_user_str (const char *ptr);
|
|
static int get_user (const uint8_t *);
|
|
static bool put_user (uint8_t *, uint8_t);
|
|
|
|
/* 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_ptr (f->esp, sizeof (uintptr_t), false);
|
|
uintptr_t syscall_number = *(int *)f->esp;
|
|
thread_current ()->curr_esp = 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_ptr (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_and_pin_user_str (cmd_line);
|
|
pid_t pid = process_execute (cmd_line);
|
|
unpin_user_str (cmd_line);
|
|
|
|
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); /* 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_and_pin_user_str (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);
|
|
|
|
unpin_user_str (file);
|
|
|
|
/* 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_and_pin_user_str (file);
|
|
|
|
/* Acquire the file system lock to prevent race conditions. */
|
|
lock_acquire (&filesys_lock);
|
|
bool status = filesys_remove (file);
|
|
lock_release (&filesys_lock);
|
|
|
|
unpin_user_str (file);
|
|
|
|
/* 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_and_pin_user_str (file);
|
|
|
|
/* Acquire the file system lock to prevent race conditions. */
|
|
lock_acquire (&filesys_lock);
|
|
struct file *ptr = filesys_open (file);
|
|
lock_release (&filesys_lock);
|
|
|
|
unpin_user_str (file);
|
|
|
|
/* 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;
|
|
|
|
if (fd == STDIN_FILENO)
|
|
{
|
|
/* Validate the user buffer. */
|
|
validate_user_ptr (buffer, size, true);
|
|
|
|
/* 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;
|
|
|
|
/* Validate the user buffer, and pin the pages to prevent eviction. */
|
|
validate_and_pin_user_ptr (buffer, size, true);
|
|
|
|
/* Acquire the file system lock to prevent race-conditions. */
|
|
lock_acquire (&filesys_lock);
|
|
int bytes_read = file_read (file_info->file, buffer, size);
|
|
lock_release (&filesys_lock);
|
|
|
|
/* Unpin the pages to allow eviction. */
|
|
unpin_user_ptr (buffer, size);
|
|
|
|
/* Return the number of bytes read. */
|
|
return bytes_read;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
|
|
if (fd == STDOUT_FILENO)
|
|
{
|
|
/* Validate the user buffer. */
|
|
validate_user_ptr (buffer, size, false);
|
|
|
|
/* 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;
|
|
|
|
/* Validate the user buffer, and pin the pages to prevent eviction. */
|
|
validate_and_pin_user_ptr (buffer, size, false);
|
|
|
|
/* Acquire the file system lock to prevent race conditions. */
|
|
lock_acquire (&filesys_lock);
|
|
int bytes_written = file_write (file_info->file, buffer, size);
|
|
lock_release (&filesys_lock);
|
|
|
|
/* Unpin the pages to allow eviction. */
|
|
unpin_user_ptr (buffer, size);
|
|
|
|
/* Return the number of bytes written. */
|
|
return bytes_written;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
|
|
/* ensures the page for mmap does not overlap with the stack */
|
|
if (addr >= (thread_current ()->curr_esp - PGSIZE))
|
|
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 (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);
|
|
}
|
|
|
|
/* Helper function that validates a block of memory and optionally pins frames.
|
|
thread_exit() if the memory is invalid. Used only by the two helper functions
|
|
validate_user_ptr and validate_and_pin_user_ptr. See the comments for those
|
|
functions for more details on each. */
|
|
static void
|
|
validate_user_ptr_helper (const void *start, size_t size, bool write, bool pin)
|
|
{
|
|
if (size == 0)
|
|
return;
|
|
|
|
/* ptr < ptr + size - 1, so sufficient to check that (ptr + size -1) is a
|
|
valid user virtual memory address. */
|
|
void *end = start + size - 1;
|
|
if (!is_user_vaddr (end))
|
|
syscall_exit (EXIT_FAILURE);
|
|
|
|
for (const void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE)
|
|
{
|
|
int result;
|
|
|
|
/* Check read access to pointer. */
|
|
if ((result = get_user (ptr)) == -1)
|
|
syscall_exit (EXIT_FAILURE);
|
|
|
|
/* Check write access to pointer (if required). */
|
|
if (write && !put_user ((uint8_t *)ptr, result))
|
|
syscall_exit (EXIT_FAILURE);
|
|
|
|
/* If pin is set, pin the frame to prevent eviction. */
|
|
if (pin)
|
|
{
|
|
void *kpage = pagedir_get_page(thread_current()->pagedir, ptr);
|
|
if (kpage == NULL)
|
|
{
|
|
// If it was evicted, try to load it back in.
|
|
ptr -= PGSIZE;
|
|
continue;
|
|
}
|
|
|
|
frame_pin(kpage);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Validates if a block of memory starting at PTR and of size SIZE bytes is
|
|
fully contained within valid user virtual memory. thread_exit () if the
|
|
memory is invalid.
|
|
If the size is 0, the function does no checks and returns PTR. */
|
|
static void
|
|
validate_user_ptr (const void *start, size_t size, bool write)
|
|
{
|
|
validate_user_ptr_helper (start, size, write, false);
|
|
}
|
|
|
|
/* Validates if a block of memory starting at PTR and of size SIZE bytes is
|
|
fully contained within valid user virtual memory. thread_exit () if the
|
|
memory is invalid. The function also checks if the memory is writable if
|
|
WRITE flag is set.
|
|
|
|
The function attempts to preload the pages in case they are not in memory
|
|
yet (e.g., in a swap, lazy loading). If this is successful, the frame pages
|
|
are pinned to prevent eviction prior to access.
|
|
|
|
As such, a call to this function MUST be followed by a call to
|
|
unpin_user_ptr (START, SIZE) to unpin the pages and allow eviction.
|
|
|
|
If the size is 0, the function does no checks and returns PTR. */
|
|
static void
|
|
validate_and_pin_user_ptr (const void *start, size_t size, bool write)
|
|
{
|
|
validate_user_ptr_helper (start, size, write, true);
|
|
}
|
|
|
|
/* Unpins all the pages containing a block of memory starting at START and of
|
|
size SIZE bytes.
|
|
|
|
Pre: The pages were previously pinned by validate_and_pin_user_ptr (START,
|
|
SIZE). */
|
|
static void
|
|
unpin_user_ptr (const void *start, size_t size)
|
|
{
|
|
void *end = start + size - 1;
|
|
|
|
/* We don't need to do any checks as this function is always called after
|
|
validate_and_pin_user_ptr. */
|
|
/* Go through all pages in the block range, unpinning the frames. */
|
|
for (void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE)
|
|
{
|
|
void *kpage = pagedir_get_page (thread_current ()->pagedir, ptr);
|
|
ASSERT (kpage != NULL);
|
|
|
|
frame_unpin (kpage);
|
|
}
|
|
}
|
|
|
|
/* Validates of a C-string starting at ptr is fully contained within valid
|
|
user virtual memory. thread_exit () if the memory is invalid. */
|
|
static void
|
|
validate_and_pin_user_str (const char *ptr)
|
|
{
|
|
size_t offset = (uintptr_t) ptr % PGSIZE;
|
|
|
|
for (;;)
|
|
{
|
|
if (!is_user_vaddr (ptr))
|
|
syscall_exit (EXIT_FAILURE);
|
|
|
|
if (get_user ((const uint8_t *)ptr) == -1)
|
|
syscall_exit (EXIT_FAILURE);
|
|
|
|
/* Pin the frame to prevent eviction. */
|
|
void *page = pg_round_down (ptr);
|
|
void *kpage = pagedir_get_page (thread_current ()->pagedir, page);
|
|
if (kpage == NULL)
|
|
{
|
|
// If it was evicted, attempt to reload.
|
|
ptr -= PGSIZE;
|
|
continue;
|
|
}
|
|
|
|
frame_pin (kpage);
|
|
|
|
while (offset < PGSIZE)
|
|
{
|
|
if (*ptr == '\0')
|
|
return; /* We reached the end of the string without issues. */
|
|
|
|
ptr++;
|
|
offset++;
|
|
}
|
|
|
|
offset = 0;
|
|
}
|
|
}
|
|
|
|
/* Unpins all the pages containing a C-string starting at PTR.
|
|
|
|
Pre: The pages were previously pinned by validate_and_pin_user_str (PTR).
|
|
PTR points to a valid C string that ends with '\0'. */
|
|
static void
|
|
unpin_user_str (const char *ptr)
|
|
{
|
|
size_t offset = (uintptr_t)ptr % PGSIZE;
|
|
const char *str_ptr = ptr;
|
|
|
|
for (;;)
|
|
{
|
|
void *page = pg_round_down(str_ptr);
|
|
void *kpage = pagedir_get_page(thread_current()->pagedir, page);
|
|
ASSERT(kpage != NULL);
|
|
frame_unpin (kpage);
|
|
|
|
/* Scan until end of string or page */
|
|
while (offset < PGSIZE)
|
|
{
|
|
if (*str_ptr == '\0')
|
|
return; /* Found end of string */
|
|
str_ptr++;
|
|
offset++;
|
|
}
|
|
|
|
offset = 0;
|
|
}
|
|
}
|
|
|
|
/* PROVIDED BY SPEC.
|
|
Reads a byte at user virtual address UADDR.
|
|
UADDR must be below PHYS_BASE.
|
|
Returns the byte value if successful, -1 if a segfault occurred. */
|
|
static int
|
|
get_user (const uint8_t *uaddr)
|
|
{
|
|
int result;
|
|
asm ("movl $1f, %0; movzbl %1, %0; 1:" : "=&a"(result) : "m"(*uaddr));
|
|
return result;
|
|
}
|
|
|
|
/* PROVIDED BY SPEC.
|
|
Writes BYTE to user address UDST.
|
|
UDST must be below PHYS_BASE.
|
|
Returns true if successful, false if a segfault occurred. */
|
|
static bool
|
|
put_user (uint8_t *udst, uint8_t byte)
|
|
{
|
|
int error_code;
|
|
asm ("movl $1f, %0; movb %b2, %1; 1:"
|
|
: "=&a"(error_code), "=m"(*udst)
|
|
: "q"(byte));
|
|
return error_code != -1;
|
|
}
|