provided code
This commit is contained in:
3
src/userprog/.gitignore
vendored
Normal file
3
src/userprog/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
bochsrc.txt
|
||||
bochsout.txt
|
||||
7
src/userprog/Make.vars
Normal file
7
src/userprog/Make.vars
Normal 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
1
src/userprog/Makefile
Normal file
@@ -0,0 +1 @@
|
||||
include ../Makefile.kernel
|
||||
158
src/userprog/exception.c
Normal file
158
src/userprog/exception.c
Normal 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
12
src/userprog/exception.h
Normal 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
146
src/userprog/gdt.c
Normal 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
15
src/userprog/gdt.h
Normal 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
290
src/userprog/pagedir.c
Normal 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
20
src/userprog/pagedir.h
Normal 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
479
src/userprog/process.c
Normal 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
11
src/userprog/process.h
Normal 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
20
src/userprog/syscall.c
Normal 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
6
src/userprog/syscall.h
Normal 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
106
src/userprog/tss.c
Normal 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
11
src/userprog/tss.h
Normal 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 */
|
||||
Reference in New Issue
Block a user