diff options
Diffstat (limited to 'src/userprog')
| -rw-r--r-- | src/userprog/.cvsignore | 3 | ||||
| -rw-r--r-- | src/userprog/Make.vars | 8 | ||||
| -rw-r--r-- | src/userprog/Makefile | 1 | ||||
| -rw-r--r-- | src/userprog/bochsrc.txt | 13 | ||||
| -rw-r--r-- | src/userprog/exception.c | 161 | ||||
| -rw-r--r-- | src/userprog/exception.h | 12 | ||||
| -rw-r--r-- | src/userprog/flist.c | 4 | ||||
| -rw-r--r-- | src/userprog/flist.h | 35 | ||||
| -rw-r--r-- | src/userprog/gdt.c | 146 | ||||
| -rw-r--r-- | src/userprog/gdt.h | 15 | ||||
| -rw-r--r-- | src/userprog/load.c | 381 | ||||
| -rw-r--r-- | src/userprog/load.h | 7 | ||||
| -rw-r--r-- | src/userprog/pagedir.c | 263 | ||||
| -rw-r--r-- | src/userprog/pagedir.h | 18 | ||||
| -rw-r--r-- | src/userprog/plist.c | 3 | ||||
| -rw-r--r-- | src/userprog/plist.h | 33 | ||||
| -rw-r--r-- | src/userprog/process.c | 275 | ||||
| -rw-r--r-- | src/userprog/process.h | 23 | ||||
| -rw-r--r-- | src/userprog/syscall.c | 61 | ||||
| -rw-r--r-- | src/userprog/syscall.h | 6 | ||||
| -rw-r--r-- | src/userprog/test.c | 66 | ||||
| -rw-r--r-- | src/userprog/tss.c | 106 | ||||
| -rw-r--r-- | src/userprog/tss.h | 11 |
23 files changed, 1651 insertions, 0 deletions
diff --git a/src/userprog/.cvsignore b/src/userprog/.cvsignore new file mode 100644 index 0000000..6d5357c --- /dev/null +++ b/src/userprog/.cvsignore @@ -0,0 +1,3 @@ +build +bochsrc.txt +bochsout.txt diff --git a/src/userprog/Make.vars b/src/userprog/Make.vars new file mode 100644 index 0000000..4335438 --- /dev/null +++ b/src/userprog/Make.vars @@ -0,0 +1,8 @@ +# -*- makefile -*- + +os.dsk: DEFINES += -DUSERPROG -DFILESYS +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +TEST_SUBDIRS = tests/klaar tests/userprog tests/filesys/base +# tests/userprog/no-vm +GRADING_FILE = $(SRCDIR)/tests/userprog/Grading +SIMULATOR = --qemu diff --git a/src/userprog/Makefile b/src/userprog/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/src/userprog/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/src/userprog/bochsrc.txt b/src/userprog/bochsrc.txt new file mode 100644 index 0000000..ea21837 --- /dev/null +++ b/src/userprog/bochsrc.txt @@ -0,0 +1,13 @@ +romimage: file=$BXSHARE/BIOS-bochs-latest, address=0xf0000 +vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest +boot: disk +cpu: ips=1000000 +megs: 4 +log: bochsout.txt +panic: action=fatal +clock: sync=none, time0=0 +ata0-master: type=disk, path=/tmp/DcPkTwrKiK.dsk, mode=flat, cylinders=1, heads=16, spt=63, translation=none +ata0-slave: type=disk, path=fs.dsk, mode=flat, cylinders=4, heads=16, spt=63, translation=none +ata1: enabled=1, ioaddr1=0x170, ioaddr2=0x370, irq=15 +ata1-master: type=disk, path=/tmp/GFsmC9D5kP.dsk, mode=flat, cylinders=1, heads=16, spt=63, translation=none +com1: enabled=1, mode=term, dev=/dev/stdout diff --git a/src/userprog/exception.c b/src/userprog/exception.c new file mode 100644 index 0000000..3865dc2 --- /dev/null +++ b/src/userprog/exception.c @@ -0,0 +1,161 @@ +#include "userprog/exception.h" +#include <inttypes.h> +#include <stdio.h> +#include "userprog/gdt.h" +#include "threads/interrupt.h" +#include "threads/thread.h" + +/* Number of page faults processed. */ +static long long page_fault_cnt; + +static void kill (struct intr_frame *); +static void page_fault (struct intr_frame *); + +/* Registers handlers for interrupts that can be caused by user + programs. + + In a real Unix-like OS, most of these interrupts would be + passed along to the user process in the form of signals, as + described in [SV-386] 3-24 and 3-25, but we don't implement + signals. Instead, we'll make them simply kill the user + process. + + Page faults are an exception. Here they are treated the same + way as other exceptions, but this will need to change to + implement virtual memory. + + Refer to [IA32-v3a] section 5.15 "Exception and Interrupt + Reference" for a description of each of these exceptions. */ +void +exception_init (void) +{ + /* These exceptions can be raised explicitly by a user program, + e.g. via the INT, INT3, INTO, and BOUND instructions. Thus, + we set DPL==3, meaning that user programs are allowed to + invoke them via these instructions. */ + intr_register_int (3, 3, INTR_ON, kill, "#BP Breakpoint Exception"); + intr_register_int (4, 3, INTR_ON, kill, "#OF Overflow Exception"); + intr_register_int (5, 3, INTR_ON, kill, + "#BR BOUND Range Exceeded Exception"); + + /* These exceptions have DPL==0, preventing user processes from + invoking them via the INT instruction. They can still be + caused indirectly, e.g. #DE can be caused by dividing by + 0. */ + intr_register_int (0, 0, INTR_ON, kill, "#DE Divide Error"); + intr_register_int (1, 0, INTR_ON, kill, "#DB Debug Exception"); + intr_register_int (6, 0, INTR_ON, kill, "#UD Invalid Opcode Exception"); + intr_register_int (7, 0, INTR_ON, kill, + "#NM Device Not Available Exception"); + intr_register_int (11, 0, INTR_ON, kill, "#NP Segment Not Present"); + intr_register_int (12, 0, INTR_ON, kill, "#SS Stack Fault Exception"); + intr_register_int (13, 0, INTR_ON, kill, "#GP General Protection Exception"); + intr_register_int (16, 0, INTR_ON, kill, "#MF x87 FPU Floating-Point Error"); + intr_register_int (19, 0, INTR_ON, kill, + "#XF SIMD Floating-Point Exception"); + + /* Most exceptions can be handled with interrupts turned on. + We need to disable interrupts for page faults because the + fault address is stored in CR2 and needs to be preserved. */ + intr_register_int (14, 0, INTR_OFF, page_fault, "#PF Page-Fault Exception"); +} + +/* Prints exception statistics. */ +void +exception_print_stats (void) +{ + printf ("Exception: %lld page faults\n", page_fault_cnt); +} + +/* Handler for an exception (probably) caused by a user process. */ +static void +kill (struct intr_frame *f) +{ + /* This interrupt is one (probably) caused by a user process. + For example, the process might have tried to access unmapped + virtual memory (a page fault). For now, we simply kill the + user process. Later, we'll want to handle page faults in + the kernel. Real Unix-like operating systems pass most + exceptions back to the process via signals, but we don't + implement them. */ + + /* The interrupt frame's code segment value tells us where the + exception originated. */ + switch (f->cs) + { + case SEL_UCSEG: + /* User's code segment, so it's a user exception, as we + expected. Kill the user process. */ + printf ("%s: dying due to interrupt %#04x (%s).\n", + thread_name (), f->vec_no, intr_name (f->vec_no)); + intr_dump_frame (f); + thread_exit (); + + case SEL_KCSEG: + /* Kernel's code segment, which indicates a kernel bug. + Kernel code shouldn't throw exceptions. (Page faults + may cause kernel exceptions--but they shouldn't arrive + here.) Panic the kernel to make the point. */ + intr_dump_frame (f); + PANIC ("Kernel bug - unexpected interrupt in kernel"); + + default: + /* Some other code segment? Shouldn't happen. Panic the + kernel. */ + printf ("Interrupt %#04x (%s) in unknown segment %04x\n", + f->vec_no, intr_name (f->vec_no), f->cs); + thread_exit (); + } +} + +/* Page fault handler. This is a skeleton that must be filled in to + implement virtual memory. Some solutions to system call + implementation may also require modifying this code. + + At entry, the address that faulted is in CR2 (Control Register + 2) and information about the fault, formatted as described in + the PF_* macros in exception.h, is in F's error_code member. The + example code here shows how to parse that information. You + can find more information about both of these in the + description of "Interrupt 14--Page Fault Exception (#PF)" in + [IA32-v3a] section 5.15 "Exception and Interrupt Reference". */ +static void +page_fault (struct intr_frame *f) +{ + bool not_present; /* True: not-present page, false: writing r/o page. */ + bool write; /* True: access was write, false: access was read. */ + bool user; /* True: access by user, false: access by kernel. */ + void *fault_addr; /* Fault address. */ + + /* Obtain faulting address, the virtual address that was + accessed to cause the fault. It may point to code or to + data. It is not necessarily the address of the instruction + that caused the fault (that's f->eip). + See [IA32-v2a] "MOV--Move to/from Control Registers" and + [IA32-v3a] 5.15 "Interrupt 14--Page Fault Exception + (#PF)". */ + asm ("movl %%cr2, %0" : "=r" (fault_addr)); + + /* Turn interrupts back on (they were only off so that we could + be assured of reading CR2 before it changed). */ + intr_enable (); + + /* Count page faults. */ + page_fault_cnt++; + + /* Determine cause. */ + not_present = (f->error_code & PF_P) == 0; + write = (f->error_code & PF_W) != 0; + user = (f->error_code & PF_U) != 0; + + /* To implement virtual memory, delete the rest of the function + body, and replace it with code that brings in the page to + which fault_addr refers. */ + printf ("Page fault at %p: %s error %s page in %s context.\n", + fault_addr, + not_present ? "not present" : "rights violation", + write ? "writing" : "reading", + user ? "user" : "kernel"); + kill (f); +} + diff --git a/src/userprog/exception.h b/src/userprog/exception.h new file mode 100644 index 0000000..f83e615 --- /dev/null +++ b/src/userprog/exception.h @@ -0,0 +1,12 @@ +#ifndef USERPROG_EXCEPTION_H +#define USERPROG_EXCEPTION_H + +/* Page fault error code bits that describe the cause of the exception. */ +#define PF_P 0x1 /* 0: not-present page. 1: access rights violation. */ +#define PF_W 0x2 /* 0: read, 1: write. */ +#define PF_U 0x4 /* 0: kernel, 1: user process. */ + +void exception_init (void); +void exception_print_stats (void); + +#endif /* userprog/exception.h */ diff --git a/src/userprog/flist.c b/src/userprog/flist.c new file mode 100644 index 0000000..e88a087 --- /dev/null +++ b/src/userprog/flist.c @@ -0,0 +1,4 @@ +#include <stddef.h> + +#include "flist.h" + diff --git a/src/userprog/flist.h b/src/userprog/flist.h new file mode 100644 index 0000000..3fcd1d5 --- /dev/null +++ b/src/userprog/flist.h @@ -0,0 +1,35 @@ +#ifndef _MAP_H_ +#define _MAP_H_ + +/* Place functions to handle a process open files here (file list). + + flist.h : Your function declarations and documentation. + flist.c : Your implementation. + + The following is strongly recommended: + + - A function that given a file (struct file*, see filesys/file.h) + and a process id INSERT this in a list of files. Return an + integer that can be used to find the opened file later. + + - A function that given an integer (obtained from above function) + and a process id FIND the file in a list. Should return NULL if + the specified process did not insert the file or already removed + it. + + - A function that given an integer (obtained from above function) + and a process id REMOVE the file from a list. Should return NULL + if the specified process did not insert the file or already + removed it. + + - A function that given a process id REMOVE ALL files the specified + process have in the list. + + All files obtained from filesys/filesys.c:filesys_open() are + considered OPEN files and must be added to a list or else kept + track of, to guarantee ALL open files are eventyally CLOSED + (probably when removed from the list(s)). + */ + + +#endif diff --git a/src/userprog/gdt.c b/src/userprog/gdt.c new file mode 100644 index 0000000..a7423b7 --- /dev/null +++ b/src/userprog/gdt.c @@ -0,0 +1,146 @@ +#include "userprog/gdt.h" +#include <debug.h> +#include "userprog/tss.h" +#include "threads/palloc.h" +#include "threads/vaddr.h" + +/* The Global Descriptor Table (GDT). + + The GDT, an x86-specific structure, defines segments that can + potentially be used by all processes in a system, subject to + their permissions. There is also a per-process Local + Descriptor Table (LDT) but that is not used by modern + operating systems. + + Each entry in the GDT, which is known by its byte offset in + the table, identifies a segment. For our purposes only three + types of segments are of interest: code, data, and TSS or + Task-State Segment descriptors. The former two types are + exactly what they sound like. The TSS is used primarily for + stack switching on interrupts. + + For more information on the GDT as used here, refer to + [IA32-v3a] 3.2 "Using Segments" through 3.5 "System Descriptor + Types". */ +static uint64_t gdt[SEL_CNT]; + +/* GDT helpers. */ +static uint64_t make_code_desc (int dpl); +static uint64_t make_data_desc (int dpl); +static uint64_t make_tss_desc (void *laddr); +static uint64_t make_gdtr_operand (uint16_t limit, void *base); + +/* Sets up a proper GDT. The bootstrap loader's GDT didn't + include user-mode selectors or a TSS, but we need both now. */ +void +gdt_init (void) +{ + uint64_t gdtr_operand; + + /* Initialize GDT. */ + gdt[SEL_NULL / sizeof *gdt] = 0; + gdt[SEL_KCSEG / sizeof *gdt] = make_code_desc (0); + gdt[SEL_KDSEG / sizeof *gdt] = make_data_desc (0); + gdt[SEL_UCSEG / sizeof *gdt] = make_code_desc (3); + gdt[SEL_UDSEG / sizeof *gdt] = make_data_desc (3); + gdt[SEL_TSS / sizeof *gdt] = make_tss_desc (tss_get ()); + + /* Load GDTR, TR. See [IA32-v3a] 2.4.1 "Global Descriptor + Table Register (GDTR)", 2.4.4 "Task Register (TR)", and + 6.2.4 "Task Register". */ + gdtr_operand = make_gdtr_operand (sizeof gdt - 1, gdt); + asm volatile ("lgdt %0" : : "m" (gdtr_operand)); + asm volatile ("ltr %w0" : : "r" (SEL_TSS)); +} + +/* System segment or code/data segment? */ +enum seg_class + { + CLS_SYSTEM = 0, /* System segment. */ + CLS_CODE_DATA = 1 /* Code or data segment. */ + }; + +/* Limit has byte or 4 kB page granularity? */ +enum seg_granularity + { + GRAN_BYTE = 0, /* Limit has 1-byte granularity. */ + GRAN_PAGE = 1 /* Limit has 4 kB granularity. */ + }; + +/* Returns a segment descriptor with the given 32-bit BASE and + 20-bit LIMIT (whose interpretation depends on GRANULARITY). + The descriptor represents a system or code/data segment + according to CLASS, and TYPE is its type (whose interpretation + depends on the class). + + The segment has descriptor privilege level DPL, meaning that + it can be used in rings numbered DPL or lower. In practice, + DPL==3 means that user processes can use the segment and + DPL==0 means that only the kernel can use the segment. See + [IA32-v3a] 4.5 "Privilege Levels" for further discussion. */ +static uint64_t +make_seg_desc (uint32_t base, + uint32_t limit, + enum seg_class class, + int type, + int dpl, + enum seg_granularity granularity) +{ + uint32_t e0, e1; + + ASSERT (limit <= 0xfffff); + ASSERT (class == CLS_SYSTEM || class == CLS_CODE_DATA); + ASSERT (type >= 0 && type <= 15); + ASSERT (dpl >= 0 && dpl <= 3); + ASSERT (granularity == GRAN_BYTE || granularity == GRAN_PAGE); + + e0 = ((limit & 0xffff) /* Limit 15:0. */ + | (base << 16)); /* Base 15:0. */ + + e1 = (((base >> 16) & 0xff) /* Base 23:16. */ + | (type << 8) /* Segment type. */ + | (class << 12) /* 0=system, 1=code/data. */ + | (dpl << 13) /* Descriptor privilege. */ + | (1 << 15) /* Present. */ + | (limit & 0xf0000) /* Limit 16:19. */ + | (1 << 22) /* 32-bit segment. */ + | (granularity << 23) /* Byte/page granularity. */ + | (base & 0xff000000)); /* Base 31:24. */ + + return e0 | ((uint64_t) e1 << 32); +} + +/* Returns a descriptor for a readable code segment with base at + 0, a limit of 4 GB, and the given DPL. */ +static uint64_t +make_code_desc (int dpl) +{ + return make_seg_desc (0, 0xfffff, CLS_CODE_DATA, 10, dpl, GRAN_PAGE); +} + +/* Returns a descriptor for a writable data segment with base at + 0, a limit of 4 GB, and the given DPL. */ +static uint64_t +make_data_desc (int dpl) +{ + return make_seg_desc (0, 0xfffff, CLS_CODE_DATA, 2, dpl, GRAN_PAGE); +} + +/* Returns a descriptor for an "available" 32-bit Task-State + Segment with its base at the given linear address, a limit of + 0x67 bytes (the size of a 32-bit TSS), and a DPL of 0. + See [IA32-v3a] 6.2.2 "TSS Descriptor". */ +static uint64_t +make_tss_desc (void *laddr) +{ + return make_seg_desc ((uint32_t) laddr, 0x67, CLS_SYSTEM, 9, 0, GRAN_BYTE); +} + + +/* Returns a descriptor that yields the given LIMIT and BASE when + used as an operand for the LGDT instruction. */ +static uint64_t +make_gdtr_operand (uint16_t limit, void *base) +{ + return limit | ((uint64_t) (uint32_t) base << 16); +} diff --git a/src/userprog/gdt.h b/src/userprog/gdt.h new file mode 100644 index 0000000..81fe50c --- /dev/null +++ b/src/userprog/gdt.h @@ -0,0 +1,15 @@ +#ifndef USERPROG_GDT_H +#define USERPROG_GDT_H + +#include "threads/loader.h" + +/* Segment selectors. + More selectors are defined by the loader in loader.h. */ +#define SEL_UCSEG 0x1B /* User code selector. */ +#define SEL_UDSEG 0x23 /* User data selector. */ +#define SEL_TSS 0x28 /* Task-state segment. */ +#define SEL_CNT 6 /* Number of segments. */ + +void gdt_init (void); + +#endif /* userprog/gdt.h */ diff --git a/src/userprog/load.c b/src/userprog/load.c new file mode 100644 index 0000000..eabc056 --- /dev/null +++ b/src/userprog/load.c @@ -0,0 +1,381 @@ +#include <inttypes.h> /* Elf32_word etc. */ +#include <round.h> /* ROUND_UP */ +#include <stdio.h> +#include <string.h> /* memcmp */ + +#include "userprog/process.h" +#include "userprog/load.h" +#include "userprog/pagedir.h" +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "threads/palloc.h" /* PAL_* constants */ +#include "threads/thread.h" +#include "threads/vaddr.h" /* 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 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; + + /* Allocate and activate page directory. */ + t->pagedir = pagedir_create (); + if (t->pagedir == NULL) + goto done; + process_activate (); + + /* Set up stack. */ + if (!setup_stack (esp)){ + goto done; + } + + /* 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; + } + } + + /* 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, bool writable); + +/* 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); + + file_seek (file, ofs); + 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; + + /* Get a page of memory. */ + uint8_t *kpage = palloc_get_page (PAL_USER); + if (kpage == NULL) + return false; + + /* Load this page. */ + if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes) + { + palloc_free_page (kpage); + return false; + } + memset (kpage + page_read_bytes, 0, page_zero_bytes); + + /* Add the page to the process's address space. */ + if (!install_page (upage, kpage, writable)) + { + palloc_free_page (kpage); + return false; + } + + /* Advance. */ + read_bytes -= page_read_bytes; + zero_bytes -= page_zero_bytes; + 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; + + kpage = palloc_get_page (PAL_USER | PAL_ZERO); + if (kpage != NULL) + { + success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage, true); + if (success) + *esp = PHYS_BASE; + else + palloc_free_page (kpage); + } + return success; +} + +/* 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. */ +static 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)); +} + +/* A function that dumps 'size' bytes of memory starting at 'ptr' + * it will dump the higher adress first letting the stack grow down. + */ +void dump_stack(void* ptr, int size) +{ + unsigned from = (unsigned)ptr; + unsigned to = (unsigned)(ptr - size); + unsigned adr; + + printf("# Adress \thex-data \tchar-data\n"); + + for ( adr = from; adr > to; --adr ) + { + unsigned* uadr = (unsigned*)( adr ); + unsigned char* byte = (unsigned char*)( adr ); + + printf("# %08x\t", adr); /* address */ + + if ( (adr % 4) == 0 ) + printf("%08x\t", *uadr); /* content interpreted as address */ + else + printf(" \t"); /* fill */ + + if ( *byte >= 32 && *byte < 127 ) + printf("%c\n", *byte); /* content interpreted as character */ + else + printf("\\0%o\n", *byte); /* octal character code */ + + if ( adr == (unsigned)PHYS_BASE ) + printf("# -- ^ KERNEL SPACE ABOVE ^v USER SPACE BELOW v --\n"); + else if ( (adr % 4) == 0 ) + printf("# ------------------------------------------------\n"); + } +} diff --git a/src/userprog/load.h b/src/userprog/load.h new file mode 100644 index 0000000..b0978c9 --- /dev/null +++ b/src/userprog/load.h @@ -0,0 +1,7 @@ +#ifndef USERPROG_LOAD_H +#define USERPROG_LOAD_H + +bool load (const char *file_name, void (**eip) (void), void **esp); +void dump_stack(void* ptr, int size); + +#endif /* userprog/load.h */ diff --git a/src/userprog/pagedir.c b/src/userprog/pagedir.c new file mode 100644 index 0000000..30bdfe2 --- /dev/null +++ b/src/userprog/pagedir.c @@ -0,0 +1,263 @@ +#include "userprog/pagedir.h" +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include "threads/init.h" +#include "threads/pte.h" +#include "threads/palloc.h" + +static uint32_t *active_pd (void); +static void invalidate_pagedir (uint32_t *); + +/* Creates a new page directory that has mappings for kernel + virtual addresses, but none for user virtual addresses. + Returns the new page directory, or a null pointer if memory + allocation fails. */ +uint32_t * +pagedir_create (void) +{ + uint32_t *pd = palloc_get_page (0); + if (pd != NULL) + memcpy (pd, base_page_dir, PGSIZE); + return pd; +} + +/* Destroys page directory PD, freeing all the pages it + references. */ +void +pagedir_destroy (uint32_t *pd) +{ + uint32_t *pde; + + if (pd == NULL) + return; + + ASSERT (pd != base_page_dir); + for (pde = pd; pde < pd + pd_no (PHYS_BASE); pde++) + if (*pde & PTE_P) + { + uint32_t *pt = pde_get_pt (*pde); + uint32_t *pte; + + for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++) + if (*pte & PTE_P) + palloc_free_page (pte_get_page (*pte)); + palloc_free_page (pt); + } + palloc_free_page (pd); +} + +/* Returns the address of the page table entry for virtual + address VADDR in page directory PD. + If PD does not have a page table for VADDR, behavior depends + on CREATE. If CREATE is true, then a new page table is + created and a pointer into it is returned. Otherwise, a null + pointer is returned. */ +static uint32_t * +lookup_page (uint32_t *pd, const void *vaddr, bool create) +{ + uint32_t *pt, *pde; + + ASSERT (pd != NULL); + + /* Shouldn't create new kernel virtual mappings. */ + ASSERT (!create || is_user_vaddr (vaddr)); + + /* Check for a page table for VADDR. + If one is missing, create one if requested. */ + pde = pd + pd_no (vaddr); + if (*pde == 0) + { + if (create) + { + pt = palloc_get_page (PAL_ZERO); + if (pt == NULL) + return NULL; + + *pde = pde_create (pt); + } + else + return NULL; + } + + /* Return the page table entry. */ + pt = pde_get_pt (*pde); + return &pt[pt_no (vaddr)]; +} + +/* Adds a mapping in page directory PD from user virtual page + UPAGE to the physical frame identified by kernel virtual + address KPAGE. + UPAGE must not already be mapped. + KPAGE should probably be a page obtained from the user pool + with palloc_get_page(). + If WRITABLE is true, the new page is read/write; + otherwise it is read-only. + Returns true if successful, false if memory allocation + failed. */ +bool +pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool writable) +{ + uint32_t *pte; + + ASSERT (pg_ofs (upage) == 0); + ASSERT (pg_ofs (kpage) == 0); + ASSERT (is_user_vaddr (upage)); + ASSERT (vtop (kpage) >> PTSHIFT < ram_pages); + ASSERT (pd != base_page_dir); + + pte = lookup_page (pd, upage, true); + + if (pte != NULL) + { + ASSERT ((*pte & PTE_P) == 0); + *pte = pte_create_user (kpage, writable); + return true; + } + else + return false; +} + +/* Looks up the physical address that corresponds to user virtual + address UADDR in PD. Returns the kernel virtual address + corresponding to that physical address, or a null pointer if + UADDR is unmapped. */ +void * +pagedir_get_page (uint32_t *pd, const void *uaddr) +{ + uint32_t *pte; + + ASSERT (is_user_vaddr (uaddr)); + + pte = lookup_page (pd, uaddr, false); + if (pte != NULL && (*pte & PTE_P) != 0) + return pte_get_page (*pte) + pg_ofs (uaddr); + else + return NULL; +} + +/* Marks user virtual page UPAGE "not present" in page + directory PD. Later accesses to the page will fault. Other + bits in the page table entry are preserved. + UPAGE need not be mapped. */ +void +pagedir_clear_page (uint32_t *pd, void *upage) +{ + uint32_t *pte; + + ASSERT (pg_ofs (upage) == 0); + ASSERT (is_user_vaddr (upage)); + + pte = lookup_page (pd, upage, false); + if (pte != NULL && (*pte & PTE_P) != 0) + { + *pte &= ~PTE_P; + invalidate_pagedir (pd); + } +} + +/* Returns true if the PTE for virtual page VPAGE in PD is dirty, + that is, if the page has been modified since the PTE was + installed. + Returns false if PD contains no PTE for VPAGE. */ +bool +pagedir_is_dirty (uint32_t *pd, const void *vpage) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + return pte != NULL && (*pte & PTE_D) != 0; +} + +/* Set the dirty bit to DIRTY in the PTE for virtual page VPAGE + in PD. */ +void +pagedir_set_dirty (uint32_t *pd, const void *vpage, bool dirty) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + if (pte != NULL) + { + if (dirty) + *pte |= PTE_D; + else + { + *pte &= ~(uint32_t) PTE_D; + invalidate_pagedir (pd); + } + } +} + +/* Returns true if the PTE for virtual page VPAGE in PD has been + accessed recently, that is, between the time the PTE was + installed and the last time it was cleared. Returns false if + PD contains no PTE for VPAGE. */ +bool +pagedir_is_accessed (uint32_t *pd, const void *vpage) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + return pte != NULL && (*pte & PTE_A) != 0; +} + +/* Sets the accessed bit to ACCESSED in the PTE for virtual page + VPAGE in PD. */ +void +pagedir_set_accessed (uint32_t *pd, const void *vpage, bool accessed) +{ + uint32_t *pte = lookup_page (pd, vpage, false); + if (pte != NULL) + { + if (accessed) + *pte |= PTE_A; + else + { + *pte &= ~(uint32_t) PTE_A; + invalidate_pagedir (pd); + } + } +} + +/* Loads page directory PD into the CPU's page directory base + register. */ +void +pagedir_activate (uint32_t *pd) +{ + if (pd == NULL) + pd = base_page_dir; + + /* Store the physical address of the page directory into CR3 + aka PDBR (page directory base register). This activates our + new page tables immediately. See [IA32-v2a] "MOV--Move + to/from Control Registers" and [IA32-v3a] 3.7.5 "Base + Address of the Page Directory". */ + asm volatile ("movl %0, %%cr3" : : "r" (vtop (pd)) : "memory"); +} + +/* Returns the currently active page directory. */ +static uint32_t * +active_pd (void) +{ + /* Copy CR3, the page directory base register (PDBR), into + `pd'. + See [IA32-v2a] "MOV--Move to/from Control Registers" and + [IA32-v3a] 3.7.5 "Base Address of the Page Directory". */ + uintptr_t pd; + asm volatile ("movl %%cr3, %0" : "=r" (pd)); + return ptov (pd); +} + +/* Seom page table changes can cause the CPU's translation + lookaside buffer (TLB) to become out-of-sync with the page + table. When this happens, we have to "invalidate" the TLB by + re-activating it. + + This function invalidates the TLB if PD is the active page + directory. (If PD is not active then its entries are not in + the TLB, so there is no need to invalidate anything.) */ +static void +invalidate_pagedir (uint32_t *pd) +{ + if (active_pd () == pd) + { + /* Re-activating PD clears the TLB. See [IA32-v3a] 3.12 + "Translation Lookaside Buffers (TLBs)". */ + pagedir_activate (pd); + } +} diff --git a/src/userprog/pagedir.h b/src/userprog/pagedir.h new file mode 100644 index 0000000..cd92447 --- /dev/null +++ b/src/userprog/pagedir.h @@ -0,0 +1,18 @@ +#ifndef USERPROG_PAGEDIR_H +#define USERPROG_PAGEDIR_H + +#include <stdbool.h> +#include <stdint.h> + +uint32_t *pagedir_create (void); +void pagedir_destroy (uint32_t *pd); +bool pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool rw); +void *pagedir_get_page (uint32_t *pd, const void *upage); +void pagedir_clear_page (uint32_t *pd, void *upage); +bool pagedir_is_dirty (uint32_t *pd, const void *upage); +void pagedir_set_dirty (uint32_t *pd, const void *upage, bool dirty); +bool pagedir_is_accessed (uint32_t *pd, const void *upage); +void pagedir_set_accessed (uint32_t *pd, const void *upage, bool accessed); +void pagedir_activate (uint32_t *pd); + +#endif /* userprog/pagedir.h */ diff --git a/src/userprog/plist.c b/src/userprog/plist.c new file mode 100644 index 0000000..da03d54 --- /dev/null +++ b/src/userprog/plist.c @@ -0,0 +1,3 @@ +#include <stddef.h> + +#include "plist.h" diff --git a/src/userprog/plist.h b/src/userprog/plist.h new file mode 100644 index 0000000..ce75524 --- /dev/null +++ b/src/userprog/plist.h @@ -0,0 +1,33 @@ +#ifndef _PLIST_H_ +#define _PLIST_H_ + + +/* Place functions to handle a running process here (process list). + + plist.h : Your function declarations and documentation. + plist.c : Your implementation. + + The following is strongly recommended: + + - A function that given process inforamtion (up to you to create) + inserts this in a list of running processes and return an integer + that can be used to find the information later on. + + - A function that given an integer (obtained from above function) + FIND the process information in the list. Should return some + failure code if no process matching the integer is in the list. + Or, optionally, several functions to access any information of a + particular process that you currently need. + + - A function that given an integer REMOVE the process information + from the list. Should only remove the information when no process + or thread need it anymore, but must guarantee it is always + removed EVENTUALLY. + + - A function that print the entire content of the list in a nice, + clean, readable format. + + */ + + +#endif diff --git a/src/userprog/process.c b/src/userprog/process.c new file mode 100644 index 0000000..7df8778 --- /dev/null +++ b/src/userprog/process.c @@ -0,0 +1,275 @@ +#include <debug.h> +#include <stdio.h> +#include <string.h> + +#include "userprog/gdt.h" /* SEL_* constants */ +#include "userprog/process.h" +#include "userprog/load.h" +#include "userprog/pagedir.h" /* pagedir_activate etc. */ +#include "userprog/tss.h" /* tss_update */ +#include "filesys/file.h" +#include "threads/flags.h" /* FLAG_* constants */ +#include "threads/thread.h" +#include "threads/vaddr.h" /* PHYS_BASE */ +#include "threads/interrupt.h" /* if_ */ + +/* Headers not yet used that you may need for various reasons. */ +#include "threads/synch.h" +#include "threads/malloc.h" +#include "lib/kernel/list.h" + +#include "userprog/flist.h" +#include "userprog/plist.h" + +/* HACK defines code you must remove and implement in a proper way */ +#define HACK + + +/* This function is called at boot time (threads/init.c) to initialize + * the process subsystem. */ +void process_init(void) +{ +} + +/* This function is currently never called. As thread_exit does not + * have an exit status parameter, this could be used to handle that + * instead. Note however that all cleanup after a process must be done + * in process_cleanup, and that process_cleanup are already called + * from thread_exit - do not call cleanup twice! */ +void process_exit(int status UNUSED) +{ +} + +/* Print a list of all running processes. The list shall include all + * relevant debug information in a clean, readable format. */ +void process_print_list() +{ +} + + +struct parameters_to_start_process +{ + char* command_line; +}; + +static void +start_process(struct parameters_to_start_process* parameters) NO_RETURN; + +/* Starts a new proccess by creating a new thread to run it. The + process is loaded from the file specified in the COMMAND_LINE and + started with the arguments on the COMMAND_LINE. 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. */ +int +process_execute (const char *command_line) +{ + char debug_name[64]; + int command_line_size = strlen(command_line) + 1; + tid_t thread_id = -1; + int process_id = -1; + + /* LOCAL variable will cease existence when function return! */ + struct parameters_to_start_process arguments; + + debug("%s#%d: process_execute(\"%s\") ENTERED\n", + thread_current()->name, + thread_current()->tid, + command_line); + + /* COPY command line out of parent process memory */ + arguments.command_line = malloc(command_line_size); + strlcpy(arguments.command_line, command_line, command_line_size); + + + strlcpy_first_word (debug_name, command_line, 64); + + /* SCHEDULES function `start_process' to run (LATER) */ + thread_id = thread_create (debug_name, PRI_DEFAULT, + (thread_func*)start_process, &arguments); + + process_id = thread_id; + + /* AVOID bad stuff by turning off. YOU will fix this! */ + power_off(); + + + /* WHICH thread may still be using this right now? */ + free(arguments.command_line); + + debug("%s#%d: process_execute(\"%s\") RETURNS %d\n", + thread_current()->name, + thread_current()->tid, + command_line, process_id); + + /* MUST be -1 if `load' in `start_process' return false */ + return process_id; +} + +/* A thread function that loads a user process and starts it + running. */ +static void +start_process (struct parameters_to_start_process* parameters) +{ + /* The last argument passed to thread_create is received here... */ + struct intr_frame if_; + bool success; + + char file_name[64]; + strlcpy_first_word (file_name, parameters->command_line, 64); + + debug("%s#%d: start_process(\"%s\") ENTERED\n", + thread_current()->name, + thread_current()->tid, + parameters->command_line); + + /* Initialize interrupt frame and load executable. */ + memset (&if_, 0, sizeof if_); + if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; + if_.cs = SEL_UCSEG; + if_.eflags = FLAG_IF | FLAG_MBS; + + success = load (file_name, &if_.eip, &if_.esp); + + debug("%s#%d: start_process(...): load returned %d\n", + thread_current()->name, + thread_current()->tid, + success); + + if (success) + { + /* We managed to load the new program to a process, and have + allocated memory for a process stack. The stack top is in + if_.esp, now we must prepare and place the arguments to main on + the stack. */ + + /* A temporary solution is to modify the stack pointer to + "pretend" the arguments are present on the stack. A normal + C-function expects the stack to contain, in order, the return + address, the first argument, the second argument etc. */ + + HACK if_.esp -= 12; /* Unacceptable solution. */ + + /* The stack and stack pointer should be setup correct just before + the process start, so this is the place to dump stack content + for debug purposes. Disable the dump when it works. */ + +// dump_stack ( PHYS_BASE + 15, PHYS_BASE - if_.esp + 16 ); + + } + + debug("%s#%d: start_process(\"%s\") DONE\n", + thread_current()->name, + thread_current()->tid, + parameters->command_line); + + + /* If load fail, quit. Load may fail for several reasons. + Some simple examples: + - File doeas not exist + - File do not contain a valid program + - Not enough memory + */ + 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 (); +} + +/* Wait for process `child_id' to die and then return its exit + status. If it was terminated by the kernel (i.e. killed due to an + exception), return -1. If `child_id' 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 `child_id', return -1 + immediately, without waiting. + + This function will be implemented last, after a communication + mechanism between parent and child is established. */ +int +process_wait (int child_id) +{ + int status = -1; + struct thread *cur = thread_current (); + + debug("%s#%d: process_wait(%d) ENTERED\n", + cur->name, cur->tid, child_id); + /* Yes! You need to do something good here ! */ + debug("%s#%d: process_wait(%d) RETURNS %d\n", + cur->name, cur->tid, child_id, status); + + return status; +} + +/* Free the current process's resources. This function is called + automatically from thread_exit() to make sure cleanup of any + process resources is always done. That is correct behaviour. But + know that thread_exit() is called at many places inside the kernel, + mostly in case of some unrecoverable error in a thread. + + In such case it may happen that some data is not yet available, or + initialized. You must make sure that nay data needed IS available + or initialized to something sane, or else that any such situation + is detected. +*/ + +void +process_cleanup (void) +{ + struct thread *cur = thread_current (); + uint32_t *pd = cur->pagedir; + int status = -1; + + debug("%s#%d: process_cleanup() ENTERED\n", cur->name, cur->tid); + + /* Later tests DEPEND on this output to work correct. You will have + * to find the actual exit status in your process list. It is + * important to do this printf BEFORE you tell the parent process + * that you exit. (Since the parent may be the main() function, + * that may sometimes poweroff as soon as process_wait() returns, + * possibly before the prontf is completed.) + */ + printf("%s: exit(%d)\n", thread_name(), status); + + /* Destroy the current process's page directory and switch back + to the kernel-only page directory. */ + 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); + } + debug("%s#%d: process_cleanup() DONE with status %d\n", + cur->name, cur->tid, status); +} + +/* 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 (); +} + diff --git a/src/userprog/process.h b/src/userprog/process.h new file mode 100644 index 0000000..0a4e4ac --- /dev/null +++ b/src/userprog/process.h @@ -0,0 +1,23 @@ +#ifndef USERPROG_PROCESS_H +#define USERPROG_PROCESS_H + +#include "threads/thread.h" + +void process_init (void); +void process_print_list (void); +void process_exit (int status); +tid_t process_execute (const char *file_name); +int process_wait (tid_t); +void process_cleanup (void); +void process_activate (void); + +/* This is unacceptable solutions. */ +#define INFINITE_WAIT() for ( ; ; ) thread_yield() +#define BUSY_WAIT(n) \ + do { \ + int i = n; \ + while ( i --> 0 ) \ + thread_yield(); \ + } while ( 0 ) + +#endif /* userprog/process.h */ diff --git a/src/userprog/syscall.c b/src/userprog/syscall.c new file mode 100644 index 0000000..4d46978 --- /dev/null +++ b/src/userprog/syscall.c @@ -0,0 +1,61 @@ +#include <stdio.h> +#include <syscall-nr.h> +#include "userprog/syscall.h" +#include "threads/interrupt.h" +#include "threads/thread.h" + +/* header files you probably need, they are not used yet */ +#include <string.h> +#include "filesys/filesys.h" +#include "filesys/file.h" +#include "threads/vaddr.h" +#include "threads/init.h" +#include "userprog/pagedir.h" +#include "userprog/process.h" +#include "devices/input.h" + +static void syscall_handler (struct intr_frame *); + +void +syscall_init (void) +{ + intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall"); +} + + +/* This array defined the number of arguments each syscall expects. + For example, if you want to find out the number of arguments for + the read system call you shall write: + + int sys_read_arg_count = argc[ SYS_READ ]; + + All system calls have a name such as SYS_READ defined as an enum + type, see `lib/syscall-nr.h'. Use them instead of numbers. + */ +const int argc[] = { + /* basic calls */ + 0, 1, 1, 1, 2, 1, 1, 1, 3, 3, 2, 1, 1, + /* not implemented */ + 2, 1, 1, 1, 2, 1, 1, + /* extended */ + 0 +}; + +static void +syscall_handler (struct intr_frame *f) +{ + int32_t* esp = (int32_t*)f->esp; + + switch ( 0 /* retrive syscall number */ ) + { + default: + { + printf ("Executed an unknown system call!\n"); + + printf ("Stack top + 0: %d\n", esp[0]); + printf ("Stack top + 1: %d\n", esp[1]); + + thread_exit (); + } + } +} diff --git a/src/userprog/syscall.h b/src/userprog/syscall.h new file mode 100644 index 0000000..9059096 --- /dev/null +++ b/src/userprog/syscall.h @@ -0,0 +1,6 @@ +#ifndef USERPROG_SYSCALL_H +#define USERPROG_SYSCALL_H + +void syscall_init (void); + +#endif /* userprog/syscall.h */ diff --git a/src/userprog/test.c b/src/userprog/test.c new file mode 100644 index 0000000..6d884d6 --- /dev/null +++ b/src/userprog/test.c @@ -0,0 +1,66 @@ +#include <stdio.h> + +typedef __SIZE_TYPE__ size_t; + +/* As strlcpy, but only copies the first word from SRC. Worde are + * delimeted by space. Leading space is also ignored. + * ( added by klaar@ida ) + */ +size_t +strlcpy_first_word (char *dst, const char *src, size_t size) +{ + size_t i = size; + + /* skip leading spaces */ + while (*src != '\0' && *src == ' ') + ++src; + + /* copy at most size characters */ + if (size > 0) + { + while (i > 1) + { + if (*src == '\0' || *src == ' ') + break; + + *dst++ = *src++; + --i; + } + *dst = '\0'; + } + return size - i; +} + +int main(int argc, char* argv[]) +{ + char word[100]; + int i; + + i = strlcpy_first_word(word, argv[1], -1); + + printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1])); + printf("First w: '%s', (%d characters)\n", word, strlen(word)); + printf("Copied %d characters of max %d\n\n", i, -1); + + + i = strlcpy_first_word(word, argv[1], 0); + + printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1])); + printf("First w: '%s', (%d characters)\n", word, strlen(word)); + printf("Copied %d characters of max %d\n\n", i, 0); + + + i = strlcpy_first_word(word, argv[1], 1); + + printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1])); + printf("First w: '%s', (%d characters)\n", word, strlen(word)); + printf("Copied %d characters of max %d\n\n", i, 1); + + + i = strlcpy_first_word(word, argv[1], 10); + + printf("Argv[1]: '%s', (%d characters)\n", argv[1], strlen(argv[1])); + printf("First w: '%s', (%d characters)\n", word, strlen(word)); + printf("Copied %d characters of max %d\n\n", i, 10); + +} diff --git a/src/userprog/tss.c b/src/userprog/tss.c new file mode 100644 index 0000000..435c780 --- /dev/null +++ b/src/userprog/tss.c @@ -0,0 +1,106 @@ +#include "userprog/tss.h" +#include <debug.h> +#include <stddef.h> +#include "userprog/gdt.h" +#include "threads/palloc.h" +#include "threads/vaddr.h" +#include "threads/thread.h" + +/* The Task-State Segment (TSS). + + Instances of the TSS, an x86-specific structure, are used to + define "tasks", a form of support for multitasking built right + into the processor. However, for various reasons including + portability, speed, and flexibility, most x86 OSes almost + completely ignore the TSS. We are no exception. + + Unfortunately, there is one thing that can only be done using + a TSS: stack switching for interrupts that occur in user mode. + When an interrupt occurs in user mode (ring 3), the processor + consults the ss0 and esp0 members of the current TSS to + determine the stack to use for handling the interrupt. Thus, + we must create a TSS and initialize at least these fields, and + this is precisely what this file does. + + When an interrupt is handled by an interrupt or trap gate + (which applies to all interrupts we handle), an x86 processor + works like this: + + - If the code interrupted by the interrupt is in the same + ring as the interrupt handler, then no stack switch takes + place. This is the case for interrupts that happen when + we're running in the kernel. The contents of the TSS are + irrelevant for this case. + + - If the interrupted code is in a different ring from the + handler, then the processor switches to the stack + specified in the TSS for the new ring. This is the case + for interrupts that happen when we're in user space. It's + important that we switch to a stack that's not already in + use, to avoid corruption. Because we're running in user + space, we know that the current process's kernel stack is + not in use, so we can always use that. Thus, when the + scheduler switches threads, it also changes the TSS's + stack pointer to point to the new thread's kernel stack. + (The call is in schedule_tail() in thread.c.) + + See [IA32-v3a] 6.2.1 "Task-State Segment (TSS)" for a + description of the TSS. See [IA32-v3a] 5.12.1 "Exception- or + Interrupt-Handler Procedures" for a description of when and + how stack switching occurs during an interrupt. */ +struct tss + { + uint16_t back_link, :16; + void *esp0; /* Ring 0 stack virtual address. */ + uint16_t ss0, :16; /* Ring 0 stack segment selector. */ + void *esp1; + uint16_t ss1, :16; + void *esp2; + uint16_t ss2, :16; + uint32_t cr3; + void (*eip) (void); + uint32_t eflags; + uint32_t eax, ecx, edx, ebx; + uint32_t esp, ebp, esi, edi; + uint16_t es, :16; + uint16_t cs, :16; + uint16_t ss, :16; + uint16_t ds, :16; + uint16_t fs, :16; + uint16_t gs, :16; + uint16_t ldt, :16; + uint16_t trace, bitmap; + }; + +/* Kernel TSS. */ +static struct tss *tss; + +/* Initializes the kernel TSS. */ +void +tss_init (void) +{ + /* Our TSS is never used in a call gate or task gate, so only a + few fields of it are ever referenced, and those are the only + ones we initialize. */ + tss = palloc_get_page (PAL_ASSERT | PAL_ZERO); + tss->ss0 = SEL_KDSEG; + tss->bitmap = 0xdfff; + tss_update (); +} + +/* Returns the kernel TSS. */ +struct tss * +tss_get (void) +{ + ASSERT (tss != NULL); + return tss; +} + +/* Sets the ring 0 stack pointer in the TSS to point to the end + of the thread stack. */ +void +tss_update (void) +{ + ASSERT (tss != NULL); + tss->esp0 = (uint8_t *) thread_current () + PGSIZE; +} diff --git a/src/userprog/tss.h b/src/userprog/tss.h new file mode 100644 index 0000000..467bd19 --- /dev/null +++ b/src/userprog/tss.h @@ -0,0 +1,11 @@ +#ifndef USERPROG_TSS_H +#define USERPROG_TSS_H + +#include <stdint.h> + +struct tss; +void tss_init (void); +struct tss *tss_get (void); +void tss_update (void); + +#endif /* userprog/tss.h */ |
