provided code

This commit is contained in:
LabTS
2024-10-01 23:37:39 +01:00
commit 8724a2641e
697 changed files with 74252 additions and 0 deletions

3
src/userprog/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build
bochsrc.txt
bochsout.txt

7
src/userprog/Make.vars Normal file
View File

@@ -0,0 +1,7 @@
# -*- makefile -*-
kernel.bin: DEFINES = -DUSERPROG -DFILESYS
KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base
GRADING_FILE = $(SRCDIR)/tests/userprog/Grading
SIMULATOR = --qemu

1
src/userprog/Makefile Normal file
View File

@@ -0,0 +1 @@
include ../Makefile.kernel

158
src/userprog/exception.c Normal file
View File

@@ -0,0 +1,158 @@
#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);
PANIC ("Kernel bug - this shouldn't be possible!");
}
}
/* Page fault handler. This is a skeleton that must be filled in
to implement virtual memory. Some solutions to task 2 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);
}

12
src/userprog/exception.h Normal file
View File

@@ -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 */

146
src/userprog/gdt.c Normal file
View File

@@ -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" : : "q" (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);
}

15
src/userprog/gdt.h Normal file
View File

@@ -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 */

290
src/userprog/pagedir.c Normal file
View File

@@ -0,0 +1,290 @@
#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, init_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 != init_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 < init_ram_pages);
ASSERT (pd != init_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);
}
}
}
/* Returns true if the PTE for virtual page VPAGE in PD is writable.
Returns false if PD contains no PTE for VPAGE. */
bool
pagedir_is_writable (uint32_t *pd, const void *vpage)
{
uint32_t *pte = lookup_page (pd, vpage, false);
return pte != NULL && (*pte & PTE_W) != 0;
}
/* Set the writable bit to WRITABLE in the PTE for virtual page VPAGE
in PD. */
void
pagedir_set_writable (uint32_t *pd, const void *vpage, bool writable)
{
uint32_t *pte = lookup_page (pd, vpage, false);
if (pte != NULL)
{
if (writable)
*pte |= PTE_W;
else
{
*pte &= ~(uint32_t) PTE_W;
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 = init_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);
}
/* Some 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);
}
}

20
src/userprog/pagedir.h Normal file
View File

@@ -0,0 +1,20 @@
#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);
bool pagedir_is_writable (uint32_t *pd, const void *upage);
void pagedir_set_writable (uint32_t *pd, const void *upage, bool writable);
void pagedir_activate (uint32_t *pd);
#endif /* userprog/pagedir.h */

479
src/userprog/process.c Normal file
View File

@@ -0,0 +1,479 @@
#include "userprog/process.h"
#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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/palloc.h"
#include "threads/thread.h"
#include "threads/vaddr.h"
static thread_func start_process 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 (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 *file_name)
{
char *fn_copy;
tid_t tid;
/* Make a copy of FILE_NAME.
Otherwise there's a race between the caller and load(). */
fn_copy = palloc_get_page (0);
if (fn_copy == NULL)
return TID_ERROR;
strlcpy (fn_copy, file_name, PGSIZE);
/* Create a new thread to execute FILE_NAME. */
tid = thread_create (file_name, PRI_DEFAULT, start_process, fn_copy);
if (tid == TID_ERROR)
palloc_free_page (fn_copy);
return tid;
}
/* A thread function that loads a user process and starts it
running. */
static void
start_process (void *file_name_)
{
char *file_name = file_name_;
struct intr_frame if_;
bool success;
/* 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);
/* If load failed, quit. */
palloc_free_page (file_name);
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 ();
}
/* 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 UNUSED)
{
return -1;
}
/* Free the current process's resources. */
void
process_exit (void)
{
struct thread *cur = thread_current ();
uint32_t *pd;
/* 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);
}
}
/* 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;
/* 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. */
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;
/* Check if virtual page already allocated */
struct thread *t = thread_current ();
uint8_t *kpage = pagedir_get_page (t->pagedir, upage);
if (kpage == NULL){
/* Get a new page of memory. */
kpage = palloc_get_page (PAL_USER);
if (kpage == NULL){
return false;
}
/* Add the page to the process's address space. */
if (!install_page (upage, kpage, writable))
{
palloc_free_page (kpage);
return false;
}
} else {
/* Check if writable flag for the page should be updated */
if(writable && !pagedir_is_writable(t->pagedir, upage)){
pagedir_set_writable(t->pagedir, upage, writable);
}
}
/* Load data into the page. */
if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes){
return false;
}
memset (kpage + page_read_bytes, 0, page_zero_bytes);
/* 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));
}

11
src/userprog/process.h Normal file
View File

@@ -0,0 +1,11 @@
#ifndef USERPROG_PROCESS_H
#define USERPROG_PROCESS_H
#include "threads/thread.h"
tid_t process_execute (const char *file_name);
int process_wait (tid_t);
void process_exit (void);
void process_activate (void);
#endif /* userprog/process.h */

20
src/userprog/syscall.c Normal file
View File

@@ -0,0 +1,20 @@
#include "userprog/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
static void syscall_handler (struct intr_frame *);
void
syscall_init (void)
{
intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}
static void
syscall_handler (struct intr_frame *f UNUSED)
{
printf ("system call!\n");
thread_exit ();
}

6
src/userprog/syscall.h Normal file
View File

@@ -0,0 +1,6 @@
#ifndef USERPROG_SYSCALL_H
#define USERPROG_SYSCALL_H
void syscall_init (void);
#endif /* userprog/syscall.h */

106
src/userprog/tss.c Normal file
View File

@@ -0,0 +1,106 @@
#include "userprog/tss.h"
#include <debug.h>
#include <stddef.h>
#include "userprog/gdt.h"
#include "threads/thread.h"
#include "threads/palloc.h"
#include "threads/vaddr.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 thread_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;
}

11
src/userprog/tss.h Normal file
View File

@@ -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 */