# Conflicts: # .gitlab-ci.yml # src/Makefile.build # src/threads/thread.c # src/userprog/exception.c # src/userprog/process.c # src/vm/frame.c # src/vm/page.c # src/vm/page.h # src/vm/stackgrowth.c # src/vm/stackgrowth.h
804 lines
26 KiB
C
804 lines
26 KiB
C
#include "userprog/process.h"
|
|
#include <debug.h>
|
|
#include <hash.h>
|
|
#include <inttypes.h>
|
|
#include <list.h>
|
|
#include <round.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "userprog/gdt.h"
|
|
#include "userprog/pagedir.h"
|
|
#include "userprog/syscall.h"
|
|
#include "userprog/tss.h"
|
|
#include "filesys/directory.h"
|
|
#include "filesys/file.h"
|
|
#include "filesys/filesys.h"
|
|
#include "threads/flags.h"
|
|
#include "threads/init.h"
|
|
#include "threads/interrupt.h"
|
|
#include "threads/synch.h"
|
|
#include "threads/palloc.h"
|
|
#include "threads/malloc.h"
|
|
#include "threads/thread.h"
|
|
#include "threads/vaddr.h"
|
|
#include "threads/synch.h"
|
|
#include "devices/timer.h"
|
|
#include "vm/page.h"
|
|
#include "vm/mmap.h"
|
|
#ifdef VM
|
|
#include "vm/frame.h"
|
|
#endif
|
|
|
|
/* Defines the native number of bytes processed by the processor
|
|
(for the purposes of alignment). */
|
|
#define WORD_SIZE 4
|
|
|
|
/* Defines non-negative integer division wherein the result is always rounded
|
|
up. */
|
|
#define DIV_CEIL(x, y) ((x + (y - 1)) / y)
|
|
|
|
/* Keeps track of the position of pointers to user program arguments
|
|
within a linked list. */
|
|
struct arg_elem
|
|
{
|
|
char* arg;
|
|
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_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. */
|
|
bool success; /* Indicates whether the process was successfully loaded. */
|
|
struct semaphore loaded; /* Semaphore used to signal that the process has
|
|
finished attempting to load. */
|
|
};
|
|
|
|
static thread_func start_process NO_RETURN;
|
|
static void destruct_process_result (struct hash_elem *e, void *aux UNUSED);
|
|
static bool load (const char *cmdline, void (**eip) (void), void **esp);
|
|
|
|
/* 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 *cmd)
|
|
{
|
|
char *cmd_copy;
|
|
tid_t tid;
|
|
struct process_start_data data;
|
|
|
|
/* Make a copy of command.
|
|
Otherwise there's a race between the caller and load(). */
|
|
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 (cmd_copy, cmd, PGSIZE);
|
|
|
|
/* Retrieve first argument of command, which is the file name
|
|
of the process. */
|
|
char *file_name = strtok_r (cmd_copy, " ", &data.cmd_saveptr);
|
|
|
|
/* Validates that the current file to be executed can be opened/exists. */
|
|
lock_acquire (&filesys_lock);
|
|
struct file *file = filesys_open (file_name);
|
|
lock_release (&filesys_lock);
|
|
if (file == NULL)
|
|
return TID_ERROR;
|
|
|
|
/* 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'. */
|
|
strlcpy (data.file_name, file_name, FNAME_MAX_LEN + 1);
|
|
sema_init (&data.loaded, 0);
|
|
data.success = false;
|
|
|
|
tid = thread_create (file_name, PRI_DEFAULT, start_process, &data);
|
|
|
|
/* Wait until process file has finished attempting to load via the child
|
|
thread before reporting success of starting execution. */
|
|
if (tid != TID_ERROR)
|
|
{
|
|
sema_down (&data.loaded);
|
|
if (!data.success)
|
|
tid = TID_ERROR;
|
|
}
|
|
|
|
palloc_free_page (cmd_copy);
|
|
return tid;
|
|
}
|
|
|
|
static void *get_usr_kpage (enum palloc_flags flags, void *upage);
|
|
static void free_usr_kpage (void *kpage);
|
|
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)))
|
|
|
|
/* 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), the name of the file being executed, and a semaphore that
|
|
calls sema_up to indicate that the 'success' variable passed to it
|
|
has been updated to indicate whether the process file loading succeeded.
|
|
This involves loading the specified file and calling its main () function
|
|
with the specified command arguments. */
|
|
static void
|
|
start_process (void *proc_start_data)
|
|
{
|
|
struct intr_frame if_;
|
|
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;
|
|
|
|
/* Acquire the file system lock to prevent race conditions. */
|
|
lock_acquire (&filesys_lock);
|
|
|
|
struct file *exec_file = filesys_open (data->file_name);
|
|
if (exec_file == NULL)
|
|
{
|
|
/* If the executable file cannot be opened, free resources and quit. */
|
|
lock_release (&filesys_lock);
|
|
sema_up (&data->loaded);
|
|
thread_exit ();
|
|
}
|
|
|
|
/* Deny write to the executable file to prevent writing to it and release the
|
|
file system lock. */
|
|
file_deny_write (exec_file);
|
|
lock_release (&filesys_lock);
|
|
|
|
thread_current ()->exec_file = exec_file;
|
|
|
|
/* Load the ELF executable file, and store the success of the operation in
|
|
the 'success' variable in data. */
|
|
data->success = load (data->file_name, &if_.eip, &if_.esp);
|
|
|
|
/* If load was sucessful, initialize user process stack and free page used
|
|
to store the command that executed the process. */
|
|
if (data->success)
|
|
{
|
|
data->success =
|
|
process_init_stack (data->cmd_saveptr, &if_.esp, data->file_name);
|
|
}
|
|
|
|
/* Signal that the process has finished attempting to load. */
|
|
bool success = data->success;
|
|
sema_up (&data->loaded);
|
|
|
|
/* If the load was unsuccessful or if it was but the stack initialization
|
|
failed, exit the thread. */
|
|
if (!success)
|
|
thread_exit ();
|
|
|
|
/* 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
|
|
arguments on the stack in the form of a `struct intr_frame',
|
|
we just point the stack pointer (%esp) to our stack frame
|
|
and jump to it. */
|
|
asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory");
|
|
NOT_REACHED ();
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
ASSERT (cmd_saveptr != NULL);
|
|
ASSERT (esp != NULL);
|
|
ASSERT (file_name != NULL);
|
|
|
|
/* 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)
|
|
{
|
|
/* filename has already been validated to be a safe-to-access string,
|
|
so we can safely use strlen here. Filename has already been
|
|
split from the command line arguments. */
|
|
push_to_stack (esp, arg, (strlen (arg) + 1) * sizeof (char));
|
|
|
|
/* Try to allocate memory for the argument pointer. */
|
|
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;
|
|
}
|
|
|
|
/* Store the argument pointer in the linked list. */
|
|
arg_elem->arg = *esp;
|
|
list_push_front (&arg_list, &arg_elem->elem);
|
|
|
|
/* Increment the argument count and get the next argument. */
|
|
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 as many extra pages as needed to the user process
|
|
contiguously in the virtual address space below the initial page. */
|
|
int overflow_bytes = (PHYS_BASE - *esp) + remaining_size - PGSIZE;
|
|
if (overflow_bytes > 0)
|
|
{
|
|
/* Calculate the number of pages needed to allocate. */
|
|
int pages_needed = DIV_CEIL (overflow_bytes, PGSIZE);
|
|
|
|
/* Allocate the pages and map them to the user process. */
|
|
void *upage;
|
|
uint8_t *kpage;
|
|
for (int i = 1; i < pages_needed + 1; i++)
|
|
{
|
|
upage = ((uint8_t *) PHYS_BASE) - PGSIZE * (i + 1);
|
|
kpage = get_usr_kpage (PAL_ZERO, upage);
|
|
if (!install_page (upage, 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.
|
|
* If it was terminated by the kernel (i.e. killed due to an exception),
|
|
* returns -1.
|
|
* If TID is invalid or if it was not a child of the calling process, or if
|
|
* process_wait() has already been successfully called for the given TID,
|
|
* returns -1 immediately, without waiting.
|
|
*
|
|
* This function will be implemented in task 2.
|
|
* For now, it does nothing. */
|
|
int
|
|
process_wait (tid_t child_tid)
|
|
{
|
|
struct thread *t = thread_current ();
|
|
struct process_result fake_result;
|
|
fake_result.tid = child_tid;
|
|
struct hash_elem *e = hash_find (&t->child_results, &fake_result.elem);
|
|
if (e == NULL)
|
|
return -1;
|
|
|
|
struct process_result *child_result
|
|
= hash_entry (e, struct process_result, elem);
|
|
/* 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 table.
|
|
No need to use lock since this is the only thread with access to
|
|
the struct process_result now. */
|
|
hash_delete (&t->child_results, &child_result->elem);
|
|
|
|
/* Get the exit status of the child */
|
|
int exit_status = child_result->exit_status;
|
|
|
|
/* Release the lock */
|
|
lock_release (&child_result->lock);
|
|
/* Result no-longer used by parent, nor child. Deallocate it. */
|
|
free (child_result);
|
|
return exit_status;
|
|
}
|
|
|
|
/* Free the current process's resources. */
|
|
void
|
|
process_exit (void)
|
|
{
|
|
struct thread *cur = thread_current ();
|
|
uint32_t *pd;
|
|
|
|
/* Unmap all memory mapped files */
|
|
mmap_destroy ();
|
|
|
|
/* Clean up all open files */
|
|
hash_destroy (&cur->open_files, fd_cleanup);
|
|
|
|
/* Clean up the thread's supplemental page table. */
|
|
hash_destroy (&cur->pages, page_cleanup);
|
|
|
|
/* Close the executable file, implicitly allowing it to be written to. */
|
|
if (cur->exec_file != NULL)
|
|
{
|
|
/* Acquire the file system lock to prevent race conditions. */
|
|
lock_acquire (&filesys_lock);
|
|
file_close (cur->exec_file);
|
|
lock_release (&filesys_lock);
|
|
}
|
|
|
|
if (cur->result != NULL)
|
|
{
|
|
printf ("%s: exit(%d)\n", cur->name, cur->result->exit_status);
|
|
/* Update own process result. */
|
|
destruct_process_result (&cur->result->elem, cur);
|
|
}
|
|
|
|
/* Free child process results or signal parent's death. */
|
|
hash_destroy (&cur->child_results, destruct_process_result);
|
|
|
|
/* Destroy the current process's page directory and switch back
|
|
to the kernel-only page directory. */
|
|
pd = cur->pagedir;
|
|
if (pd != NULL)
|
|
{
|
|
/* Correct ordering here is crucial. We must set
|
|
cur->pagedir to NULL before switching page directories,
|
|
so that a timer interrupt can't switch back to the
|
|
process page directory. We must activate the base page
|
|
directory before destroying the process's page
|
|
directory, or our active page directory will be one
|
|
that's been freed (and cleared). */
|
|
cur->pagedir = NULL;
|
|
pagedir_activate (NULL);
|
|
pagedir_destroy (pd);
|
|
}
|
|
}
|
|
|
|
/* Destruct a process_result, with multi-thread awareness.
|
|
If the other thread is running, simply signals death. Otherwise
|
|
frees the result. */
|
|
static void
|
|
destruct_process_result (struct hash_elem *e, void *aux UNUSED)
|
|
{
|
|
struct process_result *result = hash_entry (e, struct process_result, elem);
|
|
lock_acquire (&result->lock);
|
|
/* Other thread has died (and was not waited for). Free the result. */
|
|
if (sema_try_down (&result->sema))
|
|
{
|
|
lock_release (&result->lock);
|
|
free (result);
|
|
}
|
|
/* Other thread is still alive, signal via sema that parent has died. */
|
|
else
|
|
{
|
|
sema_up (&result->sema);
|
|
lock_release (&result->lock);
|
|
}
|
|
}
|
|
|
|
/* Sets up the CPU for running user code in the current
|
|
thread.
|
|
This function is called on every context switch. */
|
|
void
|
|
process_activate (void)
|
|
{
|
|
struct thread *t = thread_current ();
|
|
|
|
/* Activate thread's page tables. */
|
|
pagedir_activate (t->pagedir);
|
|
|
|
/* Set thread's kernel stack for use in processing
|
|
interrupts. */
|
|
tss_update ();
|
|
}
|
|
|
|
/* We load ELF binaries. The following definitions are taken
|
|
from the ELF specification, [ELF1], more-or-less verbatim. */
|
|
|
|
/* ELF types. See [ELF1] 1-2. */
|
|
typedef uint32_t Elf32_Word, Elf32_Addr, Elf32_Off;
|
|
typedef uint16_t Elf32_Half;
|
|
|
|
/* For use with ELF types in printf(). */
|
|
#define PE32Wx PRIx32 /* Print Elf32_Word in hexadecimal. */
|
|
#define PE32Ax PRIx32 /* Print Elf32_Addr in hexadecimal. */
|
|
#define PE32Ox PRIx32 /* Print Elf32_Off in hexadecimal. */
|
|
#define PE32Hx PRIx16 /* Print Elf32_Half in hexadecimal. */
|
|
|
|
/* Executable header. See [ELF1] 1-4 to 1-8.
|
|
This appears at the very beginning of an ELF binary. */
|
|
struct Elf32_Ehdr
|
|
{
|
|
unsigned char e_ident[16];
|
|
Elf32_Half e_type;
|
|
Elf32_Half e_machine;
|
|
Elf32_Word e_version;
|
|
Elf32_Addr e_entry;
|
|
Elf32_Off e_phoff;
|
|
Elf32_Off e_shoff;
|
|
Elf32_Word e_flags;
|
|
Elf32_Half e_ehsize;
|
|
Elf32_Half e_phentsize;
|
|
Elf32_Half e_phnum;
|
|
Elf32_Half e_shentsize;
|
|
Elf32_Half e_shnum;
|
|
Elf32_Half e_shstrndx;
|
|
};
|
|
|
|
/* Program header. See [ELF1] 2-2 to 2-4.
|
|
There are e_phnum of these, starting at file offset e_phoff
|
|
(see [ELF1] 1-6). */
|
|
struct Elf32_Phdr
|
|
{
|
|
Elf32_Word p_type;
|
|
Elf32_Off p_offset;
|
|
Elf32_Addr p_vaddr;
|
|
Elf32_Addr p_paddr;
|
|
Elf32_Word p_filesz;
|
|
Elf32_Word p_memsz;
|
|
Elf32_Word p_flags;
|
|
Elf32_Word p_align;
|
|
};
|
|
|
|
/* Values for p_type. See [ELF1] 2-3. */
|
|
#define PT_NULL 0 /* Ignore. */
|
|
#define PT_LOAD 1 /* Loadable segment. */
|
|
#define PT_DYNAMIC 2 /* Dynamic linking info. */
|
|
#define PT_INTERP 3 /* Name of dynamic loader. */
|
|
#define PT_NOTE 4 /* Auxiliary info. */
|
|
#define PT_SHLIB 5 /* Reserved. */
|
|
#define PT_PHDR 6 /* Program header table. */
|
|
#define PT_STACK 0x6474e551 /* Stack segment. */
|
|
|
|
/* Flags for p_flags. See [ELF3] 2-3 and 2-4. */
|
|
#define PF_X 1 /* Executable. */
|
|
#define PF_W 2 /* Writable. */
|
|
#define PF_R 4 /* Readable. */
|
|
|
|
static bool setup_stack (void **esp);
|
|
static bool validate_segment (const struct Elf32_Phdr *, struct file *);
|
|
static bool load_segment (struct file *file, off_t ofs, uint8_t *upage,
|
|
uint32_t read_bytes, uint32_t zero_bytes,
|
|
bool writable);
|
|
|
|
/* Loads an ELF executable from FILE_NAME into the current thread.
|
|
Stores the executable's entry point into *EIP
|
|
and its initial stack pointer into *ESP.
|
|
Returns true if successful, false otherwise. */
|
|
bool
|
|
load (const char *file_name, void (**eip) (void), void **esp)
|
|
{
|
|
struct thread *t = thread_current ();
|
|
struct Elf32_Ehdr ehdr;
|
|
struct file *file = NULL;
|
|
off_t file_ofs;
|
|
bool success = false;
|
|
int i;
|
|
lock_acquire (&filesys_lock);
|
|
|
|
/* Allocate and activate page directory. */
|
|
t->pagedir = pagedir_create ();
|
|
if (t->pagedir == NULL)
|
|
goto done;
|
|
process_activate ();
|
|
|
|
/* Open executable file. */
|
|
file = filesys_open (file_name);
|
|
if (file == NULL)
|
|
{
|
|
printf ("load: %s: open failed\n", file_name);
|
|
goto done;
|
|
}
|
|
|
|
/* Read and verify executable header. */
|
|
if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr
|
|
|| memcmp (ehdr.e_ident, "\177ELF\1\1\1", 7)
|
|
|| ehdr.e_type != 2
|
|
|| ehdr.e_machine != 3
|
|
|| ehdr.e_version != 1
|
|
|| ehdr.e_phentsize != sizeof (struct Elf32_Phdr)
|
|
|| ehdr.e_phnum > 1024)
|
|
{
|
|
printf ("load: %s: error loading executable\n", file_name);
|
|
goto done;
|
|
}
|
|
|
|
/* Read program headers. */
|
|
file_ofs = ehdr.e_phoff;
|
|
for (i = 0; i < ehdr.e_phnum; i++)
|
|
{
|
|
struct Elf32_Phdr phdr;
|
|
|
|
if (file_ofs < 0 || file_ofs > file_length (file))
|
|
goto done;
|
|
file_seek (file, file_ofs);
|
|
|
|
if (file_read (file, &phdr, sizeof phdr) != sizeof phdr)
|
|
goto done;
|
|
file_ofs += sizeof phdr;
|
|
switch (phdr.p_type)
|
|
{
|
|
case PT_NULL:
|
|
case PT_NOTE:
|
|
case PT_PHDR:
|
|
case PT_STACK:
|
|
default:
|
|
/* Ignore this segment. */
|
|
break;
|
|
case PT_DYNAMIC:
|
|
case PT_INTERP:
|
|
case PT_SHLIB:
|
|
goto done;
|
|
case PT_LOAD:
|
|
if (validate_segment (&phdr, file))
|
|
{
|
|
bool writable = (phdr.p_flags & PF_W) != 0;
|
|
uint32_t file_page = phdr.p_offset & ~PGMASK;
|
|
uint32_t mem_page = phdr.p_vaddr & ~PGMASK;
|
|
uint32_t page_offset = phdr.p_vaddr & PGMASK;
|
|
uint32_t read_bytes, zero_bytes;
|
|
if (phdr.p_filesz > 0)
|
|
{
|
|
/* Normal segment.
|
|
Read initial part from disk and zero the rest. */
|
|
read_bytes = page_offset + phdr.p_filesz;
|
|
zero_bytes = (ROUND_UP (page_offset + phdr.p_memsz, PGSIZE)
|
|
- read_bytes);
|
|
}
|
|
else
|
|
{
|
|
/* Entirely zero.
|
|
Don't read anything from disk. */
|
|
read_bytes = 0;
|
|
zero_bytes = ROUND_UP (page_offset + phdr.p_memsz, PGSIZE);
|
|
}
|
|
if (!load_segment (file, file_page, (void *) mem_page,
|
|
read_bytes, zero_bytes, writable))
|
|
goto done;
|
|
}
|
|
else
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set up stack. */
|
|
if (!setup_stack (esp))
|
|
goto done;
|
|
|
|
/* Start address. */
|
|
*eip = (void (*) (void)) ehdr.e_entry;
|
|
|
|
success = true;
|
|
|
|
done:
|
|
/* We arrive here whether the load is successful or not. */
|
|
#ifndef VM
|
|
file_close (file);
|
|
#endif
|
|
lock_release (&filesys_lock);
|
|
return success;
|
|
}
|
|
|
|
/* load() helpers. */
|
|
|
|
/* Checks whether PHDR describes a valid, loadable segment in
|
|
FILE and returns true if so, false otherwise. */
|
|
static bool
|
|
validate_segment (const struct Elf32_Phdr *phdr, struct file *file)
|
|
{
|
|
/* p_offset and p_vaddr must have the same page offset. */
|
|
if ((phdr->p_offset & PGMASK) != (phdr->p_vaddr & PGMASK))
|
|
return false;
|
|
|
|
/* p_offset must point within FILE. */
|
|
if (phdr->p_offset > (Elf32_Off) file_length (file))
|
|
return false;
|
|
|
|
/* p_memsz must be at least as big as p_filesz. */
|
|
if (phdr->p_memsz < phdr->p_filesz)
|
|
return false;
|
|
|
|
/* The segment must not be empty. */
|
|
if (phdr->p_memsz == 0)
|
|
return false;
|
|
|
|
/* The virtual memory region must both start and end within the
|
|
user address space range. */
|
|
if (!is_user_vaddr ((void *) phdr->p_vaddr))
|
|
return false;
|
|
if (!is_user_vaddr ((void *) (phdr->p_vaddr + phdr->p_memsz)))
|
|
return false;
|
|
|
|
/* The region cannot "wrap around" across the kernel virtual
|
|
address space. */
|
|
if (phdr->p_vaddr + phdr->p_memsz < phdr->p_vaddr)
|
|
return false;
|
|
|
|
/* Disallow mapping page 0.
|
|
Not only is it a bad idea to map page 0, but if we allowed
|
|
it then user code that passed a null pointer to system calls
|
|
could quite likely panic the kernel by way of null pointer
|
|
assertions in memcpy(), etc. */
|
|
if (phdr->p_vaddr < PGSIZE)
|
|
return false;
|
|
|
|
/* It's okay. */
|
|
return true;
|
|
}
|
|
|
|
/* Loads a segment starting at offset OFS in FILE at address
|
|
UPAGE. In total, READ_BYTES + ZERO_BYTES bytes of virtual
|
|
memory are initialized, as follows:
|
|
|
|
- READ_BYTES bytes at UPAGE must be read from FILE
|
|
starting at offset OFS.
|
|
|
|
- ZERO_BYTES bytes at UPAGE + READ_BYTES must be zeroed.
|
|
|
|
The pages initialized by this function must be writable by the
|
|
user process if WRITABLE is true, read-only otherwise.
|
|
|
|
Return true if successful, false if a memory allocation error
|
|
or disk read error occurs. */
|
|
static bool
|
|
load_segment (struct file *file, off_t ofs, uint8_t *upage,
|
|
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
|
|
{
|
|
ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
|
|
ASSERT (pg_ofs (upage) == 0);
|
|
ASSERT (ofs % PGSIZE == 0);
|
|
|
|
while (read_bytes > 0 || zero_bytes > 0)
|
|
{
|
|
/* Calculate how to fill this page.
|
|
We will read PAGE_READ_BYTES bytes from FILE
|
|
and zero the final PAGE_ZERO_BYTES bytes. */
|
|
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
|
|
size_t page_zero_bytes = PGSIZE - page_read_bytes;
|
|
|
|
/* Add the page metadata to the SPT to be lazy loaded later on */
|
|
if (page_insert (file, ofs, upage, page_read_bytes, page_zero_bytes,
|
|
writable, PAGE_FILE) == NULL)
|
|
return false;
|
|
|
|
/* Advance. */
|
|
read_bytes -= page_read_bytes;
|
|
zero_bytes -= page_zero_bytes;
|
|
ofs += PGSIZE;
|
|
upage += PGSIZE;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Create a minimal stack by mapping a zeroed page at the top of
|
|
user virtual memory. */
|
|
static bool
|
|
setup_stack (void **esp)
|
|
{
|
|
uint8_t *kpage;
|
|
bool success = false;
|
|
|
|
void *upage = ((uint8_t *) PHYS_BASE) - PGSIZE;
|
|
|
|
kpage = get_usr_kpage (PAL_ZERO, upage);
|
|
if (kpage != NULL)
|
|
{
|
|
success = install_page (upage, kpage, true);
|
|
if (success)
|
|
*esp = PHYS_BASE;
|
|
else
|
|
free_usr_kpage (kpage);
|
|
}
|
|
return success;
|
|
}
|
|
|
|
/* Claims a page from the user pool for ownership by the current thread
|
|
and returns its kernel address, updating the frame table if VM
|
|
is enabled. Requires the intended virtual address for where the page
|
|
will be installed. */
|
|
static void *
|
|
get_usr_kpage (enum palloc_flags flags, void *upage)
|
|
{
|
|
void *page;
|
|
#ifdef VM
|
|
struct thread *t = thread_current ();
|
|
if (pagedir_get_page (t->pagedir, upage) != NULL)
|
|
return NULL;
|
|
else
|
|
page = frame_alloc (flags, upage, t);
|
|
pagedir_set_accessed (t->pagedir, upage, true);
|
|
#else
|
|
page = palloc_get_page (flags | PAL_USER);
|
|
#endif
|
|
return page;
|
|
}
|
|
|
|
/* Frees a page belonging to a user process given its kernel address,
|
|
updating the frame table if VM is enabled. */
|
|
static void
|
|
free_usr_kpage (void *kpage)
|
|
{
|
|
#ifdef VM
|
|
frame_free (kpage);
|
|
#else
|
|
palloc_free_page (kpage);
|
|
#endif
|
|
}
|
|
|
|
/* Adds a mapping from user virtual address UPAGE to kernel
|
|
virtual address KPAGE to the page table.
|
|
If WRITABLE is true, the user process may modify the page;
|
|
otherwise, it is read-only.
|
|
UPAGE must not already be mapped.
|
|
KPAGE should probably be a page obtained from the user pool
|
|
with palloc_get_page().
|
|
Returns true on success, false if UPAGE is already mapped or
|
|
if memory allocation fails. */
|
|
bool
|
|
install_page (void *upage, void *kpage, bool writable)
|
|
{
|
|
struct thread *t = thread_current ();
|
|
|
|
/* Verify that there's not already a page at that virtual
|
|
address, then map our page there. */
|
|
return (pagedir_get_page (t->pagedir, upage) == NULL
|
|
&& pagedir_set_page (t->pagedir, upage, kpage, writable));
|
|
}
|