From: Ben Pfaff Date: Mon, 20 Sep 2004 22:29:18 +0000 (+0000) Subject: Rename addrspace to process. X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f0612244c44f4b4f0bc79e3fc882e9f74bd4a3f4;p=pintos-anon Rename addrspace to process. --- diff --git a/doc/userprog.texi b/doc/userprog.texi index 8ac03da..14fa118 100644 --- a/doc/userprog.texi +++ b/doc/userprog.texi @@ -56,15 +56,9 @@ doing is to simply go over each part you'll be working with. In where the bulk of your work will be: @table @file -@item addrspace.c -@itemx addrspace.h -An address space keeps track of all the data necessary to execute a -user program. Address space data is stored in @code{struct thread}, -but manipulated only by @file{addrspace.c}. Address spaces need to -keep track of things like paging information for the process (so that -it knows which memory the process is using). Address spaces also -handle loading the program into memory and starting up the process's -execution. +@item process.c +@itemx process.h +Loads ELF binaries and starts processes. @item pagedir.c @itemx pagedir.h @@ -172,16 +166,16 @@ we require you to support.) The only other limitation is that Pintos can't run programs using floating point operations, since it doesn't include the necessary kernel functionality to save and restore the processor's floating-point unit when switching threads. You can look -in @file{test} directory for some examples. +in @file{tests/userprog} directory for some examples. Pintos loads ELF executables, where ELF is an executable format used by Linux, Solaris, and many other Unix and Unix-like systems. Therefore, you can use any compiler and linker that produce 80@var{x}86 ELF executables to produce programs for Pintos. We -recommend using the tools we provide in the @file{tests} directory. By -default, the @file{Makefile} in this directory will compile the test -programs we provide. You can edit the @file{Makefile} to compile your -own test programs as well. +recommend using the tools we provide in the @file{tests/userprog} +directory. By default, the @file{Makefile} in this directory will +compile the test programs we provide. You can edit the +@file{Makefile} to compile your own test programs as well. One thing you should realize immediately is that, until you use the above operation to copy a test program to the emulated disk, Pintos @@ -222,9 +216,9 @@ access kernel virtual memory will cause a page fault, handled by @code{page_fault()} in @file{userprog/exception.c}, and the process will be terminated. Kernel threads can access both kernel virtual memory and, if a user process is running, the user virtual memory of -the running process. However, an attempt to access memory at a user -virtual address that doesn't have a page mapped into it will also -cause a page fault. +the running process. However, even in the kernel, an attempt to +access memory at a user virtual address that doesn't have a page +mapped into it will cause a page fault. @node Global Requirements @section Global Requirements @@ -246,13 +240,13 @@ first process. @node Problem 2-1 Argument Passing @section Problem 2-1: Argument Passing -Currently, @code{thread_execute()} does not support passing arguments +Currently, @code{process_execute()} does not support passing arguments to new processes. UNIX and other operating systems do allow passing command line arguments to a program, which accesses them via the argc, argv arguments to main. You must implement this functionality by -extending @code{thread_execute()} so that instead of simply taking a +extending @code{process_execute()} so that instead of simply taking a program file name, it can take a program name with arguments as a -single string. That is, @code{thread_execute("grep foo *.c")} should +single string. That is, @code{process_execute("grep foo *.c")} should be a legal call. @xref{80x86 Calling Convention}, for information on exactly how this works. @@ -374,7 +368,7 @@ is not safe to call into the filesystem code provided in the recommend adding a single lock that controls access to the filesystem code. You should acquire this lock before calling any functions in the @file{filesys} directory, and release it afterward. Don't forget -that @file{addrspace_load()} also accesses files. @strong{For now, we +that @file{process_execute()} also accesses files. @strong{For now, we recommend against modifying code in the @file{filesys} directory.} We have provided you a function for each system call in @@ -433,7 +427,7 @@ You need to modify @file{tests/Makefile}. @b{What's the difference between @code{tid_t} and @code{pid_t}?} A @code{tid_t} identifies a kernel thread, which may have a user -process running in it (if created with @code{thread_execute()}) or not +process running in it (if created with @code{process_execute()}) or not (if created with @code{thread_create()}). It is a data type used only in the kernel. @@ -464,6 +458,10 @@ dereference user pointers directly and handle page faults by terminating the process. In either case, you'll need to reject kernel pointers as a special case. +If you choose to translate user addresses into kernel addresses, +you'll want to look at @file{threads/mmu.h}, which has all kinds of +useful functions for manipulating virtual addresses. + @item @b{I'm also confused about reading from and writing to the stack. Can you help?} @@ -485,6 +483,12 @@ the location. @item Each character is 1 byte. @end itemize + +@item +@b{Why doesn't keyboard input work with @option{-nv}?} + +Serial input isn't implemented. Don't use @option{-nv} if you want to +use the shell or otherwise type at the keyboard. @end enumerate @item Argument Passing FAQs diff --git a/doc/vm.texi b/doc/vm.texi index d008067..d9d5504 100644 --- a/doc/vm.texi +++ b/doc/vm.texi @@ -16,7 +16,7 @@ directory contains only the @file{Makefile}s. The only change from write will either be newly generated files (e.g.@: if you choose to implement your paging code in their own source files), or will be modifications to pre-existing code (e.g.@: you will change the -behavior of @file{addrspace.c} significantly). +behavior of @file{process.c} significantly). You will be building this assignment on the last one. It will benefit you to get your project 2 in good working order before this assignment @@ -141,6 +141,28 @@ address. / / @end example +Header @file{threads/mmu.h} has useful functions for various +operations on virtual addresses. You should look over the header +yourself, but its most important functions include these: + +@table @code +@item pd_no(@var{va}) +Returns the page directory index in virtual address @var{va}. + +@item pt_no(@var{va}) +Returns the page table index in virtual address @var{va}. + +@item pg_ofs(@var{va}) +Returns the page offset in virtual address @var{va}. + +@item pg_round_down(@var{va}) +Returns @var{va} rounded down to the nearest page boundary, that is, +@var{va} but with its page offset set to 0. + +@item pg_round_up(@var{va}) +Returns @var{va} rounded up to the nearest page boundary. +@end table + @node Disk as Backing Store @section Disk as Backing Store @@ -265,7 +287,7 @@ Follow the PDE to the page table. Point the PTE for the faulting virtual address to the physical page found in step 2. @end enumerate -You'll need to modify the ELF loader in @file{userprog/addrspace.c} to +You'll need to modify the ELF loader in @file{userprog/process.c} to do page table management according to your new design. As supplied, it reads all the process's pages from disk and initializes the page tables for them at the same time. For testing purposes, you'll @@ -328,7 +350,7 @@ file itself as backing store for read-only segments, since these segments won't change. There are a few special cases. Look at the loop in -@code{load_segment()} in @file{userprog/addrspace.c}. Each time +@code{load_segment()} in @file{userprog/process.c}. Each time around the loop, @code{read_bytes} represents the number of bytes to read from the executable file and @code{zero_bytes} represents the number of bytes to initialize to zero following the bytes read. The two diff --git a/src/Makefile.build b/src/Makefile.build index 1c92b77..bd619f4 100644 --- a/src/Makefile.build +++ b/src/Makefile.build @@ -51,7 +51,7 @@ filesys_SRC += filesys/inode.c # File headers. filesys_SRC += filesys/fsutil.c # Utilities. # User process code. -userprog_SRC = userprog/addrspace.c # Address spaces. +userprog_SRC = userprog/process.c # Process loading. userprog_SRC += userprog/pagedir.c # Page directories. userprog_SRC += userprog/exception.c # User exception handler. userprog_SRC += userprog/syscall.c # System call handler. diff --git a/src/threads/init.c b/src/threads/init.c index 1025172..0d9c699 100644 --- a/src/threads/init.c +++ b/src/threads/init.c @@ -21,7 +21,7 @@ #include "threads/test.h" #include "threads/thread.h" #ifdef USERPROG -#include "userprog/addrspace.h" +#include "userprog/process.h" #include "userprog/exception.h" #include "userprog/gdt.h" #include "userprog/syscall.h" @@ -113,7 +113,7 @@ main (void) if (initial_program != NULL) { printf ("\nExecuting '%s':\n", initial_program); - addrspace_execute (initial_program); + process_execute (initial_program); } #else test (); diff --git a/src/threads/thread.c b/src/threads/thread.c index fc358de..0861e29 100644 --- a/src/threads/thread.c +++ b/src/threads/thread.c @@ -12,7 +12,7 @@ #include "threads/switch.h" #include "threads/synch.h" #ifdef USERPROG -#include "userprog/addrspace.h" +#include "userprog/process.h" #include "userprog/gdt.h" #endif @@ -357,7 +357,7 @@ destroy_thread (struct thread *t) ASSERT (t != thread_current ()); #ifdef USERPROG - addrspace_destroy (t); + process_destroy (t); #endif if (t != initial_thread) palloc_free (t); @@ -387,7 +387,7 @@ schedule_tail (struct thread *prev) #ifdef USERPROG /* Activate the new address space. */ - addrspace_activate (); + process_activate (); #endif /* If the thread we switched from is dying, destroy it. diff --git a/src/threads/thread.h b/src/threads/thread.h index 26e785f..9b55700 100644 --- a/src/threads/thread.h +++ b/src/threads/thread.h @@ -93,7 +93,7 @@ struct thread list_elem elem; /* List element. */ #ifdef USERPROG - /* Owned by userprog/addrspace.c. */ + /* Owned by userprog/process.c. */ uint32_t *pagedir; /* Page directory. */ #endif diff --git a/src/userprog/addrspace.c b/src/userprog/addrspace.c deleted file mode 100644 index 10d5616..0000000 --- a/src/userprog/addrspace.c +++ /dev/null @@ -1,401 +0,0 @@ -#include "userprog/addrspace.h" -#include -#include -#include -#include -#include -#include "userprog/gdt.h" -#include "userprog/pagedir.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/mmu.h" -#include "threads/palloc.h" -#include "threads/thread.h" - -static thread_func execute_thread 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 before - addrspace_execute() returns.*/ -tid_t -addrspace_execute (const char *filename) -{ - char *fn_copy; - tid_t tid; - - /* Make a copy of FILENAME. - Otherwise there's a race between the caller and load(). */ - fn_copy = palloc_get (0); - if (fn_copy == NULL) - return TID_ERROR; - strlcpy (fn_copy, filename, PGSIZE); - - /* Create a new thread to execute FILENAME. */ - tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy); - if (tid == TID_ERROR) - palloc_free (fn_copy); - return tid; -} - -/* A thread function that loads a user process and starts it - running. */ -static void -execute_thread (void *filename_) -{ - char *filename = filename_; - struct intr_frame if_; - bool success; - - /* Initialize interrupt frame and load executable. */ - memset (&if_, 0, sizeof if_); - if_.es = SEL_UDSEG; - if_.ds = SEL_UDSEG; - if_.cs = SEL_UCSEG; - if_.eflags = FLAG_IF | FLAG_MBS; - if_.ss = SEL_UDSEG; - success = load (filename, &if_.eip, &if_.esp); - - /* If load failed, quit. */ - palloc_free (filename); - if (!success) - thread_exit (); - - /* Switch page tables. */ - addrspace_activate (); - - /* Start the user process by simulating a return from an - interrupt, implemented by intr_exit (in - threads/intr-stubs.pl). 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 ("mov %0, %%esp\n" - "jmp intr_exit\n" - : /* no outputs */ - : "g" (&if_)); - NOT_REACHED (); -} - -/* 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 load_segment (struct file *, const struct Elf32_Phdr *); -static bool setup_stack (void **esp); - -/* Aborts loading an executable, with an error message. */ -#define LOAD_ERROR(MSG) \ - do { \ - printf ("load: %s: ", filename); \ - printf MSG; \ - printf ("\n"); \ - goto done; \ - } while (0) - -/* Loads an ELF executable from FILENAME 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 *filename, 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; - - /* Allocate page directory. */ - t->pagedir = pagedir_create (); - if (t->pagedir == NULL) - LOAD_ERROR (("page directory allocation failed")); - - /* Open executable file. */ - file = filesys_open (filename); - if (file == NULL) - LOAD_ERROR (("open failed")); - - /* Read and verify executable header. */ - if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr) - LOAD_ERROR (("error reading executable header")); - if (memcmp (ehdr.e_ident, "\177ELF\1\1\1", 7) != 0) - LOAD_ERROR (("file is not ELF")); - if (ehdr.e_type != 2) - LOAD_ERROR (("ELF file is not an executable")); - if (ehdr.e_machine != 3) - LOAD_ERROR (("ELF executable is not x86")); - if (ehdr.e_version != 1) - LOAD_ERROR (("ELF executable hasunknown version %d", - (int) ehdr.e_version)); - if (ehdr.e_phentsize != sizeof (struct Elf32_Phdr)) - LOAD_ERROR (("bad ELF program header size")); - if (ehdr.e_phnum > 1024) - LOAD_ERROR (("too many ELF program headers")); - - /* Read program headers. */ - file_ofs = ehdr.e_phoff; - for (i = 0; i < ehdr.e_phnum; i++) - { - struct Elf32_Phdr phdr; - - file_seek (file, file_ofs); - if (file_read (file, &phdr, sizeof phdr) != sizeof phdr) - LOAD_ERROR (("error reading program header")); - file_ofs += sizeof phdr; - switch (phdr.p_type) - { - case PT_NULL: - case PT_NOTE: - case PT_PHDR: - case PT_STACK: - /* Ignore this segment. */ - break; - case PT_DYNAMIC: - case PT_INTERP: - case PT_SHLIB: - /* Reject the executable. */ - LOAD_ERROR (("unsupported ELF segment type %d\n", phdr.p_type)); - break; - default: - printf ("unknown ELF segment type %08x\n", phdr.p_type); - break; - case PT_LOAD: - if (!load_segment (file, &phdr)) - 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. */ - file_close (file); - return success; -} - -/* Destroys the user address space in T and frees all of its - resources. */ -void -addrspace_destroy (struct thread *t) -{ - ASSERT (t != thread_current ()); - - if (t->pagedir != NULL) - { - pagedir_destroy (t->pagedir); - t->pagedir = NULL; - } -} - -/* Sets up the CPU for running user code in the current - thread. */ -void -addrspace_activate (void) -{ - struct thread *t = thread_current (); - - /* Activate T's page tables. */ - pagedir_activate (t->pagedir); - - /* Set T's kernel stack for use in processing interrupts. */ - tss_set_esp0 ((uint8_t *) t + PGSIZE); -} - -/* addrspace_load() helpers. */ - -static bool install_page (void *upage, void *kpage); - -/* Loads the segment described by PHDR from FILE into user - address space. Return true if successful, false otherwise. */ -static bool -load_segment (struct file *file, const struct Elf32_Phdr *phdr) -{ - void *start, *end; /* Page-rounded segment start and end. */ - uint8_t *upage; /* Iterator from start to end. */ - off_t filesz_left; /* Bytes left of file data (as opposed to - zero-initialized bytes). */ - - /* Is this a read-only segment? Not currently used, so it's - commented out. You'll want to use it when implementing VM - to decide whether to page the segment from its executable or - from swap. */ - //bool read_only = (phdr->p_flags & PF_W) == 0; - - ASSERT (file != NULL); - ASSERT (phdr != NULL); - ASSERT (phdr->p_type == PT_LOAD); - - /* [ELF1] 2-2 says that p_offset and p_vaddr must be congruent - modulo PGSIZE. */ - if (phdr->p_offset % PGSIZE != phdr->p_vaddr % PGSIZE) - { - printf ("%#08"PE32Ox" and %#08"PE32Ax" not congruent modulo %#x\n", - phdr->p_offset, phdr->p_vaddr, (unsigned) PGSIZE); - return false; - } - - /* [ELF1] 2-3 says that p_memsz must be at least as big as - p_filesz. */ - if (phdr->p_memsz < phdr->p_filesz) - { - printf ("p_memsz (%08"PE32Wx") < p_filesz (%08"PE32Wx")\n", - phdr->p_memsz, phdr->p_filesz); - return false; - } - - /* Validate virtual memory region to be mapped. - The region must both start and end within the user address - space range starting at 0 and ending at PHYS_BASE (typically - 3 GB == 0xc0000000). */ - start = pg_round_down ((void *) phdr->p_vaddr); - end = pg_round_up ((void *) (phdr->p_vaddr + phdr->p_memsz)); - if (start >= PHYS_BASE || end >= PHYS_BASE || end < start) - { - printf ("bad virtual region %08lx...%08lx\n", - (unsigned long) start, (unsigned long) end); - return false; - } - - /* Load the segment page-by-page into memory. */ - filesz_left = phdr->p_filesz + (phdr->p_vaddr & PGMASK); - file_seek (file, ROUND_DOWN (phdr->p_offset, PGSIZE)); - for (upage = start; upage < (uint8_t *) end; upage += PGSIZE) - { - /* We want to read min(PGSIZE, filesz_left) bytes from the - file into the page and zero the rest. */ - size_t read_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; - size_t zero_bytes = PGSIZE - read_bytes; - uint8_t *kpage = palloc_get (PAL_USER); - if (kpage == NULL) - return false; - - /* Do the reading and zeroing. */ - if (file_read (file, kpage, read_bytes) != (int) read_bytes) - { - palloc_free (kpage); - return false; - } - memset (kpage + read_bytes, 0, zero_bytes); - filesz_left -= read_bytes; - - /* Add the page to the process's address space. */ - if (!install_page (upage, kpage)) - { - palloc_free (kpage); - return false; - } - } - - 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; - - kpage = palloc_get (PAL_USER | PAL_ZERO); - if (kpage != NULL) - { - success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage); - if (success) - *esp = PHYS_BASE; - else - palloc_free (kpage); - } - else - printf ("failed to allocate process stack\n"); - - return success; -} - -/* Adds a mapping from user virtual address UPAGE to kernel - virtual address KPAGE to the page table. Fails if UPAGE is - already mapped or if memory allocation fails. */ -static bool -install_page (void *upage, void *kpage) -{ - 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, true)); -} diff --git a/src/userprog/addrspace.h b/src/userprog/addrspace.h deleted file mode 100644 index 6e3b584..0000000 --- a/src/userprog/addrspace.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef USERPROG_ADDRSPACE_H -#define USERPROG_ADDRSPACE_H - -#include "threads/thread.h" - -tid_t addrspace_execute (const char *filename); -void addrspace_destroy (struct thread *); -void addrspace_activate (void); - -#endif /* userprog/addrspace.h */ diff --git a/src/userprog/process.c b/src/userprog/process.c new file mode 100644 index 0000000..08e78c6 --- /dev/null +++ b/src/userprog/process.c @@ -0,0 +1,401 @@ +#include "userprog/process.h" +#include +#include +#include +#include +#include +#include "userprog/gdt.h" +#include "userprog/pagedir.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/mmu.h" +#include "threads/palloc.h" +#include "threads/thread.h" + +static thread_func execute_thread 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 before + process_execute() returns.*/ +tid_t +process_execute (const char *filename) +{ + char *fn_copy; + tid_t tid; + + /* Make a copy of FILENAME. + Otherwise there's a race between the caller and load(). */ + fn_copy = palloc_get (0); + if (fn_copy == NULL) + return TID_ERROR; + strlcpy (fn_copy, filename, PGSIZE); + + /* Create a new thread to execute FILENAME. */ + tid = thread_create (filename, PRI_DEFAULT, execute_thread, fn_copy); + if (tid == TID_ERROR) + palloc_free (fn_copy); + return tid; +} + +/* A thread function that loads a user process and starts it + running. */ +static void +execute_thread (void *filename_) +{ + char *filename = filename_; + struct intr_frame if_; + bool success; + + /* Initialize interrupt frame and load executable. */ + memset (&if_, 0, sizeof if_); + if_.es = SEL_UDSEG; + if_.ds = SEL_UDSEG; + if_.cs = SEL_UCSEG; + if_.eflags = FLAG_IF | FLAG_MBS; + if_.ss = SEL_UDSEG; + success = load (filename, &if_.eip, &if_.esp); + + /* If load failed, quit. */ + palloc_free (filename); + if (!success) + thread_exit (); + + /* Switch page tables. */ + process_activate (); + + /* Start the user process by simulating a return from an + interrupt, implemented by intr_exit (in + threads/intr-stubs.pl). 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 ("mov %0, %%esp\n" + "jmp intr_exit\n" + : /* no outputs */ + : "g" (&if_)); + NOT_REACHED (); +} + +/* Destroys the user address space in T and frees all of its + resources. */ +void +process_destroy (struct thread *t) +{ + ASSERT (t != thread_current ()); + + if (t->pagedir != NULL) + { + pagedir_destroy (t->pagedir); + t->pagedir = NULL; + } +} + +/* Sets up the CPU for running user code in the current + thread. */ +void +process_activate (void) +{ + struct thread *t = thread_current (); + + /* Activate T's page tables. */ + pagedir_activate (t->pagedir); + + /* Set T's kernel stack for use in processing interrupts. */ + tss_set_esp0 ((uint8_t *) t + PGSIZE); +} + +/* 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 load_segment (struct file *, const struct Elf32_Phdr *); +static bool setup_stack (void **esp); + +/* Aborts loading an executable, with an error message. */ +#define LOAD_ERROR(MSG) \ + do { \ + printf ("load: %s: ", filename); \ + printf MSG; \ + printf ("\n"); \ + goto done; \ + } while (0) + +/* Loads an ELF executable from FILENAME 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 *filename, 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; + + /* Allocate page directory. */ + t->pagedir = pagedir_create (); + if (t->pagedir == NULL) + LOAD_ERROR (("page directory allocation failed")); + + /* Open executable file. */ + file = filesys_open (filename); + if (file == NULL) + LOAD_ERROR (("open failed")); + + /* Read and verify executable header. */ + if (file_read (file, &ehdr, sizeof ehdr) != sizeof ehdr) + LOAD_ERROR (("error reading executable header")); + if (memcmp (ehdr.e_ident, "\177ELF\1\1\1", 7) != 0) + LOAD_ERROR (("file is not ELF")); + if (ehdr.e_type != 2) + LOAD_ERROR (("ELF file is not an executable")); + if (ehdr.e_machine != 3) + LOAD_ERROR (("ELF executable is not x86")); + if (ehdr.e_version != 1) + LOAD_ERROR (("ELF executable hasunknown version %d", + (int) ehdr.e_version)); + if (ehdr.e_phentsize != sizeof (struct Elf32_Phdr)) + LOAD_ERROR (("bad ELF program header size")); + if (ehdr.e_phnum > 1024) + LOAD_ERROR (("too many ELF program headers")); + + /* Read program headers. */ + file_ofs = ehdr.e_phoff; + for (i = 0; i < ehdr.e_phnum; i++) + { + struct Elf32_Phdr phdr; + + file_seek (file, file_ofs); + if (file_read (file, &phdr, sizeof phdr) != sizeof phdr) + LOAD_ERROR (("error reading program header")); + file_ofs += sizeof phdr; + switch (phdr.p_type) + { + case PT_NULL: + case PT_NOTE: + case PT_PHDR: + case PT_STACK: + /* Ignore this segment. */ + break; + case PT_DYNAMIC: + case PT_INTERP: + case PT_SHLIB: + /* Reject the executable. */ + LOAD_ERROR (("unsupported ELF segment type %d\n", phdr.p_type)); + break; + default: + printf ("unknown ELF segment type %08x\n", phdr.p_type); + break; + case PT_LOAD: + if (!load_segment (file, &phdr)) + 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. */ + file_close (file); + return success; +} + +/* load() helpers. */ + +static bool install_page (void *upage, void *kpage); + +/* Loads the segment described by PHDR from FILE into user + address space. Return true if successful, false otherwise. */ +static bool +load_segment (struct file *file, const struct Elf32_Phdr *phdr) +{ + void *start, *end; /* Page-rounded segment start and end. */ + uint8_t *upage; /* Iterator from start to end. */ + off_t filesz_left; /* Bytes left of file data (as opposed to + zero-initialized bytes). */ + + /* Is this a read-only segment? Not currently used, so it's + commented out. You'll want to use it when implementing VM + to decide whether to page the segment from its executable or + from swap. */ + //bool read_only = (phdr->p_flags & PF_W) == 0; + + ASSERT (file != NULL); + ASSERT (phdr != NULL); + ASSERT (phdr->p_type == PT_LOAD); + + /* [ELF1] 2-2 says that p_offset and p_vaddr must be congruent + modulo PGSIZE. */ + if (phdr->p_offset % PGSIZE != phdr->p_vaddr % PGSIZE) + { + printf ("%#08"PE32Ox" and %#08"PE32Ax" not congruent modulo %#x\n", + phdr->p_offset, phdr->p_vaddr, (unsigned) PGSIZE); + return false; + } + + /* [ELF1] 2-3 says that p_memsz must be at least as big as + p_filesz. */ + if (phdr->p_memsz < phdr->p_filesz) + { + printf ("p_memsz (%08"PE32Wx") < p_filesz (%08"PE32Wx")\n", + phdr->p_memsz, phdr->p_filesz); + return false; + } + + /* Validate virtual memory region to be mapped. + The region must both start and end within the user address + space range starting at 0 and ending at PHYS_BASE (typically + 3 GB == 0xc0000000). */ + start = pg_round_down ((void *) phdr->p_vaddr); + end = pg_round_up ((void *) (phdr->p_vaddr + phdr->p_memsz)); + if (start >= PHYS_BASE || end >= PHYS_BASE || end < start) + { + printf ("bad virtual region %08lx...%08lx\n", + (unsigned long) start, (unsigned long) end); + return false; + } + + /* Load the segment page-by-page into memory. */ + filesz_left = phdr->p_filesz + (phdr->p_vaddr & PGMASK); + file_seek (file, ROUND_DOWN (phdr->p_offset, PGSIZE)); + for (upage = start; upage < (uint8_t *) end; upage += PGSIZE) + { + /* We want to read min(PGSIZE, filesz_left) bytes from the + file into the page and zero the rest. */ + size_t read_bytes = filesz_left >= PGSIZE ? PGSIZE : filesz_left; + size_t zero_bytes = PGSIZE - read_bytes; + uint8_t *kpage = palloc_get (PAL_USER); + if (kpage == NULL) + return false; + + /* Do the reading and zeroing. */ + if (file_read (file, kpage, read_bytes) != (int) read_bytes) + { + palloc_free (kpage); + return false; + } + memset (kpage + read_bytes, 0, zero_bytes); + filesz_left -= read_bytes; + + /* Add the page to the process's address space. */ + if (!install_page (upage, kpage)) + { + palloc_free (kpage); + return false; + } + } + + 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; + + kpage = palloc_get (PAL_USER | PAL_ZERO); + if (kpage != NULL) + { + success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage); + if (success) + *esp = PHYS_BASE; + else + palloc_free (kpage); + } + else + printf ("failed to allocate process stack\n"); + + return success; +} + +/* Adds a mapping from user virtual address UPAGE to kernel + virtual address KPAGE to the page table. Fails if UPAGE is + already mapped or if memory allocation fails. */ +static bool +install_page (void *upage, void *kpage) +{ + 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, true)); +} diff --git a/src/userprog/process.h b/src/userprog/process.h new file mode 100644 index 0000000..cd9463d --- /dev/null +++ b/src/userprog/process.h @@ -0,0 +1,10 @@ +#ifndef USERPROG_PROCESS_H +#define USERPROG_PROCESS_H + +#include "threads/thread.h" + +tid_t process_execute (const char *filename); +void process_destroy (struct thread *); +void process_activate (void); + +#endif /* userprog/process.h */