Compare commits
22 Commits
03b296f7e1
...
lab5
| Author | SHA1 | Date | |
|---|---|---|---|
| 4199ce9c37 | |||
| e9f683f6d6 | |||
| 9acc7c80f7 | |||
| 721a113c93 | |||
| 86c4aa03ed | |||
| f1980d32ca | |||
| 46e7d21fbf | |||
| d943a2be37 | |||
| 1e5c0639ee | |||
| 34e9433d15 | |||
| 9bec3d83bd | |||
| 0289ec3b3e | |||
| 46cc5c9478 | |||
| 4e4de7b836 | |||
| c3e2a92b08 | |||
|
|
ed614a36f0 | ||
|
|
7d3fde5f77 | ||
|
|
023f974e4c | ||
|
|
adbb69750a | ||
|
|
2dc0ccbbdc | ||
|
|
c67463e23c | ||
|
|
da1f8392b1 |
@@ -88,6 +88,7 @@ CFLAGS := $(CFLAGS) $(DEFS) $(LABDEFS) -O1 -fno-builtin -I$(TOP) -MD
|
||||
CFLAGS += -fno-omit-frame-pointer
|
||||
CFLAGS += -std=gnu99
|
||||
CFLAGS += -static
|
||||
CFLAGS += -fno-pie
|
||||
CFLAGS += -Wall -Wno-format -Wno-unused -Werror -gstabs -m32
|
||||
# -fno-tree-ch prevented gcc from sometimes reordering read_ebp() before
|
||||
# mon_backtrace()'s function prologue on gcc version: (Debian 4.7.2-5) 4.7.2
|
||||
@@ -140,11 +141,17 @@ include boot/Makefrag
|
||||
include kern/Makefrag
|
||||
include lib/Makefrag
|
||||
include user/Makefrag
|
||||
include fs/Makefrag
|
||||
|
||||
|
||||
CPUS ?= 1
|
||||
|
||||
QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT)
|
||||
QEMUOPTS += $(shell if $(QEMU) -nographic -help | grep -q '^-D '; then echo '-D qemu.log'; fi)
|
||||
IMAGES = $(OBJDIR)/kern/kernel.img
|
||||
QEMUOPTS += -smp $(CPUS)
|
||||
QEMUOPTS += -drive file=$(OBJDIR)/fs/fs.img,index=1,media=disk,format=raw
|
||||
IMAGES += $(OBJDIR)/fs/fs.img
|
||||
QEMUOPTS += $(QEMUEXTRA)
|
||||
|
||||
.gdbinit: .gdbinit.tmpl
|
||||
|
||||
22
answers-lab2.txt
Normal file
22
answers-lab2.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
1. The entry given by PDX(UVPT) is mapped to the page directory itself,
|
||||
to allow programs to read existing memory mappings. The entry at PDX(UPAGES)
|
||||
is mapped to the pages variable in memory so that the kernel (and potentially other ring 0 programs) can access it. The entry pointed to by PDX(KSTACKTOP-KSTACKSIZE) is mapped
|
||||
to the bootstack location. Finally, both the memory pointed to by 0 and PDX(KERNBASE) are mapped to kernel memory. However, the mappings at VA 0 are read-only, so user programs can't touch them, while the mappings at PDX(KERNBASE) are kernel-private and RW.
|
||||
|
||||
A table could be as follows:
|
||||
|
||||
400 - Kernel memory
|
||||
... - Kernel memory
|
||||
3c0 - Kernel memory
|
||||
3bf - Kernel Stack
|
||||
3bd - UVPT
|
||||
3bc - UPAGES
|
||||
|
||||
|
||||
2. The kernel memory is mapped as kernel read write. This means there are several flags set in the kernel page directory and page table entries, which indicate that this is restricted memory. The lower 3 bits of the CS register will be checked when an access to this memory is made, and, if they are not 0 or 1 (indicating kernel code), the CPU generates a fault, and the user program gives up control back to the OS. Thus, unless a program is started in ring 0, it will not be able to read or write kernel memory, as it should.
|
||||
|
||||
3. The absolute maximum is 4GB, since we use 32-bit integers for referencing memory. Some of this memory is used for the kernel itself, as well as for the page directory and tables.
|
||||
|
||||
4. The page directory and the corresponding pages are all 4kb. The page directory can have 1024 entries, and each of these point to a page table. Thus, we use approximatey 4MB (slightly more than that, actually, due to the size of the page directory itself) of memory. Additionally, the "pages" structs (which are used to keep track of available physical pages), will require ~1000000 entries, each of which is between 6 and 8 bytes (depending on whether GCC aligns struct sizes). This means another 8MB is used to keep track of free pages, to a total of around 12MB.
|
||||
|
||||
5. We switch to high EIP when we jump to "relocated". Relocated is a label, and a symbol that's inserted by the linker. Since the linker is configured to link the kernel high, relocated points to the upper portion of memory, where KERNBASE is. However, the entry page directory, just like our full page directory later on, sets up two mappings, one starting at 0 (creating a one to one mapping between some of the virtual addresses and their physical counterparts), and one starting at KERNBASE. Thus, we can continue to run at a low EIP. The only reason I can think of as to why we NEED to make the switch, besides the elementary "the kernel links high", is that we need to be able to write to various symbols, also linked above KERNBASE.
|
||||
@@ -39,6 +39,7 @@ void
|
||||
bootmain(void)
|
||||
{
|
||||
struct Proghdr *ph, *eph;
|
||||
int i;
|
||||
|
||||
// read 1st page off disk
|
||||
readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
|
||||
@@ -50,10 +51,14 @@ bootmain(void)
|
||||
// load each program segment (ignores ph flags)
|
||||
ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
|
||||
eph = ph + ELFHDR->e_phnum;
|
||||
for (; ph < eph; ph++)
|
||||
for (; ph < eph; ph++) {
|
||||
// p_pa is the load address of this segment (as well
|
||||
// as the physical address)
|
||||
readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
|
||||
for (i = 0; i < ph->p_memsz - ph->p_filesz; i++) {
|
||||
*((char *) ph->p_pa + ph->p_filesz + i) = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// call the entry point from the ELF header
|
||||
// note: does not return!
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
LAB=3
|
||||
PACKAGEDATE=Tue Sep 25 12:21:10 EDT 2018
|
||||
LAB=5
|
||||
PACKAGEDATE=Wed Oct 24 20:44:37 EDT 2018
|
||||
|
||||
77
fs/Makefrag
Normal file
77
fs/Makefrag
Normal file
@@ -0,0 +1,77 @@
|
||||
|
||||
OBJDIRS += fs
|
||||
|
||||
FSOFILES := $(OBJDIR)/fs/ide.o \
|
||||
$(OBJDIR)/fs/bc.o \
|
||||
$(OBJDIR)/fs/fs.o \
|
||||
$(OBJDIR)/fs/serv.o \
|
||||
$(OBJDIR)/fs/test.o \
|
||||
|
||||
USERAPPS := $(OBJDIR)/user/init
|
||||
|
||||
FSIMGTXTFILES := fs/newmotd \
|
||||
fs/motd
|
||||
|
||||
|
||||
USERAPPS := $(USERAPPS) \
|
||||
$(OBJDIR)/user/cat \
|
||||
$(OBJDIR)/user/echo \
|
||||
$(OBJDIR)/user/init \
|
||||
$(OBJDIR)/user/ls \
|
||||
$(OBJDIR)/user/lsfd \
|
||||
$(OBJDIR)/user/num \
|
||||
$(OBJDIR)/user/forktree \
|
||||
$(OBJDIR)/user/primes \
|
||||
$(OBJDIR)/user/primespipe \
|
||||
$(OBJDIR)/user/sh \
|
||||
$(OBJDIR)/user/testfdsharing \
|
||||
$(OBJDIR)/user/testkbd \
|
||||
$(OBJDIR)/user/testpipe \
|
||||
$(OBJDIR)/user/testpteshare \
|
||||
$(OBJDIR)/user/testshell \
|
||||
$(OBJDIR)/user/hello \
|
||||
$(OBJDIR)/user/faultio \
|
||||
|
||||
FSIMGTXTFILES := $(FSIMGTXTFILES) \
|
||||
fs/lorem \
|
||||
fs/script \
|
||||
fs/testshell.key \
|
||||
fs/testshell.sh
|
||||
|
||||
|
||||
FSIMGFILES := $(FSIMGTXTFILES) $(USERAPPS)
|
||||
|
||||
$(OBJDIR)/fs/%.o: fs/%.c fs/fs.h inc/lib.h $(OBJDIR)/.vars.USER_CFLAGS
|
||||
@echo + cc[USER] $<
|
||||
@mkdir -p $(@D)
|
||||
$(V)$(CC) -nostdinc $(USER_CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR)/fs/fs: $(FSOFILES) $(OBJDIR)/lib/entry.o $(OBJDIR)/lib/libjos.a user/user.ld
|
||||
@echo + ld $@
|
||||
$(V)mkdir -p $(@D)
|
||||
$(V)$(LD) -o $@ $(ULDFLAGS) $(LDFLAGS) -nostdlib \
|
||||
$(OBJDIR)/lib/entry.o $(FSOFILES) \
|
||||
-L$(OBJDIR)/lib -ljos $(GCC_LIB)
|
||||
$(V)$(OBJDUMP) -S $@ >$@.asm
|
||||
|
||||
# How to build the file system image
|
||||
$(OBJDIR)/fs/fsformat: fs/fsformat.c
|
||||
@echo + mk $(OBJDIR)/fs/fsformat
|
||||
$(V)mkdir -p $(@D)
|
||||
$(V)$(NCC) $(NATIVE_CFLAGS) -o $(OBJDIR)/fs/fsformat fs/fsformat.c
|
||||
|
||||
$(OBJDIR)/fs/clean-fs.img: $(OBJDIR)/fs/fsformat $(FSIMGFILES)
|
||||
@echo + mk $(OBJDIR)/fs/clean-fs.img
|
||||
$(V)mkdir -p $(@D)
|
||||
$(V)$(OBJDIR)/fs/fsformat $(OBJDIR)/fs/clean-fs.img 1024 $(FSIMGFILES)
|
||||
|
||||
$(OBJDIR)/fs/fs.img: $(OBJDIR)/fs/clean-fs.img
|
||||
@echo + cp $(OBJDIR)/fs/clean-fs.img $@
|
||||
$(V)cp $(OBJDIR)/fs/clean-fs.img $@
|
||||
|
||||
all: $(OBJDIR)/fs/fs.img
|
||||
|
||||
#all: $(addsuffix .sym, $(USERAPPS))
|
||||
|
||||
#all: $(addsuffix .asm, $(USERAPPS))
|
||||
|
||||
166
fs/bc.c
Normal file
166
fs/bc.c
Normal file
@@ -0,0 +1,166 @@
|
||||
|
||||
#include "fs.h"
|
||||
|
||||
// Return the virtual address of this disk block.
|
||||
void*
|
||||
diskaddr(uint32_t blockno)
|
||||
{
|
||||
if (blockno == 0 || (super && blockno >= super->s_nblocks))
|
||||
panic("bad block number %08x in diskaddr", blockno);
|
||||
return (char*) (DISKMAP + blockno * BLKSIZE);
|
||||
}
|
||||
|
||||
// Is this virtual address mapped?
|
||||
bool
|
||||
va_is_mapped(void *va)
|
||||
{
|
||||
return (uvpd[PDX(va)] & PTE_P) && (uvpt[PGNUM(va)] & PTE_P);
|
||||
}
|
||||
|
||||
// Is this virtual address dirty?
|
||||
bool
|
||||
va_is_dirty(void *va)
|
||||
{
|
||||
return (uvpt[PGNUM(va)] & PTE_D) != 0;
|
||||
}
|
||||
|
||||
// Fault any disk block that is read in to memory by
|
||||
// loading it from disk.
|
||||
static void
|
||||
bc_pgfault(struct UTrapframe *utf)
|
||||
{
|
||||
void *addr = (void *) utf->utf_fault_va;
|
||||
uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE;
|
||||
uint32_t secno = blockno * (BLKSIZE / SECTSIZE);
|
||||
int r;
|
||||
|
||||
// Check that the fault was within the block cache region
|
||||
if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE))
|
||||
panic("page fault in FS: eip %08x, va %08x, err %04x",
|
||||
utf->utf_eip, addr, utf->utf_err);
|
||||
|
||||
// Sanity check the block number.
|
||||
if (super && blockno >= super->s_nblocks)
|
||||
panic("reading non-existent block %08x\n", blockno);
|
||||
|
||||
// Allocate a page in the disk map region, read the contents
|
||||
// of the block from the disk into that page.
|
||||
// Hint: first round addr to page boundary. fs/ide.c has code to read
|
||||
// the disk.
|
||||
//
|
||||
// LAB 5: you code here:
|
||||
addr = ROUNDDOWN(addr, PGSIZE);
|
||||
if((r = sys_page_alloc(0, addr, PTE_U | PTE_W | PTE_P)) < 0)
|
||||
panic("failed to allocate page for block cache");
|
||||
if((r = ide_read(secno, addr, BLKSIZE / SECTSIZE)) < 0)
|
||||
panic("failed to read from disk into block cache");
|
||||
|
||||
// Clear the dirty bit for the disk block page since we just read the
|
||||
// block from disk
|
||||
if ((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0)
|
||||
panic("in bc_pgfault, sys_page_map: %e", r);
|
||||
|
||||
// Check that the block we read was allocated. (exercise for
|
||||
// the reader: why do we do this *after* reading the block
|
||||
// in?)
|
||||
if (bitmap && block_is_free(blockno))
|
||||
panic("reading free block %08x\n", blockno);
|
||||
}
|
||||
|
||||
// Flush the contents of the block containing VA out to disk if
|
||||
// necessary, then clear the PTE_D bit using sys_page_map.
|
||||
// If the block is not in the block cache or is not dirty, does
|
||||
// nothing.
|
||||
// Hint: Use va_is_mapped, va_is_dirty, and ide_write.
|
||||
// Hint: Use the PTE_SYSCALL constant when calling sys_page_map.
|
||||
// Hint: Don't forget to round addr down.
|
||||
void
|
||||
flush_block(void *addr)
|
||||
{
|
||||
uint32_t blockno = ((uint32_t)addr - DISKMAP) / BLKSIZE;
|
||||
uint32_t secno = blockno * (BLKSIZE / SECTSIZE);
|
||||
int r;
|
||||
|
||||
if (addr < (void*)DISKMAP || addr >= (void*)(DISKMAP + DISKSIZE))
|
||||
panic("flush_block of bad va %08x", addr);
|
||||
|
||||
// LAB 5: Your code here.
|
||||
addr = ROUNDDOWN(addr, PGSIZE);
|
||||
if(!(va_is_mapped(addr) && va_is_dirty(addr))) return;
|
||||
|
||||
if((r = ide_write(secno, addr, BLKSIZE / SECTSIZE)) < 0)
|
||||
panic("failed to flush block into cache");
|
||||
|
||||
if((r = sys_page_map(0, addr, 0, addr, uvpt[PGNUM(addr)] & PTE_SYSCALL)) < 0)
|
||||
panic("failed to remove dirty bit from page");
|
||||
}
|
||||
|
||||
// Test that the block cache works, by smashing the superblock and
|
||||
// reading it back.
|
||||
static void
|
||||
check_bc(void)
|
||||
{
|
||||
struct Super backup;
|
||||
|
||||
// back up super block
|
||||
memmove(&backup, diskaddr(1), sizeof backup);
|
||||
|
||||
// smash it
|
||||
strcpy(diskaddr(1), "OOPS!\n");
|
||||
flush_block(diskaddr(1));
|
||||
assert(va_is_mapped(diskaddr(1)));
|
||||
assert(!va_is_dirty(diskaddr(1)));
|
||||
|
||||
// clear it out
|
||||
sys_page_unmap(0, diskaddr(1));
|
||||
assert(!va_is_mapped(diskaddr(1)));
|
||||
|
||||
// read it back in
|
||||
assert(strcmp(diskaddr(1), "OOPS!\n") == 0);
|
||||
|
||||
// fix it
|
||||
memmove(diskaddr(1), &backup, sizeof backup);
|
||||
flush_block(diskaddr(1));
|
||||
|
||||
// Now repeat the same experiment, but pass an unaligned address to
|
||||
// flush_block.
|
||||
|
||||
// back up super block
|
||||
memmove(&backup, diskaddr(1), sizeof backup);
|
||||
|
||||
// smash it
|
||||
strcpy(diskaddr(1), "OOPS!\n");
|
||||
|
||||
// Pass an unaligned address to flush_block.
|
||||
flush_block(diskaddr(1) + 20);
|
||||
assert(va_is_mapped(diskaddr(1)));
|
||||
|
||||
// Skip the !va_is_dirty() check because it makes the bug somewhat
|
||||
// obscure and hence harder to debug.
|
||||
//assert(!va_is_dirty(diskaddr(1)));
|
||||
|
||||
// clear it out
|
||||
sys_page_unmap(0, diskaddr(1));
|
||||
assert(!va_is_mapped(diskaddr(1)));
|
||||
|
||||
// read it back in
|
||||
assert(strcmp(diskaddr(1), "OOPS!\n") == 0);
|
||||
|
||||
// fix it
|
||||
memmove(diskaddr(1), &backup, sizeof backup);
|
||||
flush_block(diskaddr(1));
|
||||
|
||||
cprintf("block cache is good\n");
|
||||
}
|
||||
|
||||
void
|
||||
bc_init(void)
|
||||
{
|
||||
struct Super super;
|
||||
set_pgfault_handler(bc_pgfault);
|
||||
check_bc();
|
||||
|
||||
// cache the super block by reading it once
|
||||
memmove(&super, diskaddr(1), sizeof super);
|
||||
}
|
||||
|
||||
490
fs/fs.c
Normal file
490
fs/fs.c
Normal file
@@ -0,0 +1,490 @@
|
||||
#include <inc/string.h>
|
||||
#include <inc/partition.h>
|
||||
|
||||
#include "fs.h"
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Super block
|
||||
// --------------------------------------------------------------
|
||||
|
||||
// Validate the file system super-block.
|
||||
void
|
||||
check_super(void)
|
||||
{
|
||||
if (super->s_magic != FS_MAGIC)
|
||||
panic("bad file system magic number");
|
||||
|
||||
if (super->s_nblocks > DISKSIZE/BLKSIZE)
|
||||
panic("file system is too large");
|
||||
|
||||
cprintf("superblock is good\n");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Free block bitmap
|
||||
// --------------------------------------------------------------
|
||||
|
||||
// Check to see if the block bitmap indicates that block 'blockno' is free.
|
||||
// Return 1 if the block is free, 0 if not.
|
||||
bool
|
||||
block_is_free(uint32_t blockno)
|
||||
{
|
||||
if (super == 0 || blockno >= super->s_nblocks)
|
||||
return 0;
|
||||
if (bitmap[blockno / 32] & (1 << (blockno % 32)))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Mark a block free in the bitmap
|
||||
void
|
||||
free_block(uint32_t blockno)
|
||||
{
|
||||
// Blockno zero is the null pointer of block numbers.
|
||||
if (blockno == 0)
|
||||
panic("attempt to free zero block");
|
||||
bitmap[blockno/32] |= 1<<(blockno%32);
|
||||
}
|
||||
|
||||
// Search the bitmap for a free block and allocate it. When you
|
||||
// allocate a block, immediately flush the changed bitmap block
|
||||
// to disk.
|
||||
//
|
||||
// Return block number allocated on success,
|
||||
// -E_NO_DISK if we are out of blocks.
|
||||
//
|
||||
// Hint: use free_block as an example for manipulating the bitmap.
|
||||
int
|
||||
alloc_block(void)
|
||||
{
|
||||
// The bitmap consists of one or more blocks. A single bitmap block
|
||||
// contains the in-use bits for BLKBITSIZE blocks. There are
|
||||
// super->s_nblocks blocks in the disk altogether.
|
||||
uint32_t* search = bitmap + 1;
|
||||
for(uint32_t block = 1; block < super->s_nblocks; block++) {
|
||||
if(bitmap[block / 32] & (1 << (block % 32))) {
|
||||
bitmap[block / 32] &= ~(1 << (block % 32));
|
||||
flush_block(&bitmap[block / 32]);
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
return -E_NO_DISK;
|
||||
}
|
||||
|
||||
// Validate the file system bitmap.
|
||||
//
|
||||
// Check that all reserved blocks -- 0, 1, and the bitmap blocks themselves --
|
||||
// are all marked as in-use.
|
||||
void
|
||||
check_bitmap(void)
|
||||
{
|
||||
uint32_t i;
|
||||
|
||||
// Make sure all bitmap blocks are marked in-use
|
||||
for (i = 0; i * BLKBITSIZE < super->s_nblocks; i++)
|
||||
assert(!block_is_free(2+i));
|
||||
|
||||
// Make sure the reserved and root blocks are marked in-use.
|
||||
assert(!block_is_free(0));
|
||||
assert(!block_is_free(1));
|
||||
|
||||
cprintf("bitmap is good\n");
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// File system structures
|
||||
// --------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Initialize the file system
|
||||
void
|
||||
fs_init(void)
|
||||
{
|
||||
static_assert(sizeof(struct File) == 256);
|
||||
|
||||
// Find a JOS disk. Use the second IDE disk (number 1) if available
|
||||
if (ide_probe_disk1())
|
||||
ide_set_disk(1);
|
||||
else
|
||||
ide_set_disk(0);
|
||||
bc_init();
|
||||
|
||||
// Set "super" to point to the super block.
|
||||
super = diskaddr(1);
|
||||
check_super();
|
||||
|
||||
// Set "bitmap" to the beginning of the first bitmap block.
|
||||
bitmap = diskaddr(2);
|
||||
check_bitmap();
|
||||
|
||||
}
|
||||
|
||||
// Find the disk block number slot for the 'filebno'th block in file 'f'.
|
||||
// Set '*ppdiskbno' to point to that slot.
|
||||
// The slot will be one of the f->f_direct[] entries,
|
||||
// or an entry in the indirect block.
|
||||
// When 'alloc' is set, this function will allocate an indirect block
|
||||
// if necessary.
|
||||
//
|
||||
// Returns:
|
||||
// 0 on success (but note that *ppdiskbno might equal 0).
|
||||
// -E_NOT_FOUND if the function needed to allocate an indirect block, but
|
||||
// alloc was 0.
|
||||
// -E_NO_DISK if there's no space on the disk for an indirect block.
|
||||
// -E_INVAL if filebno is out of range (it's >= NDIRECT + NINDIRECT).
|
||||
//
|
||||
// Analogy: This is like pgdir_walk for files.
|
||||
// Hint: Don't forget to clear any block you allocate.
|
||||
static int
|
||||
file_block_walk(struct File *f, uint32_t filebno, uint32_t **ppdiskbno, bool alloc)
|
||||
{
|
||||
// LAB 5: Your code here.
|
||||
int newblock;
|
||||
if(filebno < NDIRECT) {
|
||||
*ppdiskbno = &f->f_direct[filebno];
|
||||
return 0;
|
||||
} else if(filebno < NDIRECT + NINDIRECT) {
|
||||
filebno -= NDIRECT;
|
||||
if(!f->f_indirect) {
|
||||
if(!alloc) return -E_NOT_FOUND;
|
||||
if((newblock = alloc_block()) < 0) return newblock;
|
||||
f->f_indirect = newblock;
|
||||
}
|
||||
|
||||
uint32_t* indirect = diskaddr(f->f_indirect);
|
||||
*ppdiskbno = &indirect[filebno];
|
||||
return 0;
|
||||
}
|
||||
return -E_INVAL;
|
||||
}
|
||||
|
||||
// Set *blk to the address in memory where the filebno'th
|
||||
// block of file 'f' would be mapped.
|
||||
//
|
||||
// Returns 0 on success, < 0 on error. Errors are:
|
||||
// -E_NO_DISK if a block needed to be allocated but the disk is full.
|
||||
// -E_INVAL if filebno is out of range.
|
||||
//
|
||||
// Hint: Use file_block_walk and alloc_block.
|
||||
int
|
||||
file_get_block(struct File *f, uint32_t filebno, char **blk)
|
||||
{
|
||||
// LAB 5: Your code here.
|
||||
uint32_t* blockno;
|
||||
int r;
|
||||
|
||||
if ((r = file_block_walk(f, filebno, &blockno, 1)) < 0)
|
||||
return r;
|
||||
|
||||
if(*blockno == 0) {
|
||||
if((r = alloc_block()) < 0) return r;
|
||||
*blockno = r;
|
||||
}
|
||||
|
||||
*blk = diskaddr(*blockno);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Try to find a file named "name" in dir. If so, set *file to it.
|
||||
//
|
||||
// Returns 0 and sets *file on success, < 0 on error. Errors are:
|
||||
// -E_NOT_FOUND if the file is not found
|
||||
static int
|
||||
dir_lookup(struct File *dir, const char *name, struct File **file)
|
||||
{
|
||||
int r;
|
||||
uint32_t i, j, nblock;
|
||||
char *blk;
|
||||
struct File *f;
|
||||
|
||||
// Search dir for name.
|
||||
// We maintain the invariant that the size of a directory-file
|
||||
// is always a multiple of the file system's block size.
|
||||
assert((dir->f_size % BLKSIZE) == 0);
|
||||
nblock = dir->f_size / BLKSIZE;
|
||||
for (i = 0; i < nblock; i++) {
|
||||
if ((r = file_get_block(dir, i, &blk)) < 0)
|
||||
return r;
|
||||
f = (struct File*) blk;
|
||||
for (j = 0; j < BLKFILES; j++)
|
||||
if (strcmp(f[j].f_name, name) == 0) {
|
||||
*file = &f[j];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -E_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Set *file to point at a free File structure in dir. The caller is
|
||||
// responsible for filling in the File fields.
|
||||
static int
|
||||
dir_alloc_file(struct File *dir, struct File **file)
|
||||
{
|
||||
int r;
|
||||
uint32_t nblock, i, j;
|
||||
char *blk;
|
||||
struct File *f;
|
||||
|
||||
assert((dir->f_size % BLKSIZE) == 0);
|
||||
nblock = dir->f_size / BLKSIZE;
|
||||
for (i = 0; i < nblock; i++) {
|
||||
if ((r = file_get_block(dir, i, &blk)) < 0)
|
||||
return r;
|
||||
f = (struct File*) blk;
|
||||
for (j = 0; j < BLKFILES; j++)
|
||||
if (f[j].f_name[0] == '\0') {
|
||||
*file = &f[j];
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
dir->f_size += BLKSIZE;
|
||||
if ((r = file_get_block(dir, i, &blk)) < 0)
|
||||
return r;
|
||||
f = (struct File*) blk;
|
||||
*file = &f[0];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip over slashes.
|
||||
static const char*
|
||||
skip_slash(const char *p)
|
||||
{
|
||||
while (*p == '/')
|
||||
p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Evaluate a path name, starting at the root.
|
||||
// On success, set *pf to the file we found
|
||||
// and set *pdir to the directory the file is in.
|
||||
// If we cannot find the file but find the directory
|
||||
// it should be in, set *pdir and copy the final path
|
||||
// element into lastelem.
|
||||
static int
|
||||
walk_path(const char *path, struct File **pdir, struct File **pf, char *lastelem)
|
||||
{
|
||||
const char *p;
|
||||
char name[MAXNAMELEN];
|
||||
struct File *dir, *f;
|
||||
int r;
|
||||
|
||||
// if (*path != '/')
|
||||
// return -E_BAD_PATH;
|
||||
path = skip_slash(path);
|
||||
f = &super->s_root;
|
||||
dir = 0;
|
||||
name[0] = 0;
|
||||
|
||||
if (pdir)
|
||||
*pdir = 0;
|
||||
*pf = 0;
|
||||
while (*path != '\0') {
|
||||
dir = f;
|
||||
p = path;
|
||||
while (*path != '/' && *path != '\0')
|
||||
path++;
|
||||
if (path - p >= MAXNAMELEN)
|
||||
return -E_BAD_PATH;
|
||||
memmove(name, p, path - p);
|
||||
name[path - p] = '\0';
|
||||
path = skip_slash(path);
|
||||
|
||||
if (dir->f_type != FTYPE_DIR)
|
||||
return -E_NOT_FOUND;
|
||||
|
||||
if ((r = dir_lookup(dir, name, &f)) < 0) {
|
||||
if (r == -E_NOT_FOUND && *path == '\0') {
|
||||
if (pdir)
|
||||
*pdir = dir;
|
||||
if (lastelem)
|
||||
strcpy(lastelem, name);
|
||||
*pf = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (pdir)
|
||||
*pdir = dir;
|
||||
*pf = f;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// File operations
|
||||
// --------------------------------------------------------------
|
||||
|
||||
// Create "path". On success set *pf to point at the file and return 0.
|
||||
// On error return < 0.
|
||||
int
|
||||
file_create(const char *path, struct File **pf)
|
||||
{
|
||||
char name[MAXNAMELEN];
|
||||
int r;
|
||||
struct File *dir, *f;
|
||||
|
||||
if ((r = walk_path(path, &dir, &f, name)) == 0)
|
||||
return -E_FILE_EXISTS;
|
||||
if (r != -E_NOT_FOUND || dir == 0)
|
||||
return r;
|
||||
if ((r = dir_alloc_file(dir, &f)) < 0)
|
||||
return r;
|
||||
|
||||
strcpy(f->f_name, name);
|
||||
*pf = f;
|
||||
file_flush(dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Open "path". On success set *pf to point at the file and return 0.
|
||||
// On error return < 0.
|
||||
int
|
||||
file_open(const char *path, struct File **pf)
|
||||
{
|
||||
return walk_path(path, 0, pf, 0);
|
||||
}
|
||||
|
||||
// Read count bytes from f into buf, starting from seek position
|
||||
// offset. This meant to mimic the standard pread function.
|
||||
// Returns the number of bytes read, < 0 on error.
|
||||
ssize_t
|
||||
file_read(struct File *f, void *buf, size_t count, off_t offset)
|
||||
{
|
||||
int r, bn;
|
||||
off_t pos;
|
||||
char *blk;
|
||||
|
||||
if (offset >= f->f_size)
|
||||
return 0;
|
||||
|
||||
count = MIN(count, f->f_size - offset);
|
||||
|
||||
for (pos = offset; pos < offset + count; ) {
|
||||
if ((r = file_get_block(f, pos / BLKSIZE, &blk)) < 0)
|
||||
return r;
|
||||
bn = MIN(BLKSIZE - pos % BLKSIZE, offset + count - pos);
|
||||
memmove(buf, blk + pos % BLKSIZE, bn);
|
||||
pos += bn;
|
||||
buf += bn;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
// Write count bytes from buf into f, starting at seek position
|
||||
// offset. This is meant to mimic the standard pwrite function.
|
||||
// Extends the file if necessary.
|
||||
// Returns the number of bytes written, < 0 on error.
|
||||
int
|
||||
file_write(struct File *f, const void *buf, size_t count, off_t offset)
|
||||
{
|
||||
int r, bn;
|
||||
off_t pos;
|
||||
char *blk;
|
||||
|
||||
// Extend file if necessary
|
||||
if (offset + count > f->f_size)
|
||||
if ((r = file_set_size(f, offset + count)) < 0)
|
||||
return r;
|
||||
|
||||
for (pos = offset; pos < offset + count; ) {
|
||||
if ((r = file_get_block(f, pos / BLKSIZE, &blk)) < 0)
|
||||
return r;
|
||||
bn = MIN(BLKSIZE - pos % BLKSIZE, offset + count - pos);
|
||||
memmove(blk + pos % BLKSIZE, buf, bn);
|
||||
pos += bn;
|
||||
buf += bn;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// Remove a block from file f. If it's not there, just silently succeed.
|
||||
// Returns 0 on success, < 0 on error.
|
||||
static int
|
||||
file_free_block(struct File *f, uint32_t filebno)
|
||||
{
|
||||
int r;
|
||||
uint32_t *ptr;
|
||||
|
||||
if ((r = file_block_walk(f, filebno, &ptr, 0)) < 0)
|
||||
return r;
|
||||
if (*ptr) {
|
||||
free_block(*ptr);
|
||||
*ptr = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Remove any blocks currently used by file 'f',
|
||||
// but not necessary for a file of size 'newsize'.
|
||||
// For both the old and new sizes, figure out the number of blocks required,
|
||||
// and then clear the blocks from new_nblocks to old_nblocks.
|
||||
// If the new_nblocks is no more than NDIRECT, and the indirect block has
|
||||
// been allocated (f->f_indirect != 0), then free the indirect block too.
|
||||
// (Remember to clear the f->f_indirect pointer so you'll know
|
||||
// whether it's valid!)
|
||||
// Do not change f->f_size.
|
||||
static void
|
||||
file_truncate_blocks(struct File *f, off_t newsize)
|
||||
{
|
||||
int r;
|
||||
uint32_t bno, old_nblocks, new_nblocks;
|
||||
|
||||
old_nblocks = (f->f_size + BLKSIZE - 1) / BLKSIZE;
|
||||
new_nblocks = (newsize + BLKSIZE - 1) / BLKSIZE;
|
||||
for (bno = new_nblocks; bno < old_nblocks; bno++)
|
||||
if ((r = file_free_block(f, bno)) < 0)
|
||||
cprintf("warning: file_free_block: %e", r);
|
||||
|
||||
if (new_nblocks <= NDIRECT && f->f_indirect) {
|
||||
free_block(f->f_indirect);
|
||||
f->f_indirect = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the size of file f, truncating or extending as necessary.
|
||||
int
|
||||
file_set_size(struct File *f, off_t newsize)
|
||||
{
|
||||
if (f->f_size > newsize)
|
||||
file_truncate_blocks(f, newsize);
|
||||
f->f_size = newsize;
|
||||
flush_block(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Flush the contents and metadata of file f out to disk.
|
||||
// Loop over all the blocks in file.
|
||||
// Translate the file block number into a disk block number
|
||||
// and then check whether that disk block is dirty. If so, write it out.
|
||||
void
|
||||
file_flush(struct File *f)
|
||||
{
|
||||
int i;
|
||||
uint32_t *pdiskbno;
|
||||
|
||||
for (i = 0; i < (f->f_size + BLKSIZE - 1) / BLKSIZE; i++) {
|
||||
if (file_block_walk(f, i, &pdiskbno, 0) < 0 ||
|
||||
pdiskbno == NULL || *pdiskbno == 0)
|
||||
continue;
|
||||
flush_block(diskaddr(*pdiskbno));
|
||||
}
|
||||
flush_block(f);
|
||||
if (f->f_indirect)
|
||||
flush_block(diskaddr(f->f_indirect));
|
||||
}
|
||||
|
||||
|
||||
// Sync the entire file system. A big hammer.
|
||||
void
|
||||
fs_sync(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 1; i < super->s_nblocks; i++)
|
||||
flush_block(diskaddr(i));
|
||||
}
|
||||
|
||||
49
fs/fs.h
Normal file
49
fs/fs.h
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <inc/fs.h>
|
||||
#include <inc/lib.h>
|
||||
|
||||
#define SECTSIZE 512 // bytes per disk sector
|
||||
#define BLKSECTS (BLKSIZE / SECTSIZE) // sectors per block
|
||||
|
||||
/* Disk block n, when in memory, is mapped into the file system
|
||||
* server's address space at DISKMAP + (n*BLKSIZE). */
|
||||
#define DISKMAP 0x10000000
|
||||
|
||||
/* Maximum disk size we can handle (3GB) */
|
||||
#define DISKSIZE 0xC0000000
|
||||
|
||||
struct Super *super; // superblock
|
||||
uint32_t *bitmap; // bitmap blocks mapped in memory
|
||||
|
||||
/* ide.c */
|
||||
bool ide_probe_disk1(void);
|
||||
void ide_set_disk(int diskno);
|
||||
void ide_set_partition(uint32_t first_sect, uint32_t nsect);
|
||||
int ide_read(uint32_t secno, void *dst, size_t nsecs);
|
||||
int ide_write(uint32_t secno, const void *src, size_t nsecs);
|
||||
|
||||
/* bc.c */
|
||||
void* diskaddr(uint32_t blockno);
|
||||
bool va_is_mapped(void *va);
|
||||
bool va_is_dirty(void *va);
|
||||
void flush_block(void *addr);
|
||||
void bc_init(void);
|
||||
|
||||
/* fs.c */
|
||||
void fs_init(void);
|
||||
int file_get_block(struct File *f, uint32_t file_blockno, char **pblk);
|
||||
int file_create(const char *path, struct File **f);
|
||||
int file_open(const char *path, struct File **f);
|
||||
ssize_t file_read(struct File *f, void *buf, size_t count, off_t offset);
|
||||
int file_write(struct File *f, const void *buf, size_t count, off_t offset);
|
||||
int file_set_size(struct File *f, off_t newsize);
|
||||
void file_flush(struct File *f);
|
||||
int file_remove(const char *path);
|
||||
void fs_sync(void);
|
||||
|
||||
/* int map_block(uint32_t); */
|
||||
bool block_is_free(uint32_t blockno);
|
||||
int alloc_block(void);
|
||||
|
||||
/* test.c */
|
||||
void fs_test(void);
|
||||
|
||||
244
fs/fsformat.c
Normal file
244
fs/fsformat.c
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* JOS file system format
|
||||
*/
|
||||
|
||||
// We don't actually want to define off_t!
|
||||
#define off_t xxx_off_t
|
||||
#define bool xxx_bool
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#undef off_t
|
||||
#undef bool
|
||||
|
||||
// Prevent inc/types.h, included from inc/fs.h,
|
||||
// from attempting to redefine types defined in the host's inttypes.h.
|
||||
#define JOS_INC_TYPES_H
|
||||
// Typedef the types that inc/mmu.h needs.
|
||||
typedef uint32_t physaddr_t;
|
||||
typedef uint32_t off_t;
|
||||
typedef int bool;
|
||||
|
||||
#include <inc/mmu.h>
|
||||
#include <inc/fs.h>
|
||||
|
||||
#define ROUNDUP(n, v) ((n) - 1 + (v) - ((n) - 1) % (v))
|
||||
#define MAX_DIR_ENTS 128
|
||||
|
||||
struct Dir
|
||||
{
|
||||
struct File *f;
|
||||
struct File *ents;
|
||||
int n;
|
||||
};
|
||||
|
||||
uint32_t nblocks;
|
||||
char *diskmap, *diskpos;
|
||||
struct Super *super;
|
||||
uint32_t *bitmap;
|
||||
|
||||
void
|
||||
panic(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
readn(int f, void *out, size_t n)
|
||||
{
|
||||
size_t p = 0;
|
||||
while (p < n) {
|
||||
ssize_t m = read(f, out + p, n - p);
|
||||
if (m < 0)
|
||||
panic("read: %s", strerror(errno));
|
||||
if (m == 0)
|
||||
panic("read: Unexpected EOF");
|
||||
p += m;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
blockof(void *pos)
|
||||
{
|
||||
return ((char*)pos - diskmap) / BLKSIZE;
|
||||
}
|
||||
|
||||
void *
|
||||
alloc(uint32_t bytes)
|
||||
{
|
||||
void *start = diskpos;
|
||||
diskpos += ROUNDUP(bytes, BLKSIZE);
|
||||
if (blockof(diskpos) >= nblocks)
|
||||
panic("out of disk blocks");
|
||||
return start;
|
||||
}
|
||||
|
||||
void
|
||||
opendisk(const char *name)
|
||||
{
|
||||
int r, diskfd, nbitblocks;
|
||||
|
||||
if ((diskfd = open(name, O_RDWR | O_CREAT, 0666)) < 0)
|
||||
panic("open %s: %s", name, strerror(errno));
|
||||
|
||||
if ((r = ftruncate(diskfd, 0)) < 0
|
||||
|| (r = ftruncate(diskfd, nblocks * BLKSIZE)) < 0)
|
||||
panic("truncate %s: %s", name, strerror(errno));
|
||||
|
||||
if ((diskmap = mmap(NULL, nblocks * BLKSIZE, PROT_READ|PROT_WRITE,
|
||||
MAP_SHARED, diskfd, 0)) == MAP_FAILED)
|
||||
panic("mmap %s: %s", name, strerror(errno));
|
||||
|
||||
close(diskfd);
|
||||
|
||||
diskpos = diskmap;
|
||||
alloc(BLKSIZE);
|
||||
super = alloc(BLKSIZE);
|
||||
super->s_magic = FS_MAGIC;
|
||||
super->s_nblocks = nblocks;
|
||||
super->s_root.f_type = FTYPE_DIR;
|
||||
strcpy(super->s_root.f_name, "/");
|
||||
|
||||
nbitblocks = (nblocks + BLKBITSIZE - 1) / BLKBITSIZE;
|
||||
bitmap = alloc(nbitblocks * BLKSIZE);
|
||||
memset(bitmap, 0xFF, nbitblocks * BLKSIZE);
|
||||
}
|
||||
|
||||
void
|
||||
finishdisk(void)
|
||||
{
|
||||
int r, i;
|
||||
|
||||
for (i = 0; i < blockof(diskpos); ++i)
|
||||
bitmap[i/32] &= ~(1<<(i%32));
|
||||
|
||||
if ((r = msync(diskmap, nblocks * BLKSIZE, MS_SYNC)) < 0)
|
||||
panic("msync: %s", strerror(errno));
|
||||
}
|
||||
|
||||
void
|
||||
finishfile(struct File *f, uint32_t start, uint32_t len)
|
||||
{
|
||||
int i;
|
||||
f->f_size = len;
|
||||
len = ROUNDUP(len, BLKSIZE);
|
||||
for (i = 0; i < len / BLKSIZE && i < NDIRECT; ++i)
|
||||
f->f_direct[i] = start + i;
|
||||
if (i == NDIRECT) {
|
||||
uint32_t *ind = alloc(BLKSIZE);
|
||||
f->f_indirect = blockof(ind);
|
||||
for (; i < len / BLKSIZE; ++i)
|
||||
ind[i - NDIRECT] = start + i;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
startdir(struct File *f, struct Dir *dout)
|
||||
{
|
||||
dout->f = f;
|
||||
dout->ents = malloc(MAX_DIR_ENTS * sizeof *dout->ents);
|
||||
dout->n = 0;
|
||||
}
|
||||
|
||||
struct File *
|
||||
diradd(struct Dir *d, uint32_t type, const char *name)
|
||||
{
|
||||
struct File *out = &d->ents[d->n++];
|
||||
if (d->n > MAX_DIR_ENTS)
|
||||
panic("too many directory entries");
|
||||
strcpy(out->f_name, name);
|
||||
out->f_type = type;
|
||||
return out;
|
||||
}
|
||||
|
||||
void
|
||||
finishdir(struct Dir *d)
|
||||
{
|
||||
int size = d->n * sizeof(struct File);
|
||||
struct File *start = alloc(size);
|
||||
memmove(start, d->ents, size);
|
||||
finishfile(d->f, blockof(start), ROUNDUP(size, BLKSIZE));
|
||||
free(d->ents);
|
||||
d->ents = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
writefile(struct Dir *dir, const char *name)
|
||||
{
|
||||
int r, fd;
|
||||
struct File *f;
|
||||
struct stat st;
|
||||
const char *last;
|
||||
char *start;
|
||||
|
||||
if ((fd = open(name, O_RDONLY)) < 0)
|
||||
panic("open %s: %s", name, strerror(errno));
|
||||
if ((r = fstat(fd, &st)) < 0)
|
||||
panic("stat %s: %s", name, strerror(errno));
|
||||
if (!S_ISREG(st.st_mode))
|
||||
panic("%s is not a regular file", name);
|
||||
if (st.st_size >= MAXFILESIZE)
|
||||
panic("%s too large", name);
|
||||
|
||||
last = strrchr(name, '/');
|
||||
if (last)
|
||||
last++;
|
||||
else
|
||||
last = name;
|
||||
|
||||
f = diradd(dir, FTYPE_REG, last);
|
||||
start = alloc(st.st_size);
|
||||
readn(fd, start, st.st_size);
|
||||
finishfile(f, blockof(start), st.st_size);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
fprintf(stderr, "Usage: fsformat fs.img NBLOCKS files...\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
char *s;
|
||||
struct Dir root;
|
||||
|
||||
assert(BLKSIZE % sizeof(struct File) == 0);
|
||||
|
||||
if (argc < 3)
|
||||
usage();
|
||||
|
||||
nblocks = strtol(argv[2], &s, 0);
|
||||
if (*s || s == argv[2] || nblocks < 2 || nblocks > 1024)
|
||||
usage();
|
||||
|
||||
opendisk(argv[1]);
|
||||
|
||||
startdir(&super->s_root, &root);
|
||||
for (i = 3; i < argc; i++)
|
||||
writefile(&root, argv[i]);
|
||||
finishdir(&root);
|
||||
|
||||
finishdisk();
|
||||
return 0;
|
||||
}
|
||||
|
||||
112
fs/ide.c
Normal file
112
fs/ide.c
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Minimal PIO-based (non-interrupt-driven) IDE driver code.
|
||||
* For information about what all this IDE/ATA magic means,
|
||||
* see the materials available on the class references page.
|
||||
*/
|
||||
|
||||
#include "fs.h"
|
||||
#include <inc/x86.h>
|
||||
|
||||
#define IDE_BSY 0x80
|
||||
#define IDE_DRDY 0x40
|
||||
#define IDE_DF 0x20
|
||||
#define IDE_ERR 0x01
|
||||
|
||||
static int diskno = 1;
|
||||
|
||||
static int
|
||||
ide_wait_ready(bool check_error)
|
||||
{
|
||||
int r;
|
||||
|
||||
while (((r = inb(0x1F7)) & (IDE_BSY|IDE_DRDY)) != IDE_DRDY)
|
||||
/* do nothing */;
|
||||
|
||||
if (check_error && (r & (IDE_DF|IDE_ERR)) != 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
ide_probe_disk1(void)
|
||||
{
|
||||
int r, x;
|
||||
|
||||
// wait for Device 0 to be ready
|
||||
ide_wait_ready(0);
|
||||
|
||||
// switch to Device 1
|
||||
outb(0x1F6, 0xE0 | (1<<4));
|
||||
|
||||
// check for Device 1 to be ready for a while
|
||||
for (x = 0;
|
||||
x < 1000 && ((r = inb(0x1F7)) & (IDE_BSY|IDE_DF|IDE_ERR)) != 0;
|
||||
x++)
|
||||
/* do nothing */;
|
||||
|
||||
// switch back to Device 0
|
||||
outb(0x1F6, 0xE0 | (0<<4));
|
||||
|
||||
cprintf("Device 1 presence: %d\n", (x < 1000));
|
||||
return (x < 1000);
|
||||
}
|
||||
|
||||
void
|
||||
ide_set_disk(int d)
|
||||
{
|
||||
if (d != 0 && d != 1)
|
||||
panic("bad disk number");
|
||||
diskno = d;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
ide_read(uint32_t secno, void *dst, size_t nsecs)
|
||||
{
|
||||
int r;
|
||||
|
||||
assert(nsecs <= 256);
|
||||
|
||||
ide_wait_ready(0);
|
||||
|
||||
outb(0x1F2, nsecs);
|
||||
outb(0x1F3, secno & 0xFF);
|
||||
outb(0x1F4, (secno >> 8) & 0xFF);
|
||||
outb(0x1F5, (secno >> 16) & 0xFF);
|
||||
outb(0x1F6, 0xE0 | ((diskno&1)<<4) | ((secno>>24)&0x0F));
|
||||
outb(0x1F7, 0x20); // CMD 0x20 means read sector
|
||||
|
||||
for (; nsecs > 0; nsecs--, dst += SECTSIZE) {
|
||||
if ((r = ide_wait_ready(1)) < 0)
|
||||
return r;
|
||||
insl(0x1F0, dst, SECTSIZE/4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ide_write(uint32_t secno, const void *src, size_t nsecs)
|
||||
{
|
||||
int r;
|
||||
|
||||
assert(nsecs <= 256);
|
||||
|
||||
ide_wait_ready(0);
|
||||
|
||||
outb(0x1F2, nsecs);
|
||||
outb(0x1F3, secno & 0xFF);
|
||||
outb(0x1F4, (secno >> 8) & 0xFF);
|
||||
outb(0x1F5, (secno >> 16) & 0xFF);
|
||||
outb(0x1F6, 0xE0 | ((diskno&1)<<4) | ((secno>>24)&0x0F));
|
||||
outb(0x1F7, 0x30); // CMD 0x30 means write sector
|
||||
|
||||
for (; nsecs > 0; nsecs--, src += SECTSIZE) {
|
||||
if ((r = ide_wait_ready(1)) < 0)
|
||||
return r;
|
||||
outsl(0x1F0, src, SECTSIZE/4);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
12
fs/lorem
Normal file
12
fs/lorem
Normal file
@@ -0,0 +1,12 @@
|
||||
Lorem ipsum dolor sit amet, consectetur
|
||||
adipisicing elit, sed do eiusmod tempor
|
||||
incididunt ut labore et dolore magna
|
||||
aliqua. Ut enim ad minim veniam, quis
|
||||
nostrud exercitation ullamco laboris
|
||||
nisi ut aliquip ex ea commodo consequat.
|
||||
Duis aute irure dolor in reprehenderit
|
||||
in voluptate velit esse cillum dolore eu
|
||||
fugiat nulla pariatur. Excepteur sint
|
||||
occaecat cupidatat non proident, sunt in
|
||||
culpa qui officia deserunt mollit anim
|
||||
id est laborum.
|
||||
4
fs/motd
Normal file
4
fs/motd
Normal file
@@ -0,0 +1,4 @@
|
||||
This is /motd, the message of the day.
|
||||
|
||||
Welcome to the JOS kernel, now with a file system!
|
||||
|
||||
2
fs/newmotd
Normal file
2
fs/newmotd
Normal file
@@ -0,0 +1,2 @@
|
||||
This is the NEW message of the day!
|
||||
|
||||
5
fs/script
Normal file
5
fs/script
Normal file
@@ -0,0 +1,5 @@
|
||||
echo This is from the script.
|
||||
cat lorem | num | cat
|
||||
echo These are my file descriptors.
|
||||
lsfd -1
|
||||
echo This is the end of the script.
|
||||
361
fs/serv.c
Normal file
361
fs/serv.c
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* File system server main loop -
|
||||
* serves IPC requests from other environments.
|
||||
*/
|
||||
|
||||
#include <inc/x86.h>
|
||||
#include <inc/string.h>
|
||||
|
||||
#include "fs.h"
|
||||
|
||||
|
||||
#define debug 0
|
||||
|
||||
// The file system server maintains three structures
|
||||
// for each open file.
|
||||
//
|
||||
// 1. The on-disk 'struct File' is mapped into the part of memory
|
||||
// that maps the disk. This memory is kept private to the file
|
||||
// server.
|
||||
// 2. Each open file has a 'struct Fd' as well, which sort of
|
||||
// corresponds to a Unix file descriptor. This 'struct Fd' is kept
|
||||
// on *its own page* in memory, and it is shared with any
|
||||
// environments that have the file open.
|
||||
// 3. 'struct OpenFile' links these other two structures, and is kept
|
||||
// private to the file server. The server maintains an array of
|
||||
// all open files, indexed by "file ID". (There can be at most
|
||||
// MAXOPEN files open concurrently.) The client uses file IDs to
|
||||
// communicate with the server. File IDs are a lot like
|
||||
// environment IDs in the kernel. Use openfile_lookup to translate
|
||||
// file IDs to struct OpenFile.
|
||||
|
||||
struct OpenFile {
|
||||
uint32_t o_fileid; // file id
|
||||
struct File *o_file; // mapped descriptor for open file
|
||||
int o_mode; // open mode
|
||||
struct Fd *o_fd; // Fd page
|
||||
};
|
||||
|
||||
// Max number of open files in the file system at once
|
||||
#define MAXOPEN 1024
|
||||
#define FILEVA 0xD0000000
|
||||
|
||||
// initialize to force into data section
|
||||
struct OpenFile opentab[MAXOPEN] = {
|
||||
{ 0, 0, 1, 0 }
|
||||
};
|
||||
|
||||
// Virtual address at which to receive page mappings containing client requests.
|
||||
union Fsipc *fsreq = (union Fsipc *)0x0ffff000;
|
||||
|
||||
void
|
||||
serve_init(void)
|
||||
{
|
||||
int i;
|
||||
uintptr_t va = FILEVA;
|
||||
for (i = 0; i < MAXOPEN; i++) {
|
||||
opentab[i].o_fileid = i;
|
||||
opentab[i].o_fd = (struct Fd*) va;
|
||||
va += PGSIZE;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate an open file.
|
||||
int
|
||||
openfile_alloc(struct OpenFile **o)
|
||||
{
|
||||
int i, r;
|
||||
|
||||
// Find an available open-file table entry
|
||||
for (i = 0; i < MAXOPEN; i++) {
|
||||
switch (pageref(opentab[i].o_fd)) {
|
||||
case 0:
|
||||
if ((r = sys_page_alloc(0, opentab[i].o_fd, PTE_P|PTE_U|PTE_W)) < 0)
|
||||
return r;
|
||||
/* fall through */
|
||||
case 1:
|
||||
opentab[i].o_fileid += MAXOPEN;
|
||||
*o = &opentab[i];
|
||||
memset(opentab[i].o_fd, 0, PGSIZE);
|
||||
return (*o)->o_fileid;
|
||||
}
|
||||
}
|
||||
return -E_MAX_OPEN;
|
||||
}
|
||||
|
||||
// Look up an open file for envid.
|
||||
int
|
||||
openfile_lookup(envid_t envid, uint32_t fileid, struct OpenFile **po)
|
||||
{
|
||||
struct OpenFile *o;
|
||||
|
||||
o = &opentab[fileid % MAXOPEN];
|
||||
if (pageref(o->o_fd) <= 1 || o->o_fileid != fileid)
|
||||
return -E_INVAL;
|
||||
*po = o;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Open req->req_path in mode req->req_omode, storing the Fd page and
|
||||
// permissions to return to the calling environment in *pg_store and
|
||||
// *perm_store respectively.
|
||||
int
|
||||
serve_open(envid_t envid, struct Fsreq_open *req,
|
||||
void **pg_store, int *perm_store)
|
||||
{
|
||||
char path[MAXPATHLEN];
|
||||
struct File *f;
|
||||
int fileid;
|
||||
int r;
|
||||
struct OpenFile *o;
|
||||
|
||||
if (debug)
|
||||
cprintf("serve_open %08x %s 0x%x\n", envid, req->req_path, req->req_omode);
|
||||
|
||||
// Copy in the path, making sure it's null-terminated
|
||||
memmove(path, req->req_path, MAXPATHLEN);
|
||||
path[MAXPATHLEN-1] = 0;
|
||||
|
||||
// Find an open file ID
|
||||
if ((r = openfile_alloc(&o)) < 0) {
|
||||
if (debug)
|
||||
cprintf("openfile_alloc failed: %e", r);
|
||||
return r;
|
||||
}
|
||||
fileid = r;
|
||||
|
||||
// Open the file
|
||||
if (req->req_omode & O_CREAT) {
|
||||
if ((r = file_create(path, &f)) < 0) {
|
||||
if (!(req->req_omode & O_EXCL) && r == -E_FILE_EXISTS)
|
||||
goto try_open;
|
||||
if (debug)
|
||||
cprintf("file_create failed: %e", r);
|
||||
return r;
|
||||
}
|
||||
} else {
|
||||
try_open:
|
||||
if ((r = file_open(path, &f)) < 0) {
|
||||
if (debug)
|
||||
cprintf("file_open failed: %e", r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate
|
||||
if (req->req_omode & O_TRUNC) {
|
||||
if ((r = file_set_size(f, 0)) < 0) {
|
||||
if (debug)
|
||||
cprintf("file_set_size failed: %e", r);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
if ((r = file_open(path, &f)) < 0) {
|
||||
if (debug)
|
||||
cprintf("file_open failed: %e", r);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Save the file pointer
|
||||
o->o_file = f;
|
||||
|
||||
// Fill out the Fd structure
|
||||
o->o_fd->fd_file.id = o->o_fileid;
|
||||
o->o_fd->fd_omode = req->req_omode & O_ACCMODE;
|
||||
o->o_fd->fd_dev_id = devfile.dev_id;
|
||||
o->o_mode = req->req_omode;
|
||||
|
||||
if (debug)
|
||||
cprintf("sending success, page %08x\n", (uintptr_t) o->o_fd);
|
||||
|
||||
// Share the FD page with the caller by setting *pg_store,
|
||||
// store its permission in *perm_store
|
||||
*pg_store = o->o_fd;
|
||||
*perm_store = PTE_P|PTE_U|PTE_W|PTE_SHARE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set the size of req->req_fileid to req->req_size bytes, truncating
|
||||
// or extending the file as necessary.
|
||||
int
|
||||
serve_set_size(envid_t envid, struct Fsreq_set_size *req)
|
||||
{
|
||||
struct OpenFile *o;
|
||||
int r;
|
||||
|
||||
if (debug)
|
||||
cprintf("serve_set_size %08x %08x %08x\n", envid, req->req_fileid, req->req_size);
|
||||
|
||||
// Every file system IPC call has the same general structure.
|
||||
// Here's how it goes.
|
||||
|
||||
// First, use openfile_lookup to find the relevant open file.
|
||||
// On failure, return the error code to the client with ipc_send.
|
||||
if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
|
||||
return r;
|
||||
|
||||
// Second, call the relevant file system function (from fs/fs.c).
|
||||
// On failure, return the error code to the client.
|
||||
return file_set_size(o->o_file, req->req_size);
|
||||
}
|
||||
|
||||
// Read at most ipc->read.req_n bytes from the current seek position
|
||||
// in ipc->read.req_fileid. Return the bytes read from the file to
|
||||
// the caller in ipc->readRet, then update the seek position. Returns
|
||||
// the number of bytes successfully read, or < 0 on error.
|
||||
int
|
||||
serve_read(envid_t envid, union Fsipc *ipc)
|
||||
{
|
||||
struct Fsreq_read *req = &ipc->read;
|
||||
struct Fsret_read *ret = &ipc->readRet;
|
||||
struct OpenFile* open_file;
|
||||
int r;
|
||||
|
||||
if (debug)
|
||||
cprintf("serve_read %08x %08x %08x\n", envid, req->req_fileid, req->req_n);
|
||||
|
||||
if((r = openfile_lookup(envid, req->req_fileid, &open_file)) < 0)
|
||||
return r;
|
||||
|
||||
if((r = file_read(open_file->o_file, ret->ret_buf, req->req_n, open_file->o_fd->fd_offset)) < 0)
|
||||
return r;
|
||||
|
||||
open_file->o_fd->fd_offset += r;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
// Write req->req_n bytes from req->req_buf to req_fileid, starting at
|
||||
// the current seek position, and update the seek position
|
||||
// accordingly. Extend the file if necessary. Returns the number of
|
||||
// bytes written, or < 0 on error.
|
||||
int
|
||||
serve_write(envid_t envid, struct Fsreq_write *req)
|
||||
{
|
||||
if (debug)
|
||||
cprintf("serve_write %08x %08x %08x\n", envid, req->req_fileid, req->req_n);
|
||||
|
||||
struct OpenFile* open_file;
|
||||
int r;
|
||||
|
||||
if((r = openfile_lookup(envid, req->req_fileid, &open_file)) < 0)
|
||||
return r;
|
||||
if((r = file_write(open_file->o_file, req->req_buf, req->req_n, open_file->o_fd->fd_offset)) < 0)
|
||||
return r;
|
||||
|
||||
open_file->o_fd->fd_offset += r;
|
||||
return r;
|
||||
}
|
||||
|
||||
// Stat ipc->stat.req_fileid. Return the file's struct Stat to the
|
||||
// caller in ipc->statRet.
|
||||
int
|
||||
serve_stat(envid_t envid, union Fsipc *ipc)
|
||||
{
|
||||
struct Fsreq_stat *req = &ipc->stat;
|
||||
struct Fsret_stat *ret = &ipc->statRet;
|
||||
struct OpenFile *o;
|
||||
int r;
|
||||
|
||||
if (debug)
|
||||
cprintf("serve_stat %08x %08x\n", envid, req->req_fileid);
|
||||
|
||||
if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
|
||||
return r;
|
||||
|
||||
strcpy(ret->ret_name, o->o_file->f_name);
|
||||
ret->ret_size = o->o_file->f_size;
|
||||
ret->ret_isdir = (o->o_file->f_type == FTYPE_DIR);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Flush all data and metadata of req->req_fileid to disk.
|
||||
int
|
||||
serve_flush(envid_t envid, struct Fsreq_flush *req)
|
||||
{
|
||||
struct OpenFile *o;
|
||||
int r;
|
||||
|
||||
if (debug)
|
||||
cprintf("serve_flush %08x %08x\n", envid, req->req_fileid);
|
||||
|
||||
if ((r = openfile_lookup(envid, req->req_fileid, &o)) < 0)
|
||||
return r;
|
||||
file_flush(o->o_file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
serve_sync(envid_t envid, union Fsipc *req)
|
||||
{
|
||||
fs_sync();
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef int (*fshandler)(envid_t envid, union Fsipc *req);
|
||||
|
||||
fshandler handlers[] = {
|
||||
// Open is handled specially because it passes pages
|
||||
/* [FSREQ_OPEN] = (fshandler)serve_open, */
|
||||
[FSREQ_READ] = serve_read,
|
||||
[FSREQ_STAT] = serve_stat,
|
||||
[FSREQ_FLUSH] = (fshandler)serve_flush,
|
||||
[FSREQ_WRITE] = (fshandler)serve_write,
|
||||
[FSREQ_SET_SIZE] = (fshandler)serve_set_size,
|
||||
[FSREQ_SYNC] = serve_sync
|
||||
};
|
||||
|
||||
void
|
||||
serve(void)
|
||||
{
|
||||
uint32_t req, whom;
|
||||
int perm, r;
|
||||
void *pg;
|
||||
|
||||
while (1) {
|
||||
perm = 0;
|
||||
req = ipc_recv((int32_t *) &whom, fsreq, &perm);
|
||||
if (debug)
|
||||
cprintf("fs req %d from %08x [page %08x: %s]\n",
|
||||
req, whom, uvpt[PGNUM(fsreq)], fsreq);
|
||||
|
||||
// All requests must contain an argument page
|
||||
if (!(perm & PTE_P)) {
|
||||
cprintf("Invalid request from %08x: no argument page\n",
|
||||
whom);
|
||||
continue; // just leave it hanging...
|
||||
}
|
||||
|
||||
pg = NULL;
|
||||
if (req == FSREQ_OPEN) {
|
||||
r = serve_open(whom, (struct Fsreq_open*)fsreq, &pg, &perm);
|
||||
} else if (req < ARRAY_SIZE(handlers) && handlers[req]) {
|
||||
r = handlers[req](whom, fsreq);
|
||||
} else {
|
||||
cprintf("Invalid request code %d from %08x\n", req, whom);
|
||||
r = -E_INVAL;
|
||||
}
|
||||
ipc_send(whom, r, pg, perm);
|
||||
sys_page_unmap(0, fsreq);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
static_assert(sizeof(struct File) == 256);
|
||||
binaryname = "fs";
|
||||
cprintf("FS is running\n");
|
||||
|
||||
// Check that we are able to do I/O
|
||||
outw(0x8A00, 0x8A00);
|
||||
cprintf("FS can do I/O\n");
|
||||
|
||||
serve_init();
|
||||
fs_init();
|
||||
fs_test();
|
||||
serve();
|
||||
}
|
||||
|
||||
7
fs/testshell.sh
Normal file
7
fs/testshell.sh
Normal file
@@ -0,0 +1,7 @@
|
||||
echo hello world | cat
|
||||
cat lorem
|
||||
cat lorem |num
|
||||
cat lorem |num |num |num |num |num
|
||||
lsfd -1
|
||||
cat script
|
||||
sh <script
|
||||
190
grade-lab4
Executable file
190
grade-lab4
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import re
|
||||
from gradelib import *
|
||||
|
||||
r = Runner(save("jos.out"),
|
||||
stop_breakpoint("readline"))
|
||||
|
||||
def E(s, trim=False):
|
||||
"""Expand $En in s to the environment ID of the n'th user
|
||||
environment."""
|
||||
|
||||
tmpl = "%x" if trim else "%08x"
|
||||
return re.sub(r"\$E([0-9]+)",
|
||||
lambda m: tmpl % (0x1000 + int(m.group(1))-1), s)
|
||||
|
||||
@test(5)
|
||||
def test_dumbfork():
|
||||
r.user_test("dumbfork")
|
||||
r.match(E(".00000000. new env $E1"),
|
||||
E(".$E1. new env $E2"),
|
||||
"0: I am the parent.",
|
||||
"9: I am the parent.",
|
||||
"0: I am the child.",
|
||||
"9: I am the child.",
|
||||
"19: I am the child.",
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E1. free env $E1"),
|
||||
E(".$E2. exiting gracefully"),
|
||||
E(".$E2. free env $E2"))
|
||||
|
||||
end_part("A")
|
||||
|
||||
@test(5)
|
||||
def test_faultread():
|
||||
r.user_test("faultread")
|
||||
r.match(E(".$E1. user fault va 00000000 ip 008....."),
|
||||
"TRAP frame at 0xf....... from CPU .",
|
||||
" trap 0x0000000e Page Fault",
|
||||
" err 0x00000004.*",
|
||||
E(".$E1. free env $E1"),
|
||||
no=["I read ........ from location 0."])
|
||||
|
||||
@test(5)
|
||||
def test_faultwrite():
|
||||
r.user_test("faultwrite")
|
||||
r.match(E(".$E1. user fault va 00000000 ip 008....."),
|
||||
"TRAP frame at 0xf....... from CPU .",
|
||||
" trap 0x0000000e Page Fault",
|
||||
" err 0x00000006.*",
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_faultdie():
|
||||
r.user_test("faultdie")
|
||||
r.match("i faulted at va deadbeef, err 6",
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_faultregs():
|
||||
r.user_test("faultregs")
|
||||
r.match("Registers in UTrapframe OK",
|
||||
"Registers after page-fault OK",
|
||||
no=["Registers in UTrapframe MISMATCH",
|
||||
"Registers after page-fault MISMATCH"])
|
||||
|
||||
@test(5)
|
||||
def test_faultalloc():
|
||||
r.user_test("faultalloc")
|
||||
r.match("fault deadbeef",
|
||||
"this string was faulted in at deadbeef",
|
||||
"fault cafebffe",
|
||||
"fault cafec000",
|
||||
"this string was faulted in at cafebffe",
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_faultallocbad():
|
||||
r.user_test("faultallocbad")
|
||||
r.match(E(".$E1. user_mem_check assertion failure for va deadbeef"),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_faultnostack():
|
||||
r.user_test("faultnostack")
|
||||
r.match(E(".$E1. user_mem_check assertion failure for va eebfff.."),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_faultbadhandler():
|
||||
r.user_test("faultbadhandler")
|
||||
r.match(E(".$E1. user_mem_check assertion failure for va (deadb|eebfe)..."),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_faultevilhandler():
|
||||
r.user_test("faultevilhandler")
|
||||
r.match(E(".$E1. user_mem_check assertion failure for va (f0100|eebfe)..."),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_forktree():
|
||||
r.user_test("forktree")
|
||||
r.match("....: I am .0.",
|
||||
"....: I am .1.",
|
||||
"....: I am .000.",
|
||||
"....: I am .100.",
|
||||
"....: I am .110.",
|
||||
"....: I am .111.",
|
||||
"....: I am .011.",
|
||||
"....: I am .001.",
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E2. exiting gracefully"),
|
||||
".0000200.. exiting gracefully",
|
||||
".0000200.. free env 0000200.")
|
||||
|
||||
end_part("B")
|
||||
|
||||
@test(5)
|
||||
def test_spin():
|
||||
r.user_test("spin")
|
||||
r.match(E(".00000000. new env $E1"),
|
||||
"I am the parent. Forking the child...",
|
||||
E(".$E1. new env $E2"),
|
||||
"I am the parent. Running the child...",
|
||||
"I am the child. Spinning...",
|
||||
"I am the parent. Killing the child...",
|
||||
E(".$E1. destroying $E2"),
|
||||
E(".$E1. free env $E2"),
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E1. free env $E1"))
|
||||
|
||||
@test(5)
|
||||
def test_stresssched():
|
||||
r.user_test("stresssched", make_args=["CPUS=4"])
|
||||
r.match(".000010... stresssched on CPU 0",
|
||||
".000010... stresssched on CPU 1",
|
||||
".000010... stresssched on CPU 2",
|
||||
".000010... stresssched on CPU 3",
|
||||
no=[".*ran on two CPUs at once"])
|
||||
|
||||
@test(5)
|
||||
def test_sendpage():
|
||||
r.user_test("sendpage", make_args=["CPUS=2"])
|
||||
r.match(".00000000. new env 00001000",
|
||||
E(".00000000. new env $E1"),
|
||||
E(".$E1. new env $E2"),
|
||||
E("$E1 got message: hello child environment! how are you?", trim=True),
|
||||
E("child received correct message", trim=True),
|
||||
E("$E2 got message: hello parent environment! I'm good", trim=True),
|
||||
E("parent received correct message", trim=True),
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E1. free env $E1"),
|
||||
E(".$E2. exiting gracefully"),
|
||||
E(".$E2. free env $E2"))
|
||||
|
||||
@test(5)
|
||||
def test_pingpong():
|
||||
r.user_test("pingpong", make_args=["CPUS=4"])
|
||||
r.match(E(".00000000. new env $E1"),
|
||||
E(".$E1. new env $E2"),
|
||||
E("send 0 from $E1 to $E2", trim=True),
|
||||
E("$E2 got 0 from $E1", trim=True),
|
||||
E("$E1 got 1 from $E2", trim=True),
|
||||
E("$E2 got 8 from $E1", trim=True),
|
||||
E("$E1 got 9 from $E2", trim=True),
|
||||
E("$E2 got 10 from $E1", trim=True),
|
||||
E(".$E1. exiting gracefully"),
|
||||
E(".$E1. free env $E1"),
|
||||
E(".$E2. exiting gracefully"),
|
||||
E(".$E2. free env $E2"))
|
||||
|
||||
@test(5)
|
||||
def test_primes():
|
||||
r.user_test("primes", stop_on_line("CPU .: 1877"), stop_on_line(".*panic"),
|
||||
make_args=["CPUS=4"], timeout=60)
|
||||
r.match(E(".00000000. new env $E1"),
|
||||
E(".$E1. new env $E2"),
|
||||
E("CPU .: 2 .$E2. new env $E3"),
|
||||
E("CPU .: 3 .$E3. new env $E4"),
|
||||
E("CPU .: 5 .$E4. new env $E5"),
|
||||
E("CPU .: 7 .$E5. new env $E6"),
|
||||
E("CPU .: 11 .$E6. new env $E7"),
|
||||
E("CPU .: 1877 .$E289. new env $E290"))
|
||||
|
||||
end_part("C")
|
||||
|
||||
run_tests()
|
||||
112
grade-lab5
Executable file
112
grade-lab5
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from gradelib import *
|
||||
|
||||
r = Runner(save("jos.out"),
|
||||
stop_breakpoint("readline"))
|
||||
|
||||
def matchtest(parent, name, *args, **kw):
|
||||
def do_test():
|
||||
r.match(*args, **kw)
|
||||
test(5, name, parent=parent)(do_test)
|
||||
|
||||
@test(5, "internal FS tests [fs/test.c]")
|
||||
def test_fs():
|
||||
r.user_test("hello")
|
||||
matchtest(test_fs, "fs i/o",
|
||||
"FS can do I/O")
|
||||
matchtest(test_fs, "check_bc",
|
||||
"block cache is good")
|
||||
matchtest(test_fs, "check_super",
|
||||
"superblock is good")
|
||||
matchtest(test_fs, "check_bitmap",
|
||||
"bitmap is good")
|
||||
matchtest(test_fs, "alloc_block",
|
||||
"alloc_block is good")
|
||||
matchtest(test_fs, "file_open",
|
||||
"file_open is good")
|
||||
matchtest(test_fs, "file_get_block",
|
||||
"file_get_block is good")
|
||||
matchtest(test_fs, "file_flush/file_truncate/file rewrite",
|
||||
"file_flush is good",
|
||||
"file_truncate is good",
|
||||
"file rewrite is good")
|
||||
|
||||
@test(10, "testfile")
|
||||
def test_testfile():
|
||||
r.user_test("testfile")
|
||||
matchtest(test_testfile, "serve_open/file_stat/file_close",
|
||||
"serve_open is good",
|
||||
"file_stat is good",
|
||||
"file_close is good",
|
||||
"stale fileid is good")
|
||||
matchtest(test_testfile, "file_read",
|
||||
"file_read is good")
|
||||
matchtest(test_testfile, "file_write",
|
||||
"file_write is good")
|
||||
matchtest(test_testfile, "file_read after file_write",
|
||||
"file_read after file_write is good")
|
||||
matchtest(test_testfile, "open",
|
||||
"open is good")
|
||||
matchtest(test_testfile, "large file",
|
||||
"large file is good")
|
||||
|
||||
@test(10, "spawn via spawnhello")
|
||||
def test_spawn():
|
||||
r.user_test("spawnhello")
|
||||
r.match('i am parent environment 00001001',
|
||||
'hello, world',
|
||||
'i am environment 00001002',
|
||||
'No runnable environments in the system!')
|
||||
|
||||
@test(5, "Protection I/O space")
|
||||
def test_faultio():
|
||||
r.user_test("spawnfaultio")
|
||||
r.match('TRAP')
|
||||
|
||||
@test(10, "PTE_SHARE [testpteshare]")
|
||||
def test_pte_share():
|
||||
r.user_test("testpteshare")
|
||||
r.match('fork handles PTE_SHARE right',
|
||||
'spawn handles PTE_SHARE right')
|
||||
|
||||
@test(5, "PTE_SHARE [testfdsharing]")
|
||||
def test_fd_share():
|
||||
r.user_test("testfdsharing")
|
||||
r.match('read in child succeeded',
|
||||
'read in parent succeeded')
|
||||
|
||||
@test(10, "start the shell [icode]")
|
||||
def test_icode():
|
||||
r.user_test("icode")
|
||||
r.match('icode: read /motd',
|
||||
'This is /motd, the message of the day.',
|
||||
'icode: spawn /init',
|
||||
'init: running',
|
||||
'init: data seems okay',
|
||||
'icode: exiting',
|
||||
'init: bss seems okay',
|
||||
"init: args: 'init' 'initarg1' 'initarg2'",
|
||||
'init: running sh',
|
||||
'\$ ')
|
||||
|
||||
@test(15)
|
||||
def test_testshell():
|
||||
r.user_test("testshell", timeout=60)
|
||||
r.match("shell ran correctly")
|
||||
|
||||
def gen_primes(n):
|
||||
rest = range(2, n)
|
||||
while rest:
|
||||
yield rest[0]
|
||||
rest = [n for n in rest if n % rest[0]]
|
||||
|
||||
@test(10)
|
||||
def test_primespipe():
|
||||
r.user_test("primespipe", stop_on_line("[0-9]{4}$"), timeout=120)
|
||||
primes = set(gen_primes(1000))
|
||||
nonprimes = set(range(1000)) - primes
|
||||
r.match(no=["%d$" % np for np in nonprimes],
|
||||
*["%d$" % p for p in primes])
|
||||
|
||||
run_tests()
|
||||
82
inc/args.h
Normal file
82
inc/args.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef JOS_INC_ARGS_H
|
||||
#define JOS_INC_ARGS_H
|
||||
|
||||
struct Argstate;
|
||||
|
||||
// JOS command-line parsing functions.
|
||||
|
||||
// Initializes the Argstate buffer from argc and argv.
|
||||
// (Note: it is safe to use a 'const char **' for argv.)
|
||||
void argstart(int *argc, char **argv, struct Argstate *args);
|
||||
|
||||
// Returns the next flag in the argument list,
|
||||
// or -1 if there are no more flags.
|
||||
//
|
||||
// Flags stop at a non-flag (anything that doesn't start with '-'),
|
||||
// at the end of the argument list, before "-", or after "--",
|
||||
// whichever comes first. Any "--" argument is not returned.
|
||||
//
|
||||
// Consumes arguments from the argc/argv array passed in to argstart.
|
||||
// If you argstart with an argc/argv array of ["sh", "-i", "foo"],
|
||||
// the first call to argnext will return 'i' and change the array to
|
||||
// ["sh", "foo"]. Thus, when argnext returns -1, the argc/argv array
|
||||
// contains just the non-flag arguments.
|
||||
int argnext(struct Argstate *);
|
||||
|
||||
// Returns the next value for the current flag, or 0 if it has no value.
|
||||
// For example, given an argument list ["-fval1", "val2", "val3"],
|
||||
// a call to argnext() will return 'f', after which repeated calls to
|
||||
// argnextvalue will return "val1", "val2", and "val3".
|
||||
// Consumes arguments from the argc/argv array.
|
||||
char *argnextvalue(struct Argstate *);
|
||||
|
||||
// Returns the current flag's value, or 0 if it has no value.
|
||||
// Behaves like argnextvalue, except that repeated calls to argvalue will
|
||||
// return the same value.
|
||||
char *argvalue(struct Argstate *);
|
||||
|
||||
// Example:
|
||||
//
|
||||
// #include <inc/lib.h>
|
||||
//
|
||||
// void
|
||||
// umain(int argc, char **argv)
|
||||
// {
|
||||
// int i;
|
||||
// struct Argstate args;
|
||||
//
|
||||
// argstart(&argc, argv, &args);
|
||||
// while ((i = argnext(&args)) >= 0)
|
||||
// switch (i) {
|
||||
// case 'r':
|
||||
// case 'x':
|
||||
// cprintf("'-%c' flag\n", i);
|
||||
// break;
|
||||
// case 'f':
|
||||
// cprintf("'-f %s' flag\n", argvalue(&args));
|
||||
// break;
|
||||
// default:
|
||||
// cprintf("unknown flag\n");
|
||||
// }
|
||||
//
|
||||
// for (i = 1; i < argc; i++)
|
||||
// cprintf("argument '%s'\n", argv[i]);
|
||||
// }
|
||||
//
|
||||
// If this program is run with the arguments
|
||||
// ["-rx", "-f", "foo", "--", "-r", "duh"]
|
||||
// it will print out
|
||||
// '-r' flag
|
||||
// '-x' flag
|
||||
// '-f foo' flag
|
||||
// argument '-r'
|
||||
// argument 'duh'
|
||||
|
||||
struct Argstate {
|
||||
int *argc;
|
||||
const char **argv;
|
||||
const char *curarg;
|
||||
const char *argvalue;
|
||||
};
|
||||
|
||||
#endif
|
||||
12
inc/env.h
12
inc/env.h
@@ -41,6 +41,7 @@ enum {
|
||||
// Special environment types
|
||||
enum EnvType {
|
||||
ENV_TYPE_USER = 0,
|
||||
ENV_TYPE_FS, // File system server
|
||||
};
|
||||
|
||||
struct Env {
|
||||
@@ -51,9 +52,20 @@ struct Env {
|
||||
enum EnvType env_type; // Indicates special system environments
|
||||
unsigned env_status; // Status of the environment
|
||||
uint32_t env_runs; // Number of times environment has run
|
||||
int env_cpunum; // The CPU that the env is running on
|
||||
|
||||
// Address space
|
||||
pde_t *env_pgdir; // Kernel virtual address of page dir
|
||||
|
||||
// Exception handling
|
||||
void *env_pgfault_upcall; // Page fault upcall entry point
|
||||
|
||||
// Lab 4 IPC
|
||||
bool env_ipc_recving; // Env is blocked receiving
|
||||
void *env_ipc_dstva; // VA at which to map received page
|
||||
uint32_t env_ipc_value; // Data value sent to us
|
||||
envid_t env_ipc_from; // envid of the sender
|
||||
int env_ipc_perm; // Perm of page mapping received
|
||||
};
|
||||
|
||||
#endif // !JOS_INC_ENV_H
|
||||
|
||||
12
inc/error.h
12
inc/error.h
@@ -14,6 +14,18 @@ enum {
|
||||
// the maximum allowed
|
||||
E_FAULT , // Memory fault
|
||||
|
||||
E_IPC_NOT_RECV , // Attempt to send to env that is not recving
|
||||
E_EOF , // Unexpected end of file
|
||||
|
||||
// File system error codes -- only seen in user-level
|
||||
E_NO_DISK , // No free space left on disk
|
||||
E_MAX_OPEN , // Too many files are open
|
||||
E_NOT_FOUND , // File or block not found
|
||||
E_BAD_PATH , // Bad path
|
||||
E_FILE_EXISTS , // File already exists
|
||||
E_NOT_EXEC , // File not a valid executable
|
||||
E_NOT_SUPP , // Operation not supported
|
||||
|
||||
MAXERROR
|
||||
};
|
||||
|
||||
|
||||
58
inc/fd.h
Normal file
58
inc/fd.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Public definitions for the POSIX-like file descriptor emulation layer
|
||||
// that our user-land support library implements for the use of applications.
|
||||
// See the code in the lib directory for the implementation details.
|
||||
|
||||
#ifndef JOS_INC_FD_H
|
||||
#define JOS_INC_FD_H
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/fs.h>
|
||||
|
||||
struct Fd;
|
||||
struct Stat;
|
||||
struct Dev;
|
||||
|
||||
// Per-device-class file descriptor operations
|
||||
struct Dev {
|
||||
int dev_id;
|
||||
const char *dev_name;
|
||||
ssize_t (*dev_read)(struct Fd *fd, void *buf, size_t len);
|
||||
ssize_t (*dev_write)(struct Fd *fd, const void *buf, size_t len);
|
||||
int (*dev_close)(struct Fd *fd);
|
||||
int (*dev_stat)(struct Fd *fd, struct Stat *stat);
|
||||
int (*dev_trunc)(struct Fd *fd, off_t length);
|
||||
};
|
||||
|
||||
struct FdFile {
|
||||
int id;
|
||||
};
|
||||
|
||||
struct Fd {
|
||||
int fd_dev_id;
|
||||
off_t fd_offset;
|
||||
int fd_omode;
|
||||
union {
|
||||
// File server files
|
||||
struct FdFile fd_file;
|
||||
};
|
||||
};
|
||||
|
||||
struct Stat {
|
||||
char st_name[MAXNAMELEN];
|
||||
off_t st_size;
|
||||
int st_isdir;
|
||||
struct Dev *st_dev;
|
||||
};
|
||||
|
||||
char* fd2data(struct Fd *fd);
|
||||
int fd2num(struct Fd *fd);
|
||||
int fd_alloc(struct Fd **fd_store);
|
||||
int fd_close(struct Fd *fd, bool must_exist);
|
||||
int fd_lookup(int fdnum, struct Fd **fd_store);
|
||||
int dev_lookup(int devid, struct Dev **dev_store);
|
||||
|
||||
extern struct Dev devfile;
|
||||
extern struct Dev devcons;
|
||||
extern struct Dev devpipe;
|
||||
|
||||
#endif // not JOS_INC_FD_H
|
||||
116
inc/fs.h
Normal file
116
inc/fs.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// See COPYRIGHT for copyright information.
|
||||
|
||||
#ifndef JOS_INC_FS_H
|
||||
#define JOS_INC_FS_H
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/mmu.h>
|
||||
|
||||
// File nodes (both in-memory and on-disk)
|
||||
|
||||
// Bytes per file system block - same as page size
|
||||
#define BLKSIZE PGSIZE
|
||||
#define BLKBITSIZE (BLKSIZE * 8)
|
||||
|
||||
// Maximum size of a filename (a single path component), including null
|
||||
// Must be a multiple of 4
|
||||
#define MAXNAMELEN 128
|
||||
|
||||
// Maximum size of a complete pathname, including null
|
||||
#define MAXPATHLEN 1024
|
||||
|
||||
// Number of block pointers in a File descriptor
|
||||
#define NDIRECT 10
|
||||
// Number of direct block pointers in an indirect block
|
||||
#define NINDIRECT (BLKSIZE / 4)
|
||||
|
||||
#define MAXFILESIZE ((NDIRECT + NINDIRECT) * BLKSIZE)
|
||||
|
||||
struct File {
|
||||
char f_name[MAXNAMELEN]; // filename
|
||||
off_t f_size; // file size in bytes
|
||||
uint32_t f_type; // file type
|
||||
|
||||
// Block pointers.
|
||||
// A block is allocated iff its value is != 0.
|
||||
uint32_t f_direct[NDIRECT]; // direct blocks
|
||||
uint32_t f_indirect; // indirect block
|
||||
|
||||
// Pad out to 256 bytes; must do arithmetic in case we're compiling
|
||||
// fsformat on a 64-bit machine.
|
||||
uint8_t f_pad[256 - MAXNAMELEN - 8 - 4*NDIRECT - 4];
|
||||
} __attribute__((packed)); // required only on some 64-bit machines
|
||||
|
||||
// An inode block contains exactly BLKFILES 'struct File's
|
||||
#define BLKFILES (BLKSIZE / sizeof(struct File))
|
||||
|
||||
// File types
|
||||
#define FTYPE_REG 0 // Regular file
|
||||
#define FTYPE_DIR 1 // Directory
|
||||
|
||||
|
||||
// File system super-block (both in-memory and on-disk)
|
||||
|
||||
#define FS_MAGIC 0x4A0530AE // related vaguely to 'J\0S!'
|
||||
|
||||
struct Super {
|
||||
uint32_t s_magic; // Magic number: FS_MAGIC
|
||||
uint32_t s_nblocks; // Total number of blocks on disk
|
||||
struct File s_root; // Root directory node
|
||||
};
|
||||
|
||||
// Definitions for requests from clients to file system
|
||||
enum {
|
||||
FSREQ_OPEN = 1,
|
||||
FSREQ_SET_SIZE,
|
||||
// Read returns a Fsret_read on the request page
|
||||
FSREQ_READ,
|
||||
FSREQ_WRITE,
|
||||
// Stat returns a Fsret_stat on the request page
|
||||
FSREQ_STAT,
|
||||
FSREQ_FLUSH,
|
||||
FSREQ_REMOVE,
|
||||
FSREQ_SYNC
|
||||
};
|
||||
|
||||
union Fsipc {
|
||||
struct Fsreq_open {
|
||||
char req_path[MAXPATHLEN];
|
||||
int req_omode;
|
||||
} open;
|
||||
struct Fsreq_set_size {
|
||||
int req_fileid;
|
||||
off_t req_size;
|
||||
} set_size;
|
||||
struct Fsreq_read {
|
||||
int req_fileid;
|
||||
size_t req_n;
|
||||
} read;
|
||||
struct Fsret_read {
|
||||
char ret_buf[PGSIZE];
|
||||
} readRet;
|
||||
struct Fsreq_write {
|
||||
int req_fileid;
|
||||
size_t req_n;
|
||||
char req_buf[PGSIZE - (sizeof(int) + sizeof(size_t))];
|
||||
} write;
|
||||
struct Fsreq_stat {
|
||||
int req_fileid;
|
||||
} stat;
|
||||
struct Fsret_stat {
|
||||
char ret_name[MAXNAMELEN];
|
||||
off_t ret_size;
|
||||
int ret_isdir;
|
||||
} statRet;
|
||||
struct Fsreq_flush {
|
||||
int req_fileid;
|
||||
} flush;
|
||||
struct Fsreq_remove {
|
||||
char req_path[MAXPATHLEN];
|
||||
} remove;
|
||||
|
||||
// Ensure Fsipc is one page
|
||||
char _pad[PGSIZE];
|
||||
};
|
||||
|
||||
#endif /* !JOS_INC_FS_H */
|
||||
75
inc/lib.h
75
inc/lib.h
@@ -16,6 +16,10 @@
|
||||
#include <inc/env.h>
|
||||
#include <inc/memlayout.h>
|
||||
#include <inc/syscall.h>
|
||||
#include <inc/trap.h>
|
||||
#include <inc/fs.h>
|
||||
#include <inc/fd.h>
|
||||
#include <inc/args.h>
|
||||
|
||||
#define USED(x) (void)(x)
|
||||
|
||||
@@ -31,6 +35,9 @@ extern const volatile struct PageInfo pages[];
|
||||
// exit.c
|
||||
void exit(void);
|
||||
|
||||
// pgfault.c
|
||||
void set_pgfault_handler(void (*handler)(struct UTrapframe *utf));
|
||||
|
||||
// readline.c
|
||||
char* readline(const char *buf);
|
||||
|
||||
@@ -39,8 +46,76 @@ void sys_cputs(const char *string, size_t len);
|
||||
int sys_cgetc(void);
|
||||
envid_t sys_getenvid(void);
|
||||
int sys_env_destroy(envid_t);
|
||||
void sys_yield(void);
|
||||
static envid_t sys_exofork(void);
|
||||
int sys_env_set_status(envid_t env, int status);
|
||||
int sys_env_set_trapframe(envid_t env, struct Trapframe *tf);
|
||||
int sys_env_set_pgfault_upcall(envid_t env, void *upcall);
|
||||
int sys_page_alloc(envid_t env, void *pg, int perm);
|
||||
int sys_page_map(envid_t src_env, void *src_pg,
|
||||
envid_t dst_env, void *dst_pg, int perm);
|
||||
int sys_page_unmap(envid_t env, void *pg);
|
||||
int sys_ipc_try_send(envid_t to_env, uint32_t value, void *pg, int perm);
|
||||
int sys_ipc_recv(void *rcv_pg);
|
||||
|
||||
// This must be inlined. Exercise for reader: why?
|
||||
static inline envid_t __attribute__((always_inline))
|
||||
sys_exofork(void)
|
||||
{
|
||||
envid_t ret;
|
||||
asm volatile("int %2"
|
||||
: "=a" (ret)
|
||||
: "a" (SYS_exofork), "i" (T_SYSCALL));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ipc.c
|
||||
void ipc_send(envid_t to_env, uint32_t value, void *pg, int perm);
|
||||
int32_t ipc_recv(envid_t *from_env_store, void *pg, int *perm_store);
|
||||
envid_t ipc_find_env(enum EnvType type);
|
||||
|
||||
// fork.c
|
||||
#define PTE_SHARE 0x400
|
||||
envid_t fork(void);
|
||||
envid_t sfork(void); // Challenge!
|
||||
|
||||
// fd.c
|
||||
int close(int fd);
|
||||
ssize_t read(int fd, void *buf, size_t nbytes);
|
||||
ssize_t write(int fd, const void *buf, size_t nbytes);
|
||||
int seek(int fd, off_t offset);
|
||||
void close_all(void);
|
||||
ssize_t readn(int fd, void *buf, size_t nbytes);
|
||||
int dup(int oldfd, int newfd);
|
||||
int fstat(int fd, struct Stat *statbuf);
|
||||
int stat(const char *path, struct Stat *statbuf);
|
||||
|
||||
// file.c
|
||||
int open(const char *path, int mode);
|
||||
int ftruncate(int fd, off_t size);
|
||||
int remove(const char *path);
|
||||
int sync(void);
|
||||
|
||||
// pageref.c
|
||||
int pageref(void *addr);
|
||||
|
||||
|
||||
// spawn.c
|
||||
envid_t spawn(const char *program, const char **argv);
|
||||
envid_t spawnl(const char *program, const char *arg0, ...);
|
||||
|
||||
// console.c
|
||||
void cputchar(int c);
|
||||
int getchar(void);
|
||||
int iscons(int fd);
|
||||
int opencons(void);
|
||||
|
||||
// pipe.c
|
||||
int pipe(int pipefds[2]);
|
||||
int pipeisclosed(int pipefd);
|
||||
|
||||
// wait.c
|
||||
void wait(envid_t env);
|
||||
|
||||
/* File open modes */
|
||||
#define O_RDONLY 0x0000 /* open for reading only */
|
||||
|
||||
@@ -138,6 +138,9 @@
|
||||
// The location of the user-level STABS data structure
|
||||
#define USTABDATA (PTSIZE / 2)
|
||||
|
||||
// Physical address of startup code for non-boot CPUs (APs)
|
||||
#define MPENTRY_PADDR 0x7000
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
typedef uint32_t pte_t;
|
||||
|
||||
33
inc/partition.h
Normal file
33
inc/partition.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef JOS_INC_PARTITION_H
|
||||
#define JOS_INC_PARTITION_H
|
||||
#include <inc/types.h>
|
||||
|
||||
/*
|
||||
* This file contains definitions for x86 partition tables, and comes from
|
||||
* Mike Mammarella.
|
||||
*/
|
||||
|
||||
// Offset of 1st partition descriptor in a partition sector
|
||||
#define PTABLE_OFFSET 446
|
||||
// 2-byte partition table magic number location and value
|
||||
#define PTABLE_MAGIC_OFFSET 510
|
||||
#define PTABLE_MAGIC "\x55\xAA"
|
||||
|
||||
// Partition type constants
|
||||
#define PTYPE_JOS_KERN 0x27 // JOS kernel
|
||||
#define PTYPE_JOSFS 0x28 // JOS file system
|
||||
// Extended partition identifiers
|
||||
#define PTYPE_DOS_EXTENDED 0x05
|
||||
#define PTYPE_W95_EXTENDED 0x0F
|
||||
#define PTYPE_LINUX_EXTENDED 0x85
|
||||
|
||||
struct Partitiondesc {
|
||||
uint8_t boot;
|
||||
uint8_t chs_begin[3];
|
||||
uint8_t type;
|
||||
uint8_t chs_end[3];
|
||||
uint32_t lba_start;
|
||||
uint32_t lba_length;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -7,6 +7,16 @@ enum {
|
||||
SYS_cgetc,
|
||||
SYS_getenvid,
|
||||
SYS_env_destroy,
|
||||
SYS_page_alloc,
|
||||
SYS_page_map,
|
||||
SYS_page_unmap,
|
||||
SYS_exofork,
|
||||
SYS_env_set_status,
|
||||
SYS_env_set_trapframe,
|
||||
SYS_env_set_pgfault_upcall,
|
||||
SYS_yield,
|
||||
SYS_ipc_try_send,
|
||||
SYS_ipc_recv,
|
||||
NSYSCALLS
|
||||
};
|
||||
|
||||
|
||||
11
inc/trap.h
11
inc/trap.h
@@ -74,6 +74,17 @@ struct Trapframe {
|
||||
uint16_t tf_padding4;
|
||||
} __attribute__((packed));
|
||||
|
||||
struct UTrapframe {
|
||||
/* information about the fault */
|
||||
uint32_t utf_fault_va; /* va for T_PGFLT, 0 otherwise */
|
||||
uint32_t utf_err;
|
||||
/* trap-time return state */
|
||||
struct PushRegs utf_regs;
|
||||
uintptr_t utf_eip;
|
||||
uint32_t utf_eflags;
|
||||
/* the trap-time stack to return to */
|
||||
uintptr_t utf_esp;
|
||||
} __attribute__((packed));
|
||||
|
||||
#endif /* !__ASSEMBLER__ */
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ KERN_SRCFILES := kern/entry.S \
|
||||
lib/readline.c \
|
||||
lib/string.c
|
||||
|
||||
# Source files for LAB4
|
||||
KERN_SRCFILES += kern/mpentry.S \
|
||||
kern/mpconfig.c \
|
||||
kern/lapic.c \
|
||||
kern/spinlock.c
|
||||
|
||||
# Only build files if they exist.
|
||||
KERN_SRCFILES := $(wildcard $(KERN_SRCFILES))
|
||||
|
||||
@@ -53,6 +59,43 @@ KERN_BINFILES := user/hello \
|
||||
user/faultwritekernel \
|
||||
user/getc
|
||||
|
||||
# Binary files for LAB4
|
||||
KERN_BINFILES += user/idle \
|
||||
user/yield \
|
||||
user/dumbfork \
|
||||
user/stresssched \
|
||||
user/faultdie \
|
||||
user/faultregs \
|
||||
user/faultalloc \
|
||||
user/faultallocbad \
|
||||
user/faultnostack \
|
||||
user/faultbadhandler \
|
||||
user/faultevilhandler \
|
||||
user/forktree \
|
||||
user/sendpage \
|
||||
user/spin \
|
||||
user/fairness \
|
||||
user/pingpong \
|
||||
user/pingpongs \
|
||||
user/primes
|
||||
# Binary files for LAB5
|
||||
KERN_BINFILES += user/faultio\
|
||||
user/spawnfaultio\
|
||||
user/testfile \
|
||||
user/spawnhello \
|
||||
user/icode \
|
||||
fs/fs
|
||||
|
||||
# Binary files for LAB5
|
||||
KERN_BINFILES += user/testpteshare \
|
||||
user/testfdsharing \
|
||||
user/testpipe \
|
||||
user/testpiperace \
|
||||
user/testpiperace2 \
|
||||
user/primespipe \
|
||||
user/testkbd \
|
||||
user/testshell
|
||||
|
||||
KERN_OBJFILES := $(patsubst %.c, $(OBJDIR)/%.o, $(KERN_SRCFILES))
|
||||
KERN_OBJFILES := $(patsubst %.S, $(OBJDIR)/%.o, $(KERN_OBJFILES))
|
||||
KERN_OBJFILES := $(patsubst $(OBJDIR)/lib/%, $(OBJDIR)/kern/%, $(KERN_OBJFILES))
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <inc/assert.h>
|
||||
|
||||
#include <kern/console.h>
|
||||
#include <kern/trap.h>
|
||||
#include <kern/picirq.h>
|
||||
#include <kern/ansi.h>
|
||||
|
||||
static void cons_intr(int (*proc)(void));
|
||||
@@ -101,6 +103,9 @@ serial_init(void)
|
||||
(void) inb(COM1+COM_IIR);
|
||||
(void) inb(COM1+COM_RX);
|
||||
|
||||
// Enable serial interrupts
|
||||
if (serial_exists)
|
||||
irq_setmask_8259A(irq_mask_8259A & ~(1<<IRQ_SERIAL));
|
||||
}
|
||||
|
||||
|
||||
@@ -384,6 +389,9 @@ kbd_intr(void)
|
||||
static void
|
||||
kbd_init(void)
|
||||
{
|
||||
// Drain the kbd buffer so that QEMU generates interrupts.
|
||||
kbd_intr();
|
||||
irq_setmask_8259A(irq_mask_8259A & ~(1<<IRQ_KBD));
|
||||
}
|
||||
|
||||
|
||||
|
||||
46
kern/cpu.h
Normal file
46
kern/cpu.h
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
#ifndef JOS_INC_CPU_H
|
||||
#define JOS_INC_CPU_H
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/memlayout.h>
|
||||
#include <inc/mmu.h>
|
||||
#include <inc/env.h>
|
||||
|
||||
// Maximum number of CPUs
|
||||
#define NCPU 8
|
||||
|
||||
// Values of status in struct Cpu
|
||||
enum {
|
||||
CPU_UNUSED = 0,
|
||||
CPU_STARTED,
|
||||
CPU_HALTED,
|
||||
};
|
||||
|
||||
// Per-CPU state
|
||||
struct CpuInfo {
|
||||
uint8_t cpu_id; // Local APIC ID; index into cpus[] below
|
||||
volatile unsigned cpu_status; // The status of the CPU
|
||||
struct Env *cpu_env; // The currently-running environment.
|
||||
struct Taskstate cpu_ts; // Used by x86 to find stack for interrupt
|
||||
};
|
||||
|
||||
// Initialized in mpconfig.c
|
||||
extern struct CpuInfo cpus[NCPU];
|
||||
extern int ncpu; // Total number of CPUs in the system
|
||||
extern struct CpuInfo *bootcpu; // The boot-strap processor (BSP)
|
||||
extern physaddr_t lapicaddr; // Physical MMIO address of the local APIC
|
||||
|
||||
// Per-CPU kernel stacks
|
||||
extern unsigned char percpu_kstacks[NCPU][KSTKSIZE];
|
||||
|
||||
int cpunum(void);
|
||||
#define thiscpu (&cpus[cpunum()])
|
||||
|
||||
void mp_init(void);
|
||||
void lapic_init(void);
|
||||
void lapic_startap(uint8_t apicid, uint32_t addr);
|
||||
void lapic_eoi(void);
|
||||
void lapic_ipi(int vector);
|
||||
|
||||
#endif
|
||||
56
kern/env.c
56
kern/env.c
@@ -11,9 +11,11 @@
|
||||
#include <kern/pmap.h>
|
||||
#include <kern/trap.h>
|
||||
#include <kern/monitor.h>
|
||||
#include <kern/sched.h>
|
||||
#include <kern/cpu.h>
|
||||
#include <kern/spinlock.h>
|
||||
|
||||
struct Env *envs = NULL; // All environments
|
||||
struct Env *curenv = NULL; // The current env
|
||||
static struct Env *env_free_list; // Free environment list
|
||||
// (linked by Env->env_link)
|
||||
|
||||
@@ -34,7 +36,7 @@ static struct Env *env_free_list; // Free environment list
|
||||
// definition of gdt specifies the Descriptor Privilege Level (DPL)
|
||||
// of that descriptor: 0 for kernel and 3 for user.
|
||||
//
|
||||
struct Segdesc gdt[] =
|
||||
struct Segdesc gdt[NCPU + 5] =
|
||||
{
|
||||
// 0x0 - unused (always faults -- for trapping NULL far pointers)
|
||||
SEG_NULL,
|
||||
@@ -51,7 +53,8 @@ struct Segdesc gdt[] =
|
||||
// 0x20 - user data segment
|
||||
[GD_UD >> 3] = SEG(STA_W, 0x0, 0xffffffff, 3),
|
||||
|
||||
// 0x28 - tss, initialized in trap_init_percpu()
|
||||
// Per-CPU TSS descriptors (starting from GD_TSS0) are initialized
|
||||
// in trap_init_percpu()
|
||||
[GD_TSS0 >> 3] = SEG_NULL
|
||||
};
|
||||
|
||||
@@ -250,11 +253,21 @@ env_alloc(struct Env **newenv_store, envid_t parent_id)
|
||||
e->env_tf.tf_cs = GD_UT | 3;
|
||||
// You will set e->env_tf.tf_eip later.
|
||||
|
||||
// Enable interrupts while in user mode.
|
||||
// LAB 4: Your code here.
|
||||
e->env_tf.tf_eflags |= FL_IF;
|
||||
|
||||
// Clear the page fault handler until user installs one.
|
||||
e->env_pgfault_upcall = 0;
|
||||
|
||||
// Also clear the IPC receiving flag.
|
||||
e->env_ipc_recving = 0;
|
||||
|
||||
// commit the allocation
|
||||
env_free_list = e->env_link;
|
||||
*newenv_store = e;
|
||||
|
||||
cprintf("[%08x] new env %08x\n", curenv ? curenv->env_id : 0, e->env_id);
|
||||
// cprintf("[%08x] new env %08x\n", curenv ? curenv->env_id : 0, e->env_id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -276,7 +289,7 @@ region_alloc(struct Env *e, void *va, size_t len)
|
||||
// You should round va down, and round (va + len) up.
|
||||
// (Watch out for corner-cases!)
|
||||
va = ROUNDDOWN(va, PGSIZE);
|
||||
size_t count = ROUNDUP(len, PGSIZE) / PGSIZE;
|
||||
size_t count = ROUNDUP(len, PGSIZE) / PGSIZE + 1;
|
||||
struct PageInfo* p;
|
||||
|
||||
while(count--) {
|
||||
@@ -344,6 +357,7 @@ load_icode(struct Env *e, uint8_t *binary)
|
||||
// LAB 3: Your code here.
|
||||
|
||||
// TODO validate the headers
|
||||
lcr3(PADDR(e->env_pgdir));
|
||||
struct Elf* elf = (struct Elf*) binary;
|
||||
struct Proghdr* ph = (struct Proghdr*) (binary + elf->e_phoff);
|
||||
struct Proghdr* phend = ph + elf->e_phnum;
|
||||
@@ -351,11 +365,10 @@ load_icode(struct Env *e, uint8_t *binary)
|
||||
if(ph->p_type != ELF_PROG_LOAD) continue;
|
||||
|
||||
region_alloc(e, (void*) ph->p_va, ph->p_memsz);
|
||||
lcr3(PADDR(e->env_pgdir));
|
||||
memcpy((void*) ph->p_va, binary + ph->p_offset, ph->p_filesz);
|
||||
memset((void*) ph->p_va + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz);
|
||||
lcr3(PADDR(kern_pgdir));
|
||||
}
|
||||
lcr3(PADDR(kern_pgdir));
|
||||
e->env_tf.tf_eip = elf->e_entry;
|
||||
|
||||
// Now map one page for the program's initial stack
|
||||
@@ -374,10 +387,16 @@ void
|
||||
env_create(uint8_t *binary, enum EnvType type)
|
||||
{
|
||||
// LAB 3: Your code here.
|
||||
// If this is the file server (type == ENV_TYPE_FS) give it I/O privileges.
|
||||
// LAB 5: Your code here.
|
||||
struct Env* new_env;
|
||||
if(env_alloc(&new_env, 0) < 0)
|
||||
panic("Failed to allocate environment");
|
||||
new_env->env_type = type;
|
||||
|
||||
if(type == ENV_TYPE_FS)
|
||||
new_env->env_tf.tf_eflags |= FL_IOPL_3;
|
||||
|
||||
load_icode(new_env, binary);
|
||||
}
|
||||
|
||||
@@ -398,7 +417,7 @@ env_free(struct Env *e)
|
||||
lcr3(PADDR(kern_pgdir));
|
||||
|
||||
// Note the environment's demise.
|
||||
cprintf("[%08x] free env %08x\n", curenv ? curenv->env_id : 0, e->env_id);
|
||||
// cprintf("[%08x] free env %08x\n", curenv ? curenv->env_id : 0, e->env_id);
|
||||
|
||||
// Flush all mapped pages in the user portion of the address space
|
||||
static_assert(UTOP % PTSIZE == 0);
|
||||
@@ -436,15 +455,26 @@ env_free(struct Env *e)
|
||||
|
||||
//
|
||||
// Frees environment e.
|
||||
// If e was the current env, then runs a new environment (and does not return
|
||||
// to the caller).
|
||||
//
|
||||
void
|
||||
env_destroy(struct Env *e)
|
||||
{
|
||||
// If e is currently running on other CPUs, we change its state to
|
||||
// ENV_DYING. A zombie environment will be freed the next time
|
||||
// it traps to the kernel.
|
||||
if (e->env_status == ENV_RUNNING && curenv != e) {
|
||||
e->env_status = ENV_DYING;
|
||||
return;
|
||||
}
|
||||
|
||||
env_free(e);
|
||||
|
||||
cprintf("Destroyed the only environment - nothing more to do!\n");
|
||||
while (1)
|
||||
monitor(NULL);
|
||||
if (curenv == e) {
|
||||
curenv = NULL;
|
||||
sched_yield();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -457,6 +487,9 @@ env_destroy(struct Env *e)
|
||||
void
|
||||
env_pop_tf(struct Trapframe *tf)
|
||||
{
|
||||
// Record the CPU we are running on for user-space debugging
|
||||
curenv->env_cpunum = cpunum();
|
||||
|
||||
asm volatile(
|
||||
"\tmovl %0,%%esp\n"
|
||||
"\tpopal\n"
|
||||
@@ -502,6 +535,7 @@ env_run(struct Env *e)
|
||||
e->env_status = ENV_RUNNING;
|
||||
e->env_runs++;
|
||||
lcr3(PADDR(e->env_pgdir));
|
||||
unlock_kernel();
|
||||
env_pop_tf(&e->env_tf);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
#define JOS_KERN_ENV_H
|
||||
|
||||
#include <inc/env.h>
|
||||
#include <kern/cpu.h>
|
||||
|
||||
extern struct Env *envs; // All environments
|
||||
extern struct Env *curenv; // Current environment
|
||||
#define curenv (thiscpu->cpu_env) // Current environment
|
||||
extern struct Segdesc gdt[];
|
||||
|
||||
void env_init(void);
|
||||
|
||||
97
kern/init.c
97
kern/init.c
@@ -11,19 +11,18 @@
|
||||
#include <kern/kclock.h>
|
||||
#include <kern/env.h>
|
||||
#include <kern/trap.h>
|
||||
#include <kern/sched.h>
|
||||
#include <kern/picirq.h>
|
||||
#include <kern/cpu.h>
|
||||
#include <kern/spinlock.h>
|
||||
|
||||
static void boot_aps(void);
|
||||
|
||||
void sysenter_handler();
|
||||
|
||||
void
|
||||
i386_init(void)
|
||||
{
|
||||
extern char edata[], end[];
|
||||
|
||||
// Before doing anything else, complete the ELF loading process.
|
||||
// Clear the uninitialized global data (BSS) section of our program.
|
||||
// This ensures that all static/global variables start out zero.
|
||||
memset(edata, 0, end - edata);
|
||||
|
||||
// Initialize the console.
|
||||
// Can't call cprintf until after we do this!
|
||||
cons_init();
|
||||
@@ -47,18 +46,94 @@ i386_init(void)
|
||||
env_init();
|
||||
trap_init();
|
||||
|
||||
// Lab 4 multiprocessor initialization functions
|
||||
mp_init();
|
||||
lapic_init();
|
||||
|
||||
// Lab 4 multitasking initialization functions
|
||||
pic_init();
|
||||
|
||||
// Acquire the big kernel lock before waking up APs
|
||||
// Your code here:
|
||||
lock_kernel();
|
||||
|
||||
// Starting non-boot CPUs
|
||||
boot_aps();
|
||||
|
||||
// Start fs.
|
||||
ENV_CREATE(fs_fs, ENV_TYPE_FS);
|
||||
|
||||
#if defined(TEST)
|
||||
// Don't touch -- used by grading script!
|
||||
ENV_CREATE(TEST, ENV_TYPE_USER);
|
||||
#else
|
||||
// Touch all you want.
|
||||
ENV_CREATE(user_hello, ENV_TYPE_USER);
|
||||
ENV_CREATE(user_icode, ENV_TYPE_USER);
|
||||
#endif // TEST*
|
||||
|
||||
// We only have one user environment for now, so just run it.
|
||||
env_run(&envs[0]);
|
||||
// Should not be necessary - drains keyboard because interrupt has given up.
|
||||
kbd_intr();
|
||||
|
||||
// Schedule and run the first user environment!
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
// While boot_aps is booting a given CPU, it communicates the per-core
|
||||
// stack pointer that should be loaded by mpentry.S to that CPU in
|
||||
// this variable.
|
||||
void *mpentry_kstack;
|
||||
|
||||
// Start the non-boot (AP) processors.
|
||||
static void
|
||||
boot_aps(void)
|
||||
{
|
||||
extern unsigned char mpentry_start[], mpentry_end[];
|
||||
void *code;
|
||||
struct CpuInfo *c;
|
||||
|
||||
// Write entry code to unused memory at MPENTRY_PADDR
|
||||
code = KADDR(MPENTRY_PADDR);
|
||||
memmove(code, mpentry_start, mpentry_end - mpentry_start);
|
||||
|
||||
// Boot each AP one at a time
|
||||
for (c = cpus; c < cpus + ncpu; c++) {
|
||||
if (c == cpus + cpunum()) // We've started already.
|
||||
continue;
|
||||
|
||||
// Tell mpentry.S what stack to use
|
||||
mpentry_kstack = percpu_kstacks[c - cpus] + KSTKSIZE;
|
||||
// Start the CPU at mpentry_start
|
||||
lapic_startap(c->cpu_id, PADDR(code));
|
||||
// Wait for the CPU to finish some basic setup in mp_main()
|
||||
while(c->cpu_status != CPU_STARTED)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
// Setup code for APs
|
||||
void
|
||||
mp_main(void)
|
||||
{
|
||||
// We are in high EIP now, safe to switch to kern_pgdir
|
||||
lcr3(PADDR(kern_pgdir));
|
||||
cprintf("SMP: CPU %d starting\n", cpunum());
|
||||
|
||||
lapic_init();
|
||||
env_init_percpu();
|
||||
trap_init_percpu();
|
||||
xchg(&thiscpu->cpu_status, CPU_STARTED); // tell boot_aps() we're up
|
||||
|
||||
// Now that we have finished some basic setup, call sched_yield()
|
||||
// to start running processes on this CPU. But make sure that
|
||||
// only one CPU can enter the scheduler at a time!
|
||||
//
|
||||
// Your code here:
|
||||
lock_kernel();
|
||||
sched_yield();
|
||||
|
||||
// Remove this after you finish Exercise 6
|
||||
for (;;);
|
||||
}
|
||||
|
||||
/*
|
||||
* Variable panicstr contains argument to first call to panic; used as flag
|
||||
@@ -83,7 +158,7 @@ _panic(const char *file, int line, const char *fmt,...)
|
||||
asm volatile("cli; cld");
|
||||
|
||||
va_start(ap, fmt);
|
||||
cprintf("kernel panic at %s:%d: ", file, line);
|
||||
cprintf("kernel panic on CPU %d at %s:%d: ", cpunum(), file, line);
|
||||
vcprintf(fmt, ap);
|
||||
cprintf("\n");
|
||||
va_end(ap);
|
||||
|
||||
@@ -44,18 +44,19 @@ SECTIONS
|
||||
|
||||
/* The data segment */
|
||||
.data : {
|
||||
*(.data)
|
||||
*(.data .data.*)
|
||||
}
|
||||
|
||||
.bss : {
|
||||
PROVIDE(edata = .);
|
||||
*(.bss)
|
||||
*(.dynbss)
|
||||
*(.bss .bss.*)
|
||||
*(COMMON)
|
||||
PROVIDE(end = .);
|
||||
BYTE(0)
|
||||
}
|
||||
|
||||
|
||||
/DISCARD/ : {
|
||||
*(.eh_frame .note.GNU-stack)
|
||||
*(.eh_frame .note.GNU-stack .comment .note)
|
||||
}
|
||||
}
|
||||
|
||||
182
kern/lapic.c
Normal file
182
kern/lapic.c
Normal file
@@ -0,0 +1,182 @@
|
||||
// The local APIC manages internal (non-I/O) interrupts.
|
||||
// See Chapter 8 & Appendix C of Intel processor manual volume 3.
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/memlayout.h>
|
||||
#include <inc/trap.h>
|
||||
#include <inc/mmu.h>
|
||||
#include <inc/stdio.h>
|
||||
#include <inc/x86.h>
|
||||
#include <kern/pmap.h>
|
||||
#include <kern/cpu.h>
|
||||
|
||||
// Local APIC registers, divided by 4 for use as uint32_t[] indices.
|
||||
#define ID (0x0020/4) // ID
|
||||
#define VER (0x0030/4) // Version
|
||||
#define TPR (0x0080/4) // Task Priority
|
||||
#define EOI (0x00B0/4) // EOI
|
||||
#define SVR (0x00F0/4) // Spurious Interrupt Vector
|
||||
#define ENABLE 0x00000100 // Unit Enable
|
||||
#define ESR (0x0280/4) // Error Status
|
||||
#define ICRLO (0x0300/4) // Interrupt Command
|
||||
#define INIT 0x00000500 // INIT/RESET
|
||||
#define STARTUP 0x00000600 // Startup IPI
|
||||
#define DELIVS 0x00001000 // Delivery status
|
||||
#define ASSERT 0x00004000 // Assert interrupt (vs deassert)
|
||||
#define DEASSERT 0x00000000
|
||||
#define LEVEL 0x00008000 // Level triggered
|
||||
#define BCAST 0x00080000 // Send to all APICs, including self.
|
||||
#define OTHERS 0x000C0000 // Send to all APICs, excluding self.
|
||||
#define BUSY 0x00001000
|
||||
#define FIXED 0x00000000
|
||||
#define ICRHI (0x0310/4) // Interrupt Command [63:32]
|
||||
#define TIMER (0x0320/4) // Local Vector Table 0 (TIMER)
|
||||
#define X1 0x0000000B // divide counts by 1
|
||||
#define PERIODIC 0x00020000 // Periodic
|
||||
#define PCINT (0x0340/4) // Performance Counter LVT
|
||||
#define LINT0 (0x0350/4) // Local Vector Table 1 (LINT0)
|
||||
#define LINT1 (0x0360/4) // Local Vector Table 2 (LINT1)
|
||||
#define ERROR (0x0370/4) // Local Vector Table 3 (ERROR)
|
||||
#define MASKED 0x00010000 // Interrupt masked
|
||||
#define TICR (0x0380/4) // Timer Initial Count
|
||||
#define TCCR (0x0390/4) // Timer Current Count
|
||||
#define TDCR (0x03E0/4) // Timer Divide Configuration
|
||||
|
||||
physaddr_t lapicaddr; // Initialized in mpconfig.c
|
||||
volatile uint32_t *lapic;
|
||||
|
||||
static void
|
||||
lapicw(int index, int value)
|
||||
{
|
||||
lapic[index] = value;
|
||||
lapic[ID]; // wait for write to finish, by reading
|
||||
}
|
||||
|
||||
void
|
||||
lapic_init(void)
|
||||
{
|
||||
if (!lapicaddr)
|
||||
return;
|
||||
|
||||
// lapicaddr is the physical address of the LAPIC's 4K MMIO
|
||||
// region. Map it in to virtual memory so we can access it.
|
||||
lapic = mmio_map_region(lapicaddr, 4096);
|
||||
|
||||
// Enable local APIC; set spurious interrupt vector.
|
||||
lapicw(SVR, ENABLE | (IRQ_OFFSET + IRQ_SPURIOUS));
|
||||
|
||||
// The timer repeatedly counts down at bus frequency
|
||||
// from lapic[TICR] and then issues an interrupt.
|
||||
// If we cared more about precise timekeeping,
|
||||
// TICR would be calibrated using an external time source.
|
||||
lapicw(TDCR, X1);
|
||||
lapicw(TIMER, PERIODIC | (IRQ_OFFSET + IRQ_TIMER));
|
||||
lapicw(TICR, 10000000);
|
||||
|
||||
// Leave LINT0 of the BSP enabled so that it can get
|
||||
// interrupts from the 8259A chip.
|
||||
//
|
||||
// According to Intel MP Specification, the BIOS should initialize
|
||||
// BSP's local APIC in Virtual Wire Mode, in which 8259A's
|
||||
// INTR is virtually connected to BSP's LINTIN0. In this mode,
|
||||
// we do not need to program the IOAPIC.
|
||||
if (thiscpu != bootcpu)
|
||||
lapicw(LINT0, MASKED);
|
||||
|
||||
// Disable NMI (LINT1) on all CPUs
|
||||
lapicw(LINT1, MASKED);
|
||||
|
||||
// Disable performance counter overflow interrupts
|
||||
// on machines that provide that interrupt entry.
|
||||
if (((lapic[VER]>>16) & 0xFF) >= 4)
|
||||
lapicw(PCINT, MASKED);
|
||||
|
||||
// Map error interrupt to IRQ_ERROR.
|
||||
lapicw(ERROR, IRQ_OFFSET + IRQ_ERROR);
|
||||
|
||||
// Clear error status register (requires back-to-back writes).
|
||||
lapicw(ESR, 0);
|
||||
lapicw(ESR, 0);
|
||||
|
||||
// Ack any outstanding interrupts.
|
||||
lapicw(EOI, 0);
|
||||
|
||||
// Send an Init Level De-Assert to synchronize arbitration ID's.
|
||||
lapicw(ICRHI, 0);
|
||||
lapicw(ICRLO, BCAST | INIT | LEVEL);
|
||||
while(lapic[ICRLO] & DELIVS)
|
||||
;
|
||||
|
||||
// Enable interrupts on the APIC (but not on the processor).
|
||||
lapicw(TPR, 0);
|
||||
}
|
||||
|
||||
int
|
||||
cpunum(void)
|
||||
{
|
||||
if (lapic)
|
||||
return lapic[ID] >> 24;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Acknowledge interrupt.
|
||||
void
|
||||
lapic_eoi(void)
|
||||
{
|
||||
if (lapic)
|
||||
lapicw(EOI, 0);
|
||||
}
|
||||
|
||||
// Spin for a given number of microseconds.
|
||||
// On real hardware would want to tune this dynamically.
|
||||
static void
|
||||
microdelay(int us)
|
||||
{
|
||||
}
|
||||
|
||||
#define IO_RTC 0x70
|
||||
|
||||
// Start additional processor running entry code at addr.
|
||||
// See Appendix B of MultiProcessor Specification.
|
||||
void
|
||||
lapic_startap(uint8_t apicid, uint32_t addr)
|
||||
{
|
||||
int i;
|
||||
uint16_t *wrv;
|
||||
|
||||
// "The BSP must initialize CMOS shutdown code to 0AH
|
||||
// and the warm reset vector (DWORD based at 40:67) to point at
|
||||
// the AP startup code prior to the [universal startup algorithm]."
|
||||
outb(IO_RTC, 0xF); // offset 0xF is shutdown code
|
||||
outb(IO_RTC+1, 0x0A);
|
||||
wrv = (uint16_t *)KADDR((0x40 << 4 | 0x67)); // Warm reset vector
|
||||
wrv[0] = 0;
|
||||
wrv[1] = addr >> 4;
|
||||
|
||||
// "Universal startup algorithm."
|
||||
// Send INIT (level-triggered) interrupt to reset other CPU.
|
||||
lapicw(ICRHI, apicid << 24);
|
||||
lapicw(ICRLO, INIT | LEVEL | ASSERT);
|
||||
microdelay(200);
|
||||
lapicw(ICRLO, INIT | LEVEL);
|
||||
microdelay(100); // should be 10ms, but too slow in Bochs!
|
||||
|
||||
// Send startup IPI (twice!) to enter code.
|
||||
// Regular hardware is supposed to only accept a STARTUP
|
||||
// when it is in the halted state due to an INIT. So the second
|
||||
// should be ignored, but it is part of the official Intel algorithm.
|
||||
// Bochs complains about the second one. Too bad for Bochs.
|
||||
for (i = 0; i < 2; i++) {
|
||||
lapicw(ICRHI, apicid << 24);
|
||||
lapicw(ICRLO, STARTUP | (addr >> 12));
|
||||
microdelay(200);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
lapic_ipi(int vector)
|
||||
{
|
||||
lapicw(ICRLO, OTHERS | FIXED | vector);
|
||||
while (lapic[ICRLO] & DELIVS)
|
||||
;
|
||||
}
|
||||
225
kern/mpconfig.c
Normal file
225
kern/mpconfig.c
Normal file
@@ -0,0 +1,225 @@
|
||||
// Search for and parse the multiprocessor configuration table
|
||||
// See http://developer.intel.com/design/pentium/datashts/24201606.pdf
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/string.h>
|
||||
#include <inc/memlayout.h>
|
||||
#include <inc/x86.h>
|
||||
#include <inc/mmu.h>
|
||||
#include <inc/env.h>
|
||||
#include <kern/cpu.h>
|
||||
#include <kern/pmap.h>
|
||||
|
||||
struct CpuInfo cpus[NCPU];
|
||||
struct CpuInfo *bootcpu;
|
||||
int ismp;
|
||||
int ncpu;
|
||||
|
||||
// Per-CPU kernel stacks
|
||||
unsigned char percpu_kstacks[NCPU][KSTKSIZE]
|
||||
__attribute__ ((aligned(PGSIZE)));
|
||||
|
||||
|
||||
// See MultiProcessor Specification Version 1.[14]
|
||||
|
||||
struct mp { // floating pointer [MP 4.1]
|
||||
uint8_t signature[4]; // "_MP_"
|
||||
physaddr_t physaddr; // phys addr of MP config table
|
||||
uint8_t length; // 1
|
||||
uint8_t specrev; // [14]
|
||||
uint8_t checksum; // all bytes must add up to 0
|
||||
uint8_t type; // MP system config type
|
||||
uint8_t imcrp;
|
||||
uint8_t reserved[3];
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct mpconf { // configuration table header [MP 4.2]
|
||||
uint8_t signature[4]; // "PCMP"
|
||||
uint16_t length; // total table length
|
||||
uint8_t version; // [14]
|
||||
uint8_t checksum; // all bytes must add up to 0
|
||||
uint8_t product[20]; // product id
|
||||
physaddr_t oemtable; // OEM table pointer
|
||||
uint16_t oemlength; // OEM table length
|
||||
uint16_t entry; // entry count
|
||||
physaddr_t lapicaddr; // address of local APIC
|
||||
uint16_t xlength; // extended table length
|
||||
uint8_t xchecksum; // extended table checksum
|
||||
uint8_t reserved;
|
||||
uint8_t entries[0]; // table entries
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct mpproc { // processor table entry [MP 4.3.1]
|
||||
uint8_t type; // entry type (0)
|
||||
uint8_t apicid; // local APIC id
|
||||
uint8_t version; // local APIC version
|
||||
uint8_t flags; // CPU flags
|
||||
uint8_t signature[4]; // CPU signature
|
||||
uint32_t feature; // feature flags from CPUID instruction
|
||||
uint8_t reserved[8];
|
||||
} __attribute__((__packed__));
|
||||
|
||||
// mpproc flags
|
||||
#define MPPROC_BOOT 0x02 // This mpproc is the bootstrap processor
|
||||
|
||||
// Table entry types
|
||||
#define MPPROC 0x00 // One per processor
|
||||
#define MPBUS 0x01 // One per bus
|
||||
#define MPIOAPIC 0x02 // One per I/O APIC
|
||||
#define MPIOINTR 0x03 // One per bus interrupt source
|
||||
#define MPLINTR 0x04 // One per system interrupt source
|
||||
|
||||
static uint8_t
|
||||
sum(void *addr, int len)
|
||||
{
|
||||
int i, sum;
|
||||
|
||||
sum = 0;
|
||||
for (i = 0; i < len; i++)
|
||||
sum += ((uint8_t *)addr)[i];
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Look for an MP structure in the len bytes at physical address addr.
|
||||
static struct mp *
|
||||
mpsearch1(physaddr_t a, int len)
|
||||
{
|
||||
struct mp *mp = KADDR(a), *end = KADDR(a + len);
|
||||
|
||||
for (; mp < end; mp++)
|
||||
if (memcmp(mp->signature, "_MP_", 4) == 0 &&
|
||||
sum(mp, sizeof(*mp)) == 0)
|
||||
return mp;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Search for the MP Floating Pointer Structure, which according to
|
||||
// [MP 4] is in one of the following three locations:
|
||||
// 1) in the first KB of the EBDA;
|
||||
// 2) if there is no EBDA, in the last KB of system base memory;
|
||||
// 3) in the BIOS ROM between 0xE0000 and 0xFFFFF.
|
||||
static struct mp *
|
||||
mpsearch(void)
|
||||
{
|
||||
uint8_t *bda;
|
||||
uint32_t p;
|
||||
struct mp *mp;
|
||||
|
||||
static_assert(sizeof(*mp) == 16);
|
||||
|
||||
// The BIOS data area lives in 16-bit segment 0x40.
|
||||
bda = (uint8_t *) KADDR(0x40 << 4);
|
||||
|
||||
// [MP 4] The 16-bit segment of the EBDA is in the two bytes
|
||||
// starting at byte 0x0E of the BDA. 0 if not present.
|
||||
if ((p = *(uint16_t *) (bda + 0x0E))) {
|
||||
p <<= 4; // Translate from segment to PA
|
||||
if ((mp = mpsearch1(p, 1024)))
|
||||
return mp;
|
||||
} else {
|
||||
// The size of base memory, in KB is in the two bytes
|
||||
// starting at 0x13 of the BDA.
|
||||
p = *(uint16_t *) (bda + 0x13) * 1024;
|
||||
if ((mp = mpsearch1(p - 1024, 1024)))
|
||||
return mp;
|
||||
}
|
||||
return mpsearch1(0xF0000, 0x10000);
|
||||
}
|
||||
|
||||
// Search for an MP configuration table. For now, don't accept the
|
||||
// default configurations (physaddr == 0).
|
||||
// Check for the correct signature, checksum, and version.
|
||||
static struct mpconf *
|
||||
mpconfig(struct mp **pmp)
|
||||
{
|
||||
struct mpconf *conf;
|
||||
struct mp *mp;
|
||||
|
||||
if ((mp = mpsearch()) == 0)
|
||||
return NULL;
|
||||
if (mp->physaddr == 0 || mp->type != 0) {
|
||||
cprintf("SMP: Default configurations not implemented\n");
|
||||
return NULL;
|
||||
}
|
||||
conf = (struct mpconf *) KADDR(mp->physaddr);
|
||||
if (memcmp(conf, "PCMP", 4) != 0) {
|
||||
cprintf("SMP: Incorrect MP configuration table signature\n");
|
||||
return NULL;
|
||||
}
|
||||
if (sum(conf, conf->length) != 0) {
|
||||
cprintf("SMP: Bad MP configuration checksum\n");
|
||||
return NULL;
|
||||
}
|
||||
if (conf->version != 1 && conf->version != 4) {
|
||||
cprintf("SMP: Unsupported MP version %d\n", conf->version);
|
||||
return NULL;
|
||||
}
|
||||
if ((sum((uint8_t *)conf + conf->length, conf->xlength) + conf->xchecksum) & 0xff) {
|
||||
cprintf("SMP: Bad MP configuration extended checksum\n");
|
||||
return NULL;
|
||||
}
|
||||
*pmp = mp;
|
||||
return conf;
|
||||
}
|
||||
|
||||
void
|
||||
mp_init(void)
|
||||
{
|
||||
struct mp *mp;
|
||||
struct mpconf *conf;
|
||||
struct mpproc *proc;
|
||||
uint8_t *p;
|
||||
unsigned int i;
|
||||
|
||||
bootcpu = &cpus[0];
|
||||
if ((conf = mpconfig(&mp)) == 0)
|
||||
return;
|
||||
ismp = 1;
|
||||
lapicaddr = conf->lapicaddr;
|
||||
|
||||
for (p = conf->entries, i = 0; i < conf->entry; i++) {
|
||||
switch (*p) {
|
||||
case MPPROC:
|
||||
proc = (struct mpproc *)p;
|
||||
if (proc->flags & MPPROC_BOOT)
|
||||
bootcpu = &cpus[ncpu];
|
||||
if (ncpu < NCPU) {
|
||||
cpus[ncpu].cpu_id = ncpu;
|
||||
ncpu++;
|
||||
} else {
|
||||
cprintf("SMP: too many CPUs, CPU %d disabled\n",
|
||||
proc->apicid);
|
||||
}
|
||||
p += sizeof(struct mpproc);
|
||||
continue;
|
||||
case MPBUS:
|
||||
case MPIOAPIC:
|
||||
case MPIOINTR:
|
||||
case MPLINTR:
|
||||
p += 8;
|
||||
continue;
|
||||
default:
|
||||
cprintf("mpinit: unknown config type %x\n", *p);
|
||||
ismp = 0;
|
||||
i = conf->entry;
|
||||
}
|
||||
}
|
||||
|
||||
bootcpu->cpu_status = CPU_STARTED;
|
||||
if (!ismp) {
|
||||
// Didn't like what we found; fall back to no MP.
|
||||
ncpu = 1;
|
||||
lapicaddr = 0;
|
||||
cprintf("SMP: configuration not found, SMP disabled\n");
|
||||
return;
|
||||
}
|
||||
cprintf("SMP: CPU %d found %d CPU(s)\n", bootcpu->cpu_id, ncpu);
|
||||
|
||||
if (mp->imcrp) {
|
||||
// [MP 3.2.6.1] If the hardware implements PIC mode,
|
||||
// switch to getting interrupts from the LAPIC.
|
||||
cprintf("SMP: Setting IMCR to switch from PIC mode to symmetric I/O mode\n");
|
||||
outb(0x22, 0x70); // Select IMCR
|
||||
outb(0x23, inb(0x23) | 1); // Mask external interrupts.
|
||||
}
|
||||
}
|
||||
97
kern/mpentry.S
Normal file
97
kern/mpentry.S
Normal file
@@ -0,0 +1,97 @@
|
||||
/* See COPYRIGHT for copyright information. */
|
||||
|
||||
#include <inc/mmu.h>
|
||||
#include <inc/memlayout.h>
|
||||
|
||||
###################################################################
|
||||
# entry point for APs
|
||||
###################################################################
|
||||
|
||||
# Each non-boot CPU ("AP") is started up in response to a STARTUP
|
||||
# IPI from the boot CPU. Section B.4.2 of the Multi-Processor
|
||||
# Specification says that the AP will start in real mode with CS:IP
|
||||
# set to XY00:0000, where XY is an 8-bit value sent with the
|
||||
# STARTUP. Thus this code must start at a 4096-byte boundary.
|
||||
#
|
||||
# Because this code sets DS to zero, it must run from an address in
|
||||
# the low 2^16 bytes of physical memory.
|
||||
#
|
||||
# boot_aps() (in init.c) copies this code to MPENTRY_PADDR (which
|
||||
# satisfies the above restrictions). Then, for each AP, it stores the
|
||||
# address of the pre-allocated per-core stack in mpentry_kstack, sends
|
||||
# the STARTUP IPI, and waits for this code to acknowledge that it has
|
||||
# started (which happens in mp_main in init.c).
|
||||
#
|
||||
# This code is similar to boot/boot.S except that
|
||||
# - it does not need to enable A20
|
||||
# - it uses MPBOOTPHYS to calculate absolute addresses of its
|
||||
# symbols, rather than relying on the linker to fill them
|
||||
|
||||
#define RELOC(x) ((x) - KERNBASE)
|
||||
#define MPBOOTPHYS(s) ((s) - mpentry_start + MPENTRY_PADDR)
|
||||
|
||||
.set PROT_MODE_CSEG, 0x8 # kernel code segment selector
|
||||
.set PROT_MODE_DSEG, 0x10 # kernel data segment selector
|
||||
|
||||
.code16
|
||||
.globl mpentry_start
|
||||
mpentry_start:
|
||||
cli
|
||||
|
||||
xorw %ax, %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
|
||||
lgdt MPBOOTPHYS(gdtdesc)
|
||||
movl %cr0, %eax
|
||||
orl $CR0_PE, %eax
|
||||
movl %eax, %cr0
|
||||
|
||||
ljmpl $(PROT_MODE_CSEG), $(MPBOOTPHYS(start32))
|
||||
|
||||
.code32
|
||||
start32:
|
||||
movw $(PROT_MODE_DSEG), %ax
|
||||
movw %ax, %ds
|
||||
movw %ax, %es
|
||||
movw %ax, %ss
|
||||
movw $0, %ax
|
||||
movw %ax, %fs
|
||||
movw %ax, %gs
|
||||
|
||||
# Set up initial page table. We cannot use kern_pgdir yet because
|
||||
# we are still running at a low EIP.
|
||||
movl $(RELOC(entry_pgdir)), %eax
|
||||
movl %eax, %cr3
|
||||
# Turn on paging.
|
||||
movl %cr0, %eax
|
||||
orl $(CR0_PE|CR0_PG|CR0_WP), %eax
|
||||
movl %eax, %cr0
|
||||
|
||||
# Switch to the per-cpu stack allocated in boot_aps()
|
||||
movl mpentry_kstack, %esp
|
||||
movl $0x0, %ebp # nuke frame pointer
|
||||
|
||||
# Call mp_main(). (Exercise for the reader: why the indirect call?)
|
||||
movl $mp_main, %eax
|
||||
call *%eax
|
||||
|
||||
# If mp_main returns (it shouldn't), loop.
|
||||
spin:
|
||||
jmp spin
|
||||
|
||||
# Bootstrap GDT
|
||||
.p2align 2 # force 4 byte alignment
|
||||
gdt:
|
||||
SEG_NULL # null seg
|
||||
SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
|
||||
SEG(STA_W, 0x0, 0xffffffff) # data seg
|
||||
|
||||
gdtdesc:
|
||||
.word 0x17 # sizeof(gdt) - 1
|
||||
.long MPBOOTPHYS(gdt) # address gdt
|
||||
|
||||
.globl mpentry_end
|
||||
mpentry_end:
|
||||
nop
|
||||
86
kern/picirq.c
Normal file
86
kern/picirq.c
Normal file
@@ -0,0 +1,86 @@
|
||||
/* See COPYRIGHT for copyright information. */
|
||||
|
||||
#include <inc/assert.h>
|
||||
#include <inc/trap.h>
|
||||
|
||||
#include <kern/picirq.h>
|
||||
|
||||
|
||||
// Current IRQ mask.
|
||||
// Initial IRQ mask has interrupt 2 enabled (for slave 8259A).
|
||||
uint16_t irq_mask_8259A = 0xFFFF & ~(1<<IRQ_SLAVE);
|
||||
static bool didinit;
|
||||
|
||||
/* Initialize the 8259A interrupt controllers. */
|
||||
void
|
||||
pic_init(void)
|
||||
{
|
||||
didinit = 1;
|
||||
|
||||
// mask all interrupts
|
||||
outb(IO_PIC1+1, 0xFF);
|
||||
outb(IO_PIC2+1, 0xFF);
|
||||
|
||||
// Set up master (8259A-1)
|
||||
|
||||
// ICW1: 0001g0hi
|
||||
// g: 0 = edge triggering, 1 = level triggering
|
||||
// h: 0 = cascaded PICs, 1 = master only
|
||||
// i: 0 = no ICW4, 1 = ICW4 required
|
||||
outb(IO_PIC1, 0x11);
|
||||
|
||||
// ICW2: Vector offset
|
||||
outb(IO_PIC1+1, IRQ_OFFSET);
|
||||
|
||||
// ICW3: bit mask of IR lines connected to slave PICs (master PIC),
|
||||
// 3-bit No of IR line at which slave connects to master(slave PIC).
|
||||
outb(IO_PIC1+1, 1<<IRQ_SLAVE);
|
||||
|
||||
// ICW4: 000nbmap
|
||||
// n: 1 = special fully nested mode
|
||||
// b: 1 = buffered mode
|
||||
// m: 0 = slave PIC, 1 = master PIC
|
||||
// (ignored when b is 0, as the master/slave role
|
||||
// can be hardwired).
|
||||
// a: 1 = Automatic EOI mode
|
||||
// p: 0 = MCS-80/85 mode, 1 = intel x86 mode
|
||||
outb(IO_PIC1+1, 0x3);
|
||||
|
||||
// Set up slave (8259A-2)
|
||||
outb(IO_PIC2, 0x11); // ICW1
|
||||
outb(IO_PIC2+1, IRQ_OFFSET + 8); // ICW2
|
||||
outb(IO_PIC2+1, IRQ_SLAVE); // ICW3
|
||||
// NB Automatic EOI mode doesn't tend to work on the slave.
|
||||
// Linux source code says it's "to be investigated".
|
||||
outb(IO_PIC2+1, 0x01); // ICW4
|
||||
|
||||
// OCW3: 0ef01prs
|
||||
// ef: 0x = NOP, 10 = clear specific mask, 11 = set specific mask
|
||||
// p: 0 = no polling, 1 = polling mode
|
||||
// rs: 0x = NOP, 10 = read IRR, 11 = read ISR
|
||||
outb(IO_PIC1, 0x68); /* clear specific mask */
|
||||
outb(IO_PIC1, 0x0a); /* read IRR by default */
|
||||
|
||||
outb(IO_PIC2, 0x68); /* OCW3 */
|
||||
outb(IO_PIC2, 0x0a); /* OCW3 */
|
||||
|
||||
if (irq_mask_8259A != 0xFFFF)
|
||||
irq_setmask_8259A(irq_mask_8259A);
|
||||
}
|
||||
|
||||
void
|
||||
irq_setmask_8259A(uint16_t mask)
|
||||
{
|
||||
int i;
|
||||
irq_mask_8259A = mask;
|
||||
if (!didinit)
|
||||
return;
|
||||
outb(IO_PIC1+1, (char)mask);
|
||||
outb(IO_PIC2+1, (char)(mask >> 8));
|
||||
cprintf("enabled interrupts:");
|
||||
for (i = 0; i < 16; i++)
|
||||
if (~mask & (1<<i))
|
||||
cprintf(" %d", i);
|
||||
cprintf("\n");
|
||||
}
|
||||
|
||||
28
kern/picirq.h
Normal file
28
kern/picirq.h
Normal file
@@ -0,0 +1,28 @@
|
||||
/* See COPYRIGHT for copyright information. */
|
||||
|
||||
#ifndef JOS_KERN_PICIRQ_H
|
||||
#define JOS_KERN_PICIRQ_H
|
||||
#ifndef JOS_KERNEL
|
||||
# error "This is a JOS kernel header; user programs should not #include it"
|
||||
#endif
|
||||
|
||||
#define MAX_IRQS 16 // Number of IRQs
|
||||
|
||||
// I/O Addresses of the two 8259A programmable interrupt controllers
|
||||
#define IO_PIC1 0x20 // Master (IRQs 0-7)
|
||||
#define IO_PIC2 0xA0 // Slave (IRQs 8-15)
|
||||
|
||||
#define IRQ_SLAVE 2 // IRQ at which slave connects to master
|
||||
|
||||
|
||||
#ifndef __ASSEMBLER__
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/x86.h>
|
||||
|
||||
extern uint16_t irq_mask_8259A;
|
||||
void pic_init(void);
|
||||
void irq_setmask_8259A(uint16_t mask);
|
||||
#endif // !__ASSEMBLER__
|
||||
|
||||
#endif // !JOS_KERN_PICIRQ_H
|
||||
132
kern/pmap.c
132
kern/pmap.c
@@ -10,6 +10,7 @@
|
||||
#include <kern/pmap.h>
|
||||
#include <kern/kclock.h>
|
||||
#include <kern/env.h>
|
||||
#include <kern/cpu.h>
|
||||
|
||||
// These variables are set by i386_detect_memory()
|
||||
size_t npages; // Amount of physical memory (in pages)
|
||||
@@ -63,6 +64,7 @@ i386_detect_memory(void)
|
||||
// Set up memory mappings above UTOP.
|
||||
// --------------------------------------------------------------
|
||||
|
||||
static void mem_init_mp(void);
|
||||
static void boot_map_region(pde_t *pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm);
|
||||
static void check_page_free_list(bool only_low_memory);
|
||||
static void check_page_alloc(void);
|
||||
@@ -83,6 +85,8 @@ static void check_page_installed_pgdir(void);
|
||||
// If we're out of memory, boot_alloc should panic.
|
||||
// This function may ONLY be used during initialization,
|
||||
// before the page_free_list list has been set up.
|
||||
// Note that when this function is called, we are still using entry_pgdir,
|
||||
// which only maps the first 4MB of physical memory.
|
||||
static void *
|
||||
boot_alloc(uint32_t n)
|
||||
{
|
||||
@@ -185,8 +189,6 @@ mem_init(void)
|
||||
boot_map_region(kern_pgdir,
|
||||
UPAGES, ROUNDUP(pages_size, PGSIZE),
|
||||
PADDR(pages), PTE_U);
|
||||
kern_pgdir[PDX(UPAGES)] |= PTE_U;
|
||||
kern_pgdir[PDX(UPAGES)] &= ~PTE_W;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Map the 'envs' array read-only by the user at linear address UENVS
|
||||
@@ -195,11 +197,10 @@ mem_init(void)
|
||||
// - the new image at UENVS -- kernel R, user R
|
||||
// - envs itself -- kernel RW, user NONE
|
||||
// LAB 3: Your code here.
|
||||
cprintf("Mapping envs from %p to %p\n", UENVS, ROUNDUP(envs_size, PGSIZE));
|
||||
boot_map_region(kern_pgdir,
|
||||
UENVS, ROUNDUP(envs_size, PGSIZE),
|
||||
PADDR(envs), PTE_U);
|
||||
kern_pgdir[PDX(UENVS)] |= PTE_U;
|
||||
kern_pgdir[PDX(UPAGES)] &= ~PTE_W;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Use the physical memory that 'bootstack' refers to as the kernel
|
||||
@@ -215,8 +216,6 @@ mem_init(void)
|
||||
boot_map_region(kern_pgdir,
|
||||
KSTACKTOP-KSTKSIZE, KSTKSIZE,
|
||||
PADDR(bootstack), PTE_W);
|
||||
kern_pgdir[PDX(KSTACKTOP-KSTKSIZE)] |= PTE_W;
|
||||
kern_pgdir[PDX(KSTACKTOP-KSTKSIZE)] &= ~PTE_U;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Map all of physical memory at KERNBASE.
|
||||
@@ -229,9 +228,9 @@ mem_init(void)
|
||||
boot_map_region(kern_pgdir,
|
||||
KERNBASE, 0x100000000 - KERNBASE,
|
||||
0, PTE_W);
|
||||
kern_pgdir[PDX(KERNBASE)] |= PTE_W | PTE_P;
|
||||
kern_pgdir[PDX(KERNBASE)] &= ~PTE_U;
|
||||
|
||||
// Initialize the SMP-related parts of the memory map
|
||||
mem_init_mp();
|
||||
|
||||
// Check that the initial page directory has been set up correctly.
|
||||
check_kern_pgdir();
|
||||
@@ -258,6 +257,36 @@ mem_init(void)
|
||||
check_page_installed_pgdir();
|
||||
}
|
||||
|
||||
// Modify mappings in kern_pgdir to support SMP
|
||||
// - Map the per-CPU stacks in the region [KSTACKTOP-PTSIZE, KSTACKTOP)
|
||||
//
|
||||
static void
|
||||
mem_init_mp(void)
|
||||
{
|
||||
// Map per-CPU stacks starting at KSTACKTOP, for up to 'NCPU' CPUs.
|
||||
//
|
||||
// For CPU i, use the physical memory that 'percpu_kstacks[i]' refers
|
||||
// to as its kernel stack. CPU i's kernel stack grows down from virtual
|
||||
// address kstacktop_i = KSTACKTOP - i * (KSTKSIZE + KSTKGAP), and is
|
||||
// divided into two pieces, just like the single stack you set up in
|
||||
// mem_init:
|
||||
// * [kstacktop_i - KSTKSIZE, kstacktop_i)
|
||||
// -- backed by physical memory
|
||||
// * [kstacktop_i - (KSTKSIZE + KSTKGAP), kstacktop_i - KSTKSIZE)
|
||||
// -- not backed; so if the kernel overflows its stack,
|
||||
// it will fault rather than overwrite another CPU's stack.
|
||||
// Known as a "guard page".
|
||||
// Permissions: kernel RW, user NONE
|
||||
//
|
||||
// LAB 4: Your code here:
|
||||
for(int i = 0; i < NCPU; i++) {
|
||||
uintptr_t kstacktop = KSTACKTOP - i * (KSTKSIZE + KSTKGAP);
|
||||
boot_map_region(kern_pgdir, kstacktop - KSTKSIZE,
|
||||
KSTKSIZE, PADDR(percpu_kstacks[i]), PTE_W);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Tracking of physical pages.
|
||||
// The 'pages' array has one 'struct PageInfo' entry per physical page.
|
||||
@@ -268,6 +297,7 @@ is_reserved(size_t pagenum) {
|
||||
if(pagenum == 0) return true;
|
||||
if(pagenum >= PGNUM(IOPHYSMEM) &&
|
||||
pagenum < PGNUM(PADDR(boot_alloc(0)))) return true;
|
||||
if(pagenum == PGNUM(MPENTRY_PADDR)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -281,6 +311,10 @@ is_reserved(size_t pagenum) {
|
||||
void
|
||||
page_init(void)
|
||||
{
|
||||
// LAB 4:
|
||||
// Change your code to mark the physical page at MPENTRY_PADDR
|
||||
// as in use
|
||||
|
||||
// The example code here marks all physical pages as free.
|
||||
// However this is not truly the case. What memory is free?
|
||||
// 1) Mark physical page 0 as in use.
|
||||
@@ -535,10 +569,53 @@ void
|
||||
tlb_invalidate(pde_t *pgdir, void *va)
|
||||
{
|
||||
// Flush the entry only if we're modifying the current address space.
|
||||
// For now, there is only one address space, so always invalidate.
|
||||
if (!curenv || curenv->env_pgdir == pgdir)
|
||||
invlpg(va);
|
||||
}
|
||||
|
||||
//
|
||||
// Reserve size bytes in the MMIO region and map [pa,pa+size) at this
|
||||
// location. Return the base of the reserved region. size does *not*
|
||||
// have to be multiple of PGSIZE.
|
||||
//
|
||||
void *
|
||||
mmio_map_region(physaddr_t pa, size_t size)
|
||||
{
|
||||
// Where to start the next region. Initially, this is the
|
||||
// beginning of the MMIO region. Because this is static, its
|
||||
// value will be preserved between calls to mmio_map_region
|
||||
// (just like nextfree in boot_alloc).
|
||||
static uintptr_t base = MMIOBASE;
|
||||
|
||||
// Reserve size bytes of virtual memory starting at base and
|
||||
// map physical pages [pa,pa+size) to virtual addresses
|
||||
// [base,base+size). Since this is device memory and not
|
||||
// regular DRAM, you'll have to tell the CPU that it isn't
|
||||
// safe to cache access to this memory. Luckily, the page
|
||||
// tables provide bits for this purpose; simply create the
|
||||
// mapping with PTE_PCD|PTE_PWT (cache-disable and
|
||||
// write-through) in addition to PTE_W. (If you're interested
|
||||
// in more details on this, see section 10.5 of IA32 volume
|
||||
// 3A.)
|
||||
//
|
||||
// Be sure to round size up to a multiple of PGSIZE and to
|
||||
// handle if this reservation would overflow MMIOLIM (it's
|
||||
// okay to simply panic if this happens).
|
||||
//
|
||||
// Hint: The staff solution uses boot_map_region.
|
||||
//
|
||||
// Your code here:
|
||||
size = ROUNDUP(size, PGSIZE);
|
||||
if((base + size) > MMIOLIM)
|
||||
panic("Not enough memory-mapped IO space!");
|
||||
|
||||
boot_map_region(kern_pgdir, base, size, pa, PTE_PCD | PTE_PWT | PTE_W);
|
||||
uintptr_t to_return = base;
|
||||
base += size;
|
||||
|
||||
return (void*) to_return;
|
||||
}
|
||||
|
||||
static uintptr_t user_mem_check_addr;
|
||||
|
||||
//
|
||||
@@ -654,6 +731,8 @@ check_page_free_list(bool only_low_memory)
|
||||
assert(page2pa(pp) != EXTPHYSMEM - PGSIZE);
|
||||
assert(page2pa(pp) != EXTPHYSMEM);
|
||||
assert(page2pa(pp) < EXTPHYSMEM || (char *) page2kva(pp) >= first_free_page);
|
||||
// (new test for lab 4)
|
||||
assert(page2pa(pp) != MPENTRY_PADDR);
|
||||
|
||||
if (page2pa(pp) < EXTPHYSMEM)
|
||||
++nfree_basemem;
|
||||
@@ -776,9 +855,15 @@ check_kern_pgdir(void)
|
||||
assert(check_va2pa(pgdir, KERNBASE + i) == i);
|
||||
|
||||
// check kernel stack
|
||||
// (updated in lab 4 to check per-CPU kernel stacks)
|
||||
for (n = 0; n < NCPU; n++) {
|
||||
uint32_t base = KSTACKTOP - (KSTKSIZE + KSTKGAP) * (n + 1);
|
||||
for (i = 0; i < KSTKSIZE; i += PGSIZE)
|
||||
assert(check_va2pa(pgdir, KSTACKTOP - KSTKSIZE + i) == PADDR(bootstack) + i);
|
||||
assert(check_va2pa(pgdir, KSTACKTOP - PTSIZE) == ~0);
|
||||
assert(check_va2pa(pgdir, base + KSTKGAP + i)
|
||||
== PADDR(percpu_kstacks[n]) + i);
|
||||
for (i = 0; i < KSTKGAP; i += PGSIZE)
|
||||
assert(check_va2pa(pgdir, base + i) == ~0);
|
||||
}
|
||||
|
||||
// check PDE permissions
|
||||
for (i = 0; i < NPDENTRIES; i++) {
|
||||
@@ -787,6 +872,7 @@ check_kern_pgdir(void)
|
||||
case PDX(KSTACKTOP-1):
|
||||
case PDX(UPAGES):
|
||||
case PDX(UENVS):
|
||||
case PDX(MMIOBASE):
|
||||
assert(pgdir[i] & PTE_P);
|
||||
break;
|
||||
default:
|
||||
@@ -829,6 +915,7 @@ check_page(void)
|
||||
struct PageInfo *fl;
|
||||
pte_t *ptep, *ptep1;
|
||||
void *va;
|
||||
uintptr_t mm1, mm2;
|
||||
int i;
|
||||
extern pde_t entry_pgdir[];
|
||||
|
||||
@@ -971,6 +1058,29 @@ check_page(void)
|
||||
page_free(pp1);
|
||||
page_free(pp2);
|
||||
|
||||
// test mmio_map_region
|
||||
mm1 = (uintptr_t) mmio_map_region(0, 4097);
|
||||
mm2 = (uintptr_t) mmio_map_region(0, 4096);
|
||||
// check that they're in the right region
|
||||
assert(mm1 >= MMIOBASE && mm1 + 8192 < MMIOLIM);
|
||||
assert(mm2 >= MMIOBASE && mm2 + 8192 < MMIOLIM);
|
||||
// check that they're page-aligned
|
||||
assert(mm1 % PGSIZE == 0 && mm2 % PGSIZE == 0);
|
||||
// check that they don't overlap
|
||||
assert(mm1 + 8192 <= mm2);
|
||||
// check page mappings
|
||||
assert(check_va2pa(kern_pgdir, mm1) == 0);
|
||||
assert(check_va2pa(kern_pgdir, mm1+PGSIZE) == PGSIZE);
|
||||
assert(check_va2pa(kern_pgdir, mm2) == 0);
|
||||
assert(check_va2pa(kern_pgdir, mm2+PGSIZE) == ~0);
|
||||
// check permissions
|
||||
assert(*pgdir_walk(kern_pgdir, (void*) mm1, 0) & (PTE_W|PTE_PWT|PTE_PCD));
|
||||
assert(!(*pgdir_walk(kern_pgdir, (void*) mm1, 0) & PTE_U));
|
||||
// clear the mappings
|
||||
*pgdir_walk(kern_pgdir, (void*) mm1, 0) = 0;
|
||||
*pgdir_walk(kern_pgdir, (void*) mm1 + PGSIZE, 0) = 0;
|
||||
*pgdir_walk(kern_pgdir, (void*) mm2, 0) = 0;
|
||||
|
||||
cprintf("check_page() succeeded!\n");
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ void page_decref(struct PageInfo *pp);
|
||||
|
||||
void tlb_invalidate(pde_t *pgdir, void *va);
|
||||
|
||||
void * mmio_map_region(physaddr_t pa, size_t size);
|
||||
|
||||
int user_mem_check(struct Env *env, const void *va, size_t len, int perm);
|
||||
void user_mem_assert(struct Env *env, const void *va, size_t len, int perm);
|
||||
|
||||
|
||||
103
kern/sched.c
Normal file
103
kern/sched.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <inc/assert.h>
|
||||
#include <inc/x86.h>
|
||||
#include <kern/spinlock.h>
|
||||
#include <kern/env.h>
|
||||
#include <kern/pmap.h>
|
||||
#include <kern/monitor.h>
|
||||
|
||||
void sched_halt(void);
|
||||
|
||||
// Choose a user environment to run and run it.
|
||||
void
|
||||
sched_yield(void)
|
||||
{
|
||||
struct Env *idle;
|
||||
|
||||
// Implement simple round-robin scheduling.
|
||||
//
|
||||
// Search through 'envs' for an ENV_RUNNABLE environment in
|
||||
// circular fashion starting just after the env this CPU was
|
||||
// last running. Switch to the first such environment found.
|
||||
//
|
||||
// If no envs are runnable, but the environment previously
|
||||
// running on this CPU is still ENV_RUNNING, it's okay to
|
||||
// choose that environment. Make sure curenv is not null before
|
||||
// dereferencing it.
|
||||
//
|
||||
// Never choose an environment that's currently running on
|
||||
// another CPU (env_status == ENV_RUNNING). If there are
|
||||
// no runnable environments, simply drop through to the code
|
||||
// below to halt the cpu.
|
||||
|
||||
// LAB 4: Your code here.
|
||||
struct Env* next_env = curenv ? curenv + 1 : envs;
|
||||
struct Env* end_env = envs + NENV;
|
||||
struct Env* to_run = NULL;
|
||||
|
||||
for(int i = 0; i < NENV; i++, next_env++) {
|
||||
if(next_env == end_env) next_env = envs;
|
||||
if(next_env->env_status == ENV_RUNNABLE) {
|
||||
to_run = next_env;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!to_run && curenv && curenv->env_status == ENV_RUNNING) {
|
||||
to_run = curenv;
|
||||
}
|
||||
|
||||
if(to_run) env_run(to_run);
|
||||
|
||||
// sched_halt never returns
|
||||
sched_halt();
|
||||
}
|
||||
|
||||
// Halt this CPU when there is nothing to do. Wait until the
|
||||
// timer interrupt wakes it up. This function never returns.
|
||||
//
|
||||
void
|
||||
sched_halt(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
// For debugging and testing purposes, if there are no runnable
|
||||
// environments in the system, then drop into the kernel monitor.
|
||||
for (i = 0; i < NENV; i++) {
|
||||
if ((envs[i].env_status == ENV_RUNNABLE ||
|
||||
envs[i].env_status == ENV_RUNNING ||
|
||||
envs[i].env_status == ENV_DYING))
|
||||
break;
|
||||
}
|
||||
if (i == NENV) {
|
||||
cprintf("No runnable environments in the system!\n");
|
||||
while (1)
|
||||
monitor(NULL);
|
||||
}
|
||||
|
||||
// Mark that no environment is running on this CPU
|
||||
curenv = NULL;
|
||||
lcr3(PADDR(kern_pgdir));
|
||||
|
||||
// Mark that this CPU is in the HALT state, so that when
|
||||
// timer interupts come in, we know we should re-acquire the
|
||||
// big kernel lock
|
||||
xchg(&thiscpu->cpu_status, CPU_HALTED);
|
||||
|
||||
// Release the big kernel lock as if we were "leaving" the kernel
|
||||
unlock_kernel();
|
||||
|
||||
// Reset stack pointer, enable interrupts and then halt.
|
||||
asm volatile (
|
||||
"movl $0, %%ebp\n"
|
||||
"movl %0, %%esp\n"
|
||||
"pushl $0\n"
|
||||
"pushl $0\n"
|
||||
// LAB 4:
|
||||
// Uncomment the following line after completing exercise 13
|
||||
"sti\n"
|
||||
"1:\n"
|
||||
"hlt\n"
|
||||
"jmp 1b\n"
|
||||
: : "a" (thiscpu->cpu_ts.ts_esp0));
|
||||
}
|
||||
|
||||
12
kern/sched.h
Normal file
12
kern/sched.h
Normal file
@@ -0,0 +1,12 @@
|
||||
/* See COPYRIGHT for copyright information. */
|
||||
|
||||
#ifndef JOS_KERN_SCHED_H
|
||||
#define JOS_KERN_SCHED_H
|
||||
#ifndef JOS_KERNEL
|
||||
# error "This is a JOS kernel header; user programs should not #include it"
|
||||
#endif
|
||||
|
||||
// This function does not return.
|
||||
void sched_yield(void) __attribute__((noreturn));
|
||||
|
||||
#endif // !JOS_KERN_SCHED_H
|
||||
116
kern/spinlock.c
Normal file
116
kern/spinlock.c
Normal file
@@ -0,0 +1,116 @@
|
||||
// Mutual exclusion spin locks.
|
||||
|
||||
#include <inc/types.h>
|
||||
#include <inc/assert.h>
|
||||
#include <inc/x86.h>
|
||||
#include <inc/memlayout.h>
|
||||
#include <inc/string.h>
|
||||
#include <kern/cpu.h>
|
||||
#include <kern/spinlock.h>
|
||||
#include <kern/kdebug.h>
|
||||
|
||||
// The big kernel lock
|
||||
struct spinlock kernel_lock = {
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
.name = "kernel_lock"
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
// Record the current call stack in pcs[] by following the %ebp chain.
|
||||
static void
|
||||
get_caller_pcs(uint32_t pcs[])
|
||||
{
|
||||
uint32_t *ebp;
|
||||
int i;
|
||||
|
||||
ebp = (uint32_t *)read_ebp();
|
||||
for (i = 0; i < 10; i++){
|
||||
if (ebp == 0 || ebp < (uint32_t *)ULIM)
|
||||
break;
|
||||
pcs[i] = ebp[1]; // saved %eip
|
||||
ebp = (uint32_t *)ebp[0]; // saved %ebp
|
||||
}
|
||||
for (; i < 10; i++)
|
||||
pcs[i] = 0;
|
||||
}
|
||||
|
||||
// Check whether this CPU is holding the lock.
|
||||
static int
|
||||
holding(struct spinlock *lock)
|
||||
{
|
||||
return lock->locked && lock->cpu == thiscpu;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
__spin_initlock(struct spinlock *lk, char *name)
|
||||
{
|
||||
lk->locked = 0;
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
lk->name = name;
|
||||
lk->cpu = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Acquire the lock.
|
||||
// Loops (spins) until the lock is acquired.
|
||||
// Holding a lock for a long time may cause
|
||||
// other CPUs to waste time spinning to acquire it.
|
||||
void
|
||||
spin_lock(struct spinlock *lk)
|
||||
{
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
if (holding(lk))
|
||||
panic("CPU %d cannot acquire %s: already holding", cpunum(), lk->name);
|
||||
#endif
|
||||
|
||||
// The xchg is atomic.
|
||||
// It also serializes, so that reads after acquire are not
|
||||
// reordered before it.
|
||||
while (xchg(&lk->locked, 1) != 0)
|
||||
asm volatile ("pause");
|
||||
|
||||
// Record info about lock acquisition for debugging.
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
lk->cpu = thiscpu;
|
||||
get_caller_pcs(lk->pcs);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Release the lock.
|
||||
void
|
||||
spin_unlock(struct spinlock *lk)
|
||||
{
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
if (!holding(lk)) {
|
||||
int i;
|
||||
uint32_t pcs[10];
|
||||
// Nab the acquiring EIP chain before it gets released
|
||||
memmove(pcs, lk->pcs, sizeof pcs);
|
||||
cprintf("CPU %d cannot release %s: held by CPU %d\nAcquired at:",
|
||||
cpunum(), lk->name, lk->cpu->cpu_id);
|
||||
for (i = 0; i < 10 && pcs[i]; i++) {
|
||||
struct Eipdebuginfo info;
|
||||
if (debuginfo_eip(pcs[i], &info) >= 0)
|
||||
cprintf(" %08x %s:%d: %.*s+%x\n", pcs[i],
|
||||
info.eip_file, info.eip_line,
|
||||
info.eip_fn_namelen, info.eip_fn_name,
|
||||
pcs[i] - info.eip_fn_addr);
|
||||
else
|
||||
cprintf(" %08x\n", pcs[i]);
|
||||
}
|
||||
panic("spin_unlock");
|
||||
}
|
||||
|
||||
lk->pcs[0] = 0;
|
||||
lk->cpu = 0;
|
||||
#endif
|
||||
|
||||
// The xchg instruction is atomic (i.e. uses the "lock" prefix) with
|
||||
// respect to any other instruction which references the same memory.
|
||||
// x86 CPUs will not reorder loads/stores across locked instructions
|
||||
// (vol 3, 8.2.2). Because xchg() is implemented using asm volatile,
|
||||
// gcc will not reorder C statements across the xchg.
|
||||
xchg(&lk->locked, 0);
|
||||
}
|
||||
48
kern/spinlock.h
Normal file
48
kern/spinlock.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef JOS_INC_SPINLOCK_H
|
||||
#define JOS_INC_SPINLOCK_H
|
||||
|
||||
#include <inc/types.h>
|
||||
|
||||
// Comment this to disable spinlock debugging
|
||||
#define DEBUG_SPINLOCK
|
||||
|
||||
// Mutual exclusion lock.
|
||||
struct spinlock {
|
||||
unsigned locked; // Is the lock held?
|
||||
|
||||
#ifdef DEBUG_SPINLOCK
|
||||
// For debugging:
|
||||
char *name; // Name of lock.
|
||||
struct CpuInfo *cpu; // The CPU holding the lock.
|
||||
uintptr_t pcs[10]; // The call stack (an array of program counters)
|
||||
// that locked the lock.
|
||||
#endif
|
||||
};
|
||||
|
||||
void __spin_initlock(struct spinlock *lk, char *name);
|
||||
void spin_lock(struct spinlock *lk);
|
||||
void spin_unlock(struct spinlock *lk);
|
||||
|
||||
#define spin_initlock(lock) __spin_initlock(lock, #lock)
|
||||
|
||||
extern struct spinlock kernel_lock;
|
||||
|
||||
static inline void
|
||||
lock_kernel(void)
|
||||
{
|
||||
spin_lock(&kernel_lock);
|
||||
}
|
||||
|
||||
static inline void
|
||||
unlock_kernel(void)
|
||||
{
|
||||
spin_unlock(&kernel_lock);
|
||||
|
||||
// Normally we wouldn't need to do this, but QEMU only runs
|
||||
// one CPU at a time and has a long time-slice. Without the
|
||||
// pause, this CPU is likely to reacquire the lock before
|
||||
// another CPU has even been given a chance to acquire it.
|
||||
asm volatile("pause");
|
||||
}
|
||||
|
||||
#endif
|
||||
352
kern/syscall.c
352
kern/syscall.c
@@ -10,6 +10,7 @@
|
||||
#include <kern/trap.h>
|
||||
#include <kern/syscall.h>
|
||||
#include <kern/console.h>
|
||||
#include <kern/sched.h>
|
||||
|
||||
// Print a string to the system console.
|
||||
// The string is exactly 'len' characters long.
|
||||
@@ -52,14 +53,336 @@ sys_env_destroy(envid_t envid)
|
||||
|
||||
if ((r = envid2env(envid, &e, 1)) < 0)
|
||||
return r;
|
||||
if (e == curenv)
|
||||
cprintf("[%08x] exiting gracefully\n", curenv->env_id);
|
||||
else
|
||||
cprintf("[%08x] destroying %08x\n", curenv->env_id, e->env_id);
|
||||
env_destroy(e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Deschedule current environment and pick a different one to run.
|
||||
static void
|
||||
sys_yield(void)
|
||||
{
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
// Allocate a new environment.
|
||||
// Returns envid of new environment, or < 0 on error. Errors are:
|
||||
// -E_NO_FREE_ENV if no free environment is available.
|
||||
// -E_NO_MEM on memory exhaustion.
|
||||
static envid_t
|
||||
sys_exofork(void)
|
||||
{
|
||||
// Create the new environment with env_alloc(), from kern/env.c.
|
||||
// It should be left as env_alloc created it, except that
|
||||
// status is set to ENV_NOT_RUNNABLE, and the register set is copied
|
||||
// from the current environment -- but tweaked so sys_exofork
|
||||
// will appear to return 0.
|
||||
struct Env* new_env;
|
||||
int error_code;
|
||||
|
||||
error_code = env_alloc(&new_env, curenv->env_id);
|
||||
if(error_code < 0) return error_code;
|
||||
|
||||
new_env->env_tf = curenv->env_tf;
|
||||
new_env->env_tf.tf_regs.reg_eax = 0;
|
||||
new_env->env_status = ENV_NOT_RUNNABLE;
|
||||
return new_env->env_id;
|
||||
}
|
||||
|
||||
// Set envid's env_status to status, which must be ENV_RUNNABLE
|
||||
// or ENV_NOT_RUNNABLE.
|
||||
//
|
||||
// Returns 0 on success, < 0 on error. Errors are:
|
||||
// -E_BAD_ENV if environment envid doesn't currently exist,
|
||||
// or the caller doesn't have permission to change envid.
|
||||
// -E_INVAL if status is not a valid status for an environment.
|
||||
static int
|
||||
sys_env_set_status(envid_t envid, int status)
|
||||
{
|
||||
// Hint: Use the 'envid2env' function from kern/env.c to translate an
|
||||
// envid to a struct Env.
|
||||
// You should set envid2env's third argument to 1, which will
|
||||
// check whether the current environment has permission to set
|
||||
// envid's status.
|
||||
struct Env* env;
|
||||
int error_code;
|
||||
|
||||
error_code = envid2env(envid, &env, 1);
|
||||
if(error_code < 0) return error_code;
|
||||
|
||||
if(status != ENV_RUNNABLE && status != ENV_NOT_RUNNABLE)
|
||||
return -E_INVAL;
|
||||
|
||||
env->env_status = status;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define SYS_CHECKPERMS(perm) \
|
||||
((((perm) & (PTE_P | PTE_U)) == (PTE_P | PTE_U)) && \
|
||||
(((perm) & ~(PTE_P | PTE_U | PTE_W | PTE_AVAIL)) == 0))
|
||||
#define SYS_CHECKADDR(addr) (((uintptr_t) (addr) < UTOP) && ((uintptr_t) (addr) % PGSIZE == 0))
|
||||
|
||||
// Set envid's trap frame to 'tf'.
|
||||
// tf is modified to make sure that user environments always run at code
|
||||
// protection level 3 (CPL 3), interrupts enabled, and IOPL of 0.
|
||||
//
|
||||
// Returns 0 on success, < 0 on error. Errors are:
|
||||
// -E_BAD_ENV if environment envid doesn't currently exist,
|
||||
// or the caller doesn't have permission to change envid.
|
||||
static int
|
||||
sys_env_set_trapframe(envid_t envid, struct Trapframe *tf)
|
||||
{
|
||||
// LAB 5: Your code here.
|
||||
// Remember to check whether the user has supplied us with a good
|
||||
// address!
|
||||
int r;
|
||||
struct Env* chenv;
|
||||
SYS_CHECKADDR(tf);
|
||||
|
||||
if((r = envid2env(envid, &chenv, true)) < 0)
|
||||
return r;
|
||||
|
||||
tf->tf_cs |= 3;
|
||||
tf->tf_eflags &= ~(FL_IOPL_3);
|
||||
tf->tf_eflags |= FL_IF;
|
||||
|
||||
chenv->env_tf = *tf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Set the page fault upcall for 'envid' by modifying the corresponding struct
|
||||
// Env's 'env_pgfault_upcall' field. When 'envid' causes a page fault, the
|
||||
// kernel will push a fault record onto the exception stack, then branch to
|
||||
// 'func'.
|
||||
//
|
||||
// Returns 0 on success, < 0 on error. Errors are:
|
||||
// -E_BAD_ENV if environment envid doesn't currently exist,
|
||||
// or the caller doesn't have permission to change envid.
|
||||
static int
|
||||
sys_env_set_pgfault_upcall(envid_t envid, void *func)
|
||||
{
|
||||
struct Env* env;
|
||||
int return_code;
|
||||
if((return_code = envid2env(envid, &env, 1)) < 0) return return_code;
|
||||
env->env_pgfault_upcall = func;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Allocate a page of memory and map it at 'va' with permission
|
||||
// 'perm' in the address space of 'envid'.
|
||||
// The page's contents are set to 0.
|
||||
// If a page is already mapped at 'va', that page is unmapped as a
|
||||
// side effect.
|
||||
//
|
||||
// perm -- PTE_U | PTE_P must be set, PTE_AVAIL | PTE_W may or may not be set,
|
||||
// but no other bits may be set. See PTE_SYSCALL in inc/mmu.h.
|
||||
//
|
||||
// Return 0 on success, < 0 on error. Errors are:
|
||||
// -E_BAD_ENV if environment envid doesn't currently exist,
|
||||
// or the caller doesn't have permission to change envid.
|
||||
// -E_INVAL if va >= UTOP, or va is not page-aligned.
|
||||
// -E_INVAL if perm is inappropriate (see above).
|
||||
// -E_NO_MEM if there's no memory to allocate the new page,
|
||||
// or to allocate any necessary page tables.
|
||||
static int
|
||||
sys_page_alloc(envid_t envid, void *va, int perm)
|
||||
{
|
||||
// Hint: This function is a wrapper around page_alloc() and
|
||||
// page_insert() from kern/pmap.c.
|
||||
// Most of the new code you write should be to check the
|
||||
// parameters for correctness.
|
||||
// If page_insert() fails, remember to free the page you
|
||||
// allocated!
|
||||
struct Env* env;
|
||||
int return_code;
|
||||
if((return_code = envid2env(envid, &env, 1)) < 0) return return_code;
|
||||
|
||||
if(!SYS_CHECKPERMS(perm)) return -E_INVAL;
|
||||
if(!SYS_CHECKADDR(va)) return -E_INVAL;
|
||||
|
||||
struct PageInfo* page = page_alloc(1);
|
||||
if(!page) return -E_NO_MEM;
|
||||
|
||||
if((return_code = page_insert(env->env_pgdir, page, va, perm)) < 0) {
|
||||
page_free(page);
|
||||
return return_code;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Map the page of memory at 'srcva' in srcenvid's address space
|
||||
// at 'dstva' in dstenvid's address space with permission 'perm'.
|
||||
// Perm has the same restrictions as in sys_page_alloc, except
|
||||
// that it also must not grant write access to a read-only
|
||||
// page.
|
||||
//
|
||||
// Return 0 on success, < 0 on error. Errors are:
|
||||
// -E_BAD_ENV if srcenvid and/or dstenvid doesn't currently exist,
|
||||
// or the caller doesn't have permission to change one of them.
|
||||
// -E_INVAL if srcva >= UTOP or srcva is not page-aligned,
|
||||
// or dstva >= UTOP or dstva is not page-aligned.
|
||||
// -E_INVAL is srcva is not mapped in srcenvid's address space.
|
||||
// -E_INVAL if perm is inappropriate (see sys_page_alloc).
|
||||
// -E_INVAL if (perm & PTE_W), but srcva is read-only in srcenvid's
|
||||
// address space.
|
||||
// -E_NO_MEM if there's no memory to allocate any necessary page tables.
|
||||
static int
|
||||
sys_page_map(envid_t srcenvid, void *srcva,
|
||||
envid_t dstenvid, void *dstva, int perm)
|
||||
{
|
||||
// Hint: This function is a wrapper around page_lookup() and
|
||||
// page_insert() from kern/pmap.c.
|
||||
// Again, most of the new code you write should be to check the
|
||||
// parameters for correctness.
|
||||
// Use the third argument to page_lookup() to
|
||||
// check the current permissions on the page.
|
||||
struct Env *srcenv, *dstenv;
|
||||
pte_t* srcpte;
|
||||
int return_code;
|
||||
|
||||
if((return_code = envid2env(srcenvid, &srcenv, 1)) < 0) return return_code;
|
||||
if((return_code = envid2env(dstenvid, &dstenv, 1)) < 0) return return_code;
|
||||
|
||||
if(!SYS_CHECKADDR(srcva)) return -E_INVAL;
|
||||
if(!SYS_CHECKADDR(dstva)) return -E_INVAL;
|
||||
if(!SYS_CHECKPERMS(perm)) return -E_INVAL;
|
||||
|
||||
struct PageInfo* page = page_lookup(srcenv->env_pgdir, srcva, &srcpte);
|
||||
if(page == NULL) return -E_INVAL;
|
||||
if(perm & PTE_W && !(*srcpte & PTE_W)) return -E_INVAL;
|
||||
|
||||
if((return_code = page_insert(dstenv->env_pgdir, page, dstva, perm)) < 0)
|
||||
return return_code;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Unmap the page of memory at 'va' in the address space of 'envid'.
|
||||
// If no page is mapped, the function silently succeeds.
|
||||
//
|
||||
// Return 0 on success, < 0 on error. Errors are:
|
||||
// -E_BAD_ENV if environment envid doesn't currently exist,
|
||||
// or the caller doesn't have permission to change envid.
|
||||
// -E_INVAL if va >= UTOP, or va is not page-aligned.
|
||||
static int
|
||||
sys_page_unmap(envid_t envid, void *va)
|
||||
{
|
||||
// Hint: This function is a wrapper around page_remove().
|
||||
struct Env* env;
|
||||
int return_code;
|
||||
|
||||
if((return_code = envid2env(envid, &env, 1)) < 0) return return_code;
|
||||
if(!SYS_CHECKADDR(va)) return -E_INVAL;
|
||||
|
||||
page_remove(env->env_pgdir, va);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Try to send 'value' to the target env 'envid'.
|
||||
// If srcva < UTOP, then also send page currently mapped at 'srcva',
|
||||
// so that receiver gets a duplicate mapping of the same page.
|
||||
//
|
||||
// The send fails with a return value of -E_IPC_NOT_RECV if the
|
||||
// target is not blocked, waiting for an IPC.
|
||||
//
|
||||
// The send also can fail for the other reasons listed below.
|
||||
//
|
||||
// Otherwise, the send succeeds, and the target's ipc fields are
|
||||
// updated as follows:
|
||||
// env_ipc_recving is set to 0 to block future sends;
|
||||
// env_ipc_from is set to the sending envid;
|
||||
// env_ipc_value is set to the 'value' parameter;
|
||||
// env_ipc_perm is set to 'perm' if a page was transferred, 0 otherwise.
|
||||
// The target environment is marked runnable again, returning 0
|
||||
// from the paused sys_ipc_recv system call. (Hint: does the
|
||||
// sys_ipc_recv function ever actually return?)
|
||||
//
|
||||
// If the sender wants to send a page but the receiver isn't asking for one,
|
||||
// then no page mapping is transferred, but no error occurs.
|
||||
// The ipc only happens when no errors occur.
|
||||
//
|
||||
// Returns 0 on success, < 0 on error.
|
||||
// Errors are:
|
||||
// -E_BAD_ENV if environment envid doesn't currently exist.
|
||||
// (No need to check permissions.)
|
||||
// -E_IPC_NOT_RECV if envid is not currently blocked in sys_ipc_recv,
|
||||
// or another environment managed to send first.
|
||||
// -E_INVAL if srcva < UTOP but srcva is not page-aligned.
|
||||
// -E_INVAL if srcva < UTOP and perm is inappropriate
|
||||
// (see sys_page_alloc).
|
||||
// -E_INVAL if srcva < UTOP but srcva is not mapped in the caller's
|
||||
// address space.
|
||||
// -E_INVAL if (perm & PTE_W), but srcva is read-only in the
|
||||
// current environment's address space.
|
||||
// -E_NO_MEM if there's not enough memory to map srcva in envid's
|
||||
// address space.
|
||||
static int
|
||||
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, unsigned perm)
|
||||
{
|
||||
struct Env* dest_env;
|
||||
struct Env* src_env;
|
||||
int return_code;
|
||||
|
||||
if((return_code = envid2env(0, &src_env, 0)) < 0)
|
||||
return return_code;
|
||||
if((return_code = envid2env(envid, &dest_env, 0)) < 0)
|
||||
return return_code;
|
||||
|
||||
if(!dest_env->env_ipc_recving)
|
||||
return -E_IPC_NOT_RECV;
|
||||
|
||||
if((uintptr_t) srcva < UTOP && dest_env->env_ipc_dstva) {
|
||||
if(!SYS_CHECKADDR(srcva)) return -E_INVAL;
|
||||
if(!SYS_CHECKPERMS(perm)) return -E_INVAL;
|
||||
|
||||
pte_t* srcpte;
|
||||
struct PageInfo* page = page_lookup(src_env->env_pgdir, srcva, &srcpte);
|
||||
if(page == NULL) return -E_INVAL;
|
||||
if(perm & PTE_W && !(*srcpte & PTE_W)) return -E_INVAL;
|
||||
|
||||
page_insert(dest_env->env_pgdir, page, dest_env->env_ipc_dstva, perm);
|
||||
dest_env->env_ipc_perm = perm;
|
||||
}
|
||||
|
||||
dest_env->env_ipc_from = src_env->env_id;
|
||||
dest_env->env_ipc_value = value;
|
||||
dest_env->env_ipc_recving = false;
|
||||
if(dest_env->env_status == ENV_NOT_RUNNABLE)
|
||||
dest_env->env_status = ENV_RUNNABLE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Block until a value is ready. Record that you want to receive
|
||||
// using the env_ipc_recving and env_ipc_dstva fields of struct Env,
|
||||
// mark yourself not runnable, and then give up the CPU.
|
||||
//
|
||||
// If 'dstva' is < UTOP, then you are willing to receive a page of data.
|
||||
// 'dstva' is the virtual address at which the sent page should be mapped.
|
||||
//
|
||||
// This function only returns on error, but the system call will eventually
|
||||
// return 0 on success.
|
||||
// Return < 0 on error. Errors are:
|
||||
// -E_INVAL if dstva < UTOP but dstva is not page-aligned.
|
||||
static int
|
||||
sys_ipc_recv(void *dstva)
|
||||
{
|
||||
struct Env* env;
|
||||
int return_code;
|
||||
|
||||
if((return_code = envid2env(0, &env, 1)) < 0)
|
||||
return return_code;
|
||||
|
||||
// LAB 4: Your code here.
|
||||
if((uintptr_t) dstva < UTOP) {
|
||||
if(!SYS_CHECKADDR(dstva)) return -E_INVAL;
|
||||
env->env_ipc_dstva = dstva;
|
||||
}
|
||||
env->env_ipc_recving = true;
|
||||
env->env_status = ENV_NOT_RUNNABLE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Dispatches to the correct kernel function, passing the arguments.
|
||||
int32_t
|
||||
syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5)
|
||||
@@ -78,6 +401,27 @@ syscall(uint32_t syscallno, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4,
|
||||
return sys_getenvid();
|
||||
case SYS_env_destroy:
|
||||
return sys_env_destroy(a1);
|
||||
case SYS_yield:
|
||||
sys_yield();
|
||||
return 0;
|
||||
case SYS_exofork:
|
||||
return sys_exofork();
|
||||
case SYS_env_set_status:
|
||||
return sys_env_set_status(a1, a2);
|
||||
case SYS_env_set_pgfault_upcall:
|
||||
return sys_env_set_pgfault_upcall(a1, (void*) a2);
|
||||
case SYS_page_alloc:
|
||||
return sys_page_alloc(a1, (void*) a2, a3);
|
||||
case SYS_page_map:
|
||||
return sys_page_map(a1, (void*) a2, a3, (void*) a4, a5);
|
||||
case SYS_page_unmap:
|
||||
return sys_page_unmap(a1, (void*) a2);
|
||||
case SYS_ipc_try_send:
|
||||
return sys_ipc_try_send(a1, a2, (void*) a3, a4);
|
||||
case SYS_ipc_recv:
|
||||
return sys_ipc_recv((void*) a1);
|
||||
case SYS_env_set_trapframe:
|
||||
return sys_env_set_trapframe(a1, (void*) a2);
|
||||
default:
|
||||
return -E_INVAL;
|
||||
}
|
||||
|
||||
176
kern/trap.c
176
kern/trap.c
@@ -8,6 +8,11 @@
|
||||
#include <kern/monitor.h>
|
||||
#include <kern/env.h>
|
||||
#include <kern/syscall.h>
|
||||
#include <kern/sched.h>
|
||||
#include <kern/kclock.h>
|
||||
#include <kern/picirq.h>
|
||||
#include <kern/cpu.h>
|
||||
#include <kern/spinlock.h>
|
||||
|
||||
static struct Taskstate ts;
|
||||
|
||||
@@ -55,6 +60,8 @@ static const char *trapname(int trapno)
|
||||
return excnames[trapno];
|
||||
if (trapno == T_SYSCALL)
|
||||
return "System call";
|
||||
if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16)
|
||||
return "Hardware Interrupt";
|
||||
return "(unknown trap)";
|
||||
}
|
||||
|
||||
@@ -82,6 +89,13 @@ void t_simderr();
|
||||
void t_syscall();
|
||||
void t_default();
|
||||
|
||||
void irq_timer();
|
||||
void irq_kbd();
|
||||
void irq_serial();
|
||||
void irq_spurious();
|
||||
void irq_ide();
|
||||
void irq_error();
|
||||
|
||||
void
|
||||
trap_init(void)
|
||||
{
|
||||
@@ -118,6 +132,13 @@ trap_init(void)
|
||||
SETGATE(idt[T_SYSCALL], 0, GD_KT, t_syscall, 3);
|
||||
SETGATE(idt[T_DEFAULT], 0, GD_KT, t_default, 0);
|
||||
|
||||
SETGATE(idt[IRQ_OFFSET + IRQ_TIMER], 0, GD_KT, irq_timer, 0);
|
||||
SETGATE(idt[IRQ_OFFSET + IRQ_KBD], 0, GD_KT, irq_kbd, 0);
|
||||
SETGATE(idt[IRQ_OFFSET + IRQ_SERIAL], 0, GD_KT, irq_serial, 0);
|
||||
SETGATE(idt[IRQ_OFFSET + IRQ_SPURIOUS], 0, GD_KT, irq_spurious, 0);
|
||||
SETGATE(idt[IRQ_OFFSET + IRQ_IDE], 0, GD_KT, irq_ide, 0);
|
||||
SETGATE(idt[IRQ_OFFSET + IRQ_ERROR], 0, GD_KT, irq_error, 0);
|
||||
|
||||
// Per-CPU setup
|
||||
trap_init_percpu();
|
||||
}
|
||||
@@ -126,20 +147,45 @@ trap_init(void)
|
||||
void
|
||||
trap_init_percpu(void)
|
||||
{
|
||||
// The example code here sets up the Task State Segment (TSS) and
|
||||
// the TSS descriptor for CPU 0. But it is incorrect if we are
|
||||
// running on other CPUs because each CPU has its own kernel stack.
|
||||
// Fix the code so that it works for all CPUs.
|
||||
//
|
||||
// Hints:
|
||||
// - The macro "thiscpu" always refers to the current CPU's
|
||||
// struct CpuInfo;
|
||||
// - The ID of the current CPU is given by cpunum() or
|
||||
// thiscpu->cpu_id;
|
||||
// - Use "thiscpu->cpu_ts" as the TSS for the current CPU,
|
||||
// rather than the global "ts" variable;
|
||||
// - Use gdt[(GD_TSS0 >> 3) + i] for CPU i's TSS descriptor;
|
||||
// - You mapped the per-CPU kernel stacks in mem_init_mp()
|
||||
// - Initialize cpu_ts.ts_iomb to prevent unauthorized environments
|
||||
// from doing IO (0 is not the correct value!)
|
||||
//
|
||||
// ltr sets a 'busy' flag in the TSS selector, so if you
|
||||
// accidentally load the same TSS on more than one CPU, you'll
|
||||
// get a triple fault. If you set up an individual CPU's TSS
|
||||
// wrong, you may not get a fault until you try to return from
|
||||
// user space on that CPU.
|
||||
//
|
||||
// LAB 4: Your code here:
|
||||
|
||||
// Setup a TSS so that we get the right stack
|
||||
// when we trap to the kernel.
|
||||
ts.ts_esp0 = KSTACKTOP;
|
||||
ts.ts_ss0 = GD_KD;
|
||||
ts.ts_iomb = sizeof(struct Taskstate);
|
||||
thiscpu->cpu_ts.ts_esp0 = KSTACKTOP - thiscpu->cpu_id * (KSTKSIZE + KSTKGAP);
|
||||
thiscpu->cpu_ts.ts_ss0 = GD_KD;
|
||||
thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate);
|
||||
|
||||
// Initialize the TSS slot of the gdt.
|
||||
gdt[GD_TSS0 >> 3] = SEG16(STS_T32A, (uint32_t) (&ts),
|
||||
gdt[(GD_TSS0 >> 3) + cpunum()] = SEG16(STS_T32A, (uint32_t) (&thiscpu->cpu_ts),
|
||||
sizeof(struct Taskstate) - 1, 0);
|
||||
gdt[GD_TSS0 >> 3].sd_s = 0;
|
||||
gdt[(GD_TSS0 >> 3) + cpunum()].sd_s = 0;
|
||||
|
||||
// Load the TSS selector (like other segment selectors, the
|
||||
// bottom three bits are special; we leave them 0)
|
||||
ltr(GD_TSS0);
|
||||
ltr(GD_TSS0 + (cpunum() << 3));
|
||||
|
||||
// Load the IDT
|
||||
lidt(&idt_pd);
|
||||
@@ -148,7 +194,7 @@ trap_init_percpu(void)
|
||||
void
|
||||
print_trapframe(struct Trapframe *tf)
|
||||
{
|
||||
cprintf("TRAP frame at %p\n", tf);
|
||||
cprintf("TRAP frame at %p from CPU %d\n", tf, cpunum());
|
||||
print_regs(&tf->tf_regs);
|
||||
cprintf(" es 0x----%04x\n", tf->tf_es);
|
||||
cprintf(" ds 0x----%04x\n", tf->tf_ds);
|
||||
@@ -211,8 +257,33 @@ trap_dispatch(struct Trapframe *tf)
|
||||
tf->tf_regs.reg_esi);
|
||||
tf->tf_regs.reg_eax = returned;
|
||||
return;
|
||||
} else if (tf->tf_trapno == IRQ_OFFSET + IRQ_TIMER) {
|
||||
lapic_eoi();
|
||||
sched_yield();
|
||||
} else if (tf->tf_trapno == IRQ_OFFSET + IRQ_KBD) {
|
||||
kbd_intr();
|
||||
return;
|
||||
} else if (tf->tf_trapno == IRQ_OFFSET + IRQ_SERIAL) {
|
||||
serial_intr();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle spurious interrupts
|
||||
// The hardware sometimes raises these because of noise on the
|
||||
// IRQ line or other reasons. We don't care.
|
||||
if (tf->tf_trapno == IRQ_OFFSET + IRQ_SPURIOUS) {
|
||||
cprintf("Spurious interrupt on irq 7\n");
|
||||
print_trapframe(tf);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle clock interrupts. Don't forget to acknowledge the
|
||||
// interrupt using lapic_eoi() before calling the scheduler!
|
||||
// LAB 4: Your code here.
|
||||
|
||||
// Handle keyboard and serial interrupts.
|
||||
// LAB 5: Your code here.
|
||||
|
||||
// Unexpected trap: The user process or the kernel has a bug.
|
||||
print_trapframe(tf);
|
||||
if (tf->tf_cs == GD_KT)
|
||||
@@ -230,17 +301,35 @@ trap(struct Trapframe *tf)
|
||||
// of GCC rely on DF being clear
|
||||
asm volatile("cld" ::: "cc");
|
||||
|
||||
// Halt the CPU if some other CPU has called panic()
|
||||
extern char *panicstr;
|
||||
if (panicstr)
|
||||
asm volatile("hlt");
|
||||
|
||||
// Re-acqurie the big kernel lock if we were halted in
|
||||
// sched_yield()
|
||||
if (xchg(&thiscpu->cpu_status, CPU_STARTED) == CPU_HALTED)
|
||||
lock_kernel();
|
||||
// Check that interrupts are disabled. If this assertion
|
||||
// fails, DO NOT be tempted to fix it by inserting a "cli" in
|
||||
// the interrupt path.
|
||||
assert(!(read_eflags() & FL_IF));
|
||||
|
||||
cprintf("Incoming TRAP frame at %p\n", tf);
|
||||
|
||||
if ((tf->tf_cs & 3) == 3) {
|
||||
// Trapped from user mode.
|
||||
// Acquire the big kernel lock before doing any
|
||||
// serious kernel work.
|
||||
// LAB 4: Your code here.
|
||||
lock_kernel();
|
||||
assert(curenv);
|
||||
|
||||
// Garbage collect if current enviroment is a zombie
|
||||
if (curenv->env_status == ENV_DYING) {
|
||||
env_free(curenv);
|
||||
curenv = NULL;
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
// Copy trap frame (which is currently on the stack)
|
||||
// into 'curenv->env_tf', so that running the environment
|
||||
// will restart at the trap point.
|
||||
@@ -256,9 +345,13 @@ trap(struct Trapframe *tf)
|
||||
// Dispatch based on what type of trap occurred
|
||||
trap_dispatch(tf);
|
||||
|
||||
// Return to the current environment, which should be running.
|
||||
assert(curenv && curenv->env_status == ENV_RUNNING);
|
||||
// If we made it to this point, then no other environment was
|
||||
// scheduled, so we should return to the current environment
|
||||
// if doing so makes sense.
|
||||
if (curenv && curenv->env_status == ENV_RUNNING)
|
||||
env_run(curenv);
|
||||
else
|
||||
sched_yield();
|
||||
}
|
||||
|
||||
|
||||
@@ -273,14 +366,75 @@ page_fault_handler(struct Trapframe *tf)
|
||||
// Handle kernel-mode page faults.
|
||||
|
||||
// LAB 3: Your code here.
|
||||
if(!curenv)
|
||||
panic("kernel-level page fault, va %p, eip %p", fault_va, tf->tf_eip);
|
||||
|
||||
// We've already handled kernel-mode exceptions, so if we get here,
|
||||
// the page fault happened in user mode.
|
||||
|
||||
// Call the environment's page fault upcall, if one exists. Set up a
|
||||
// page fault stack frame on the user exception stack (below
|
||||
// UXSTACKTOP), then branch to curenv->env_pgfault_upcall.
|
||||
//
|
||||
// The page fault upcall might cause another page fault, in which case
|
||||
// we branch to the page fault upcall recursively, pushing another
|
||||
// page fault stack frame on top of the user exception stack.
|
||||
//
|
||||
// It is convenient for our code which returns from a page fault
|
||||
// (lib/pfentry.S) to have one word of scratch space at the top of the
|
||||
// trap-time stack; it allows us to more easily restore the eip/esp. In
|
||||
// the non-recursive case, we don't have to worry about this because
|
||||
// the top of the regular user stack is free. In the recursive case,
|
||||
// this means we have to leave an extra word between the current top of
|
||||
// the exception stack and the new stack frame because the exception
|
||||
// stack _is_ the trap-time stack.
|
||||
//
|
||||
// If there's no page fault upcall, the environment didn't allocate a
|
||||
// page for its exception stack or can't write to it, or the exception
|
||||
// stack overflows, then destroy the environment that caused the fault.
|
||||
// Note that the grade script assumes you will first check for the page
|
||||
// fault upcall and print the "user fault va" message below if there is
|
||||
// none. The remaining three checks can be combined into a single test.
|
||||
//
|
||||
// Hints:
|
||||
// user_mem_assert() and env_run() are useful here.
|
||||
// To change what the user environment runs, modify 'curenv->env_tf'
|
||||
// (the 'tf' variable points at 'curenv->env_tf').
|
||||
|
||||
// LAB 4: Your code here.
|
||||
if(!curenv->env_pgfault_upcall) {
|
||||
// Destroy the environment that caused the fault.
|
||||
cprintf("[%08x] user fault va %08x ip %08x\n",
|
||||
curenv->env_id, fault_va, tf->tf_eip);
|
||||
print_trapframe(tf);
|
||||
env_destroy(curenv);
|
||||
}
|
||||
user_mem_assert(curenv, curenv->env_pgfault_upcall, 1, PTE_U | PTE_P);
|
||||
user_mem_assert(curenv, (void*) UXSTACKTOP - 1, 1, PTE_U | PTE_P | PTE_W);
|
||||
|
||||
uintptr_t top_addr = UXSTACKTOP;
|
||||
if(tf->tf_esp <= UXSTACKTOP && tf->tf_esp >= (UXSTACKTOP - PGSIZE)) {
|
||||
top_addr = tf->tf_esp - 4;
|
||||
}
|
||||
|
||||
struct UTrapframe utf;
|
||||
utf.utf_eflags = tf->tf_eflags;
|
||||
utf.utf_eip = tf->tf_eip;
|
||||
utf.utf_err = tf->tf_err;
|
||||
utf.utf_esp = tf->tf_esp;
|
||||
utf.utf_fault_va = fault_va;
|
||||
utf.utf_regs = tf->tf_regs;
|
||||
|
||||
struct UTrapframe* push_to = (struct UTrapframe*) top_addr - 1;
|
||||
if((uintptr_t) push_to < USTACKTOP - PGSIZE) {
|
||||
cprintf("[%08x] stack overflow in page fault handler\n",
|
||||
curenv->env_id);
|
||||
env_destroy(curenv);
|
||||
}
|
||||
|
||||
*push_to = utf;
|
||||
curenv->env_tf.tf_eip = (uintptr_t) curenv->env_pgfault_upcall;
|
||||
curenv->env_tf.tf_esp = (uintptr_t) push_to;
|
||||
env_run(curenv);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <inc/memlayout.h>
|
||||
#include <inc/trap.h>
|
||||
|
||||
#include <kern/picirq.h>
|
||||
|
||||
|
||||
###################################################################
|
||||
@@ -81,6 +82,12 @@ TRAPHANDLER_NOEC(t_mchk, T_MCHK);
|
||||
TRAPHANDLER_NOEC(t_simderr, T_SIMDERR);
|
||||
TRAPHANDLER_NOEC(t_syscall, T_SYSCALL);
|
||||
TRAPHANDLER(t_default, T_DEFAULT);
|
||||
TRAPHANDLER_NOEC(irq_timer, IRQ_OFFSET + IRQ_TIMER);
|
||||
TRAPHANDLER_NOEC(irq_kbd, IRQ_OFFSET + IRQ_KBD);
|
||||
TRAPHANDLER_NOEC(irq_serial, IRQ_OFFSET + IRQ_SERIAL);
|
||||
TRAPHANDLER_NOEC(irq_spurious, IRQ_OFFSET + IRQ_SPURIOUS);
|
||||
TRAPHANDLER_NOEC(irq_ide, IRQ_OFFSET + IRQ_IDE);
|
||||
TRAPHANDLER_NOEC(irq_error, IRQ_OFFSET + IRQ_ERROR);
|
||||
|
||||
// HINT 1 : TRAPHANDLER_NOEC(t_divide, T_DIVIDE);
|
||||
// Do something like this if there is no error code for the trap
|
||||
|
||||
15
lib/Makefrag
15
lib/Makefrag
@@ -10,8 +10,23 @@ LIB_SRCFILES := lib/console.c \
|
||||
lib/string.c \
|
||||
lib/syscall.c
|
||||
|
||||
LIB_SRCFILES := $(LIB_SRCFILES) \
|
||||
lib/pgfault.c \
|
||||
lib/pfentry.S \
|
||||
lib/fork.c \
|
||||
lib/ipc.c
|
||||
|
||||
LIB_SRCFILES := $(LIB_SRCFILES) \
|
||||
lib/args.c \
|
||||
lib/fd.c \
|
||||
lib/file.c \
|
||||
lib/fprintf.c \
|
||||
lib/pageref.c \
|
||||
lib/spawn.c
|
||||
|
||||
LIB_SRCFILES := $(LIB_SRCFILES) \
|
||||
lib/pipe.c \
|
||||
lib/wait.c
|
||||
|
||||
LIB_OBJFILES := $(patsubst lib/%.c, $(OBJDIR)/lib/%.o, $(LIB_SRCFILES))
|
||||
LIB_OBJFILES := $(patsubst lib/%.S, $(OBJDIR)/lib/%.o, $(LIB_OBJFILES))
|
||||
|
||||
73
lib/args.c
Normal file
73
lib/args.c
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <inc/args.h>
|
||||
#include <inc/string.h>
|
||||
|
||||
void
|
||||
argstart(int *argc, char **argv, struct Argstate *args)
|
||||
{
|
||||
args->argc = argc;
|
||||
args->argv = (const char **) argv;
|
||||
args->curarg = (*argc > 1 && argv ? "" : 0);
|
||||
args->argvalue = 0;
|
||||
}
|
||||
|
||||
int
|
||||
argnext(struct Argstate *args)
|
||||
{
|
||||
int arg;
|
||||
|
||||
args->argvalue = 0;
|
||||
|
||||
// Done processing arguments if args->curarg == 0
|
||||
if (args->curarg == 0)
|
||||
return -1;
|
||||
|
||||
if (!*args->curarg) {
|
||||
// Need to process the next argument
|
||||
// Check for end of argument list
|
||||
if (*args->argc == 1
|
||||
|| args->argv[1][0] != '-'
|
||||
|| args->argv[1][1] == '\0')
|
||||
goto endofargs;
|
||||
// Shift arguments down one
|
||||
args->curarg = args->argv[1] + 1;
|
||||
memmove(args->argv + 1, args->argv + 2, sizeof(const char *) * (*args->argc - 1));
|
||||
(*args->argc)--;
|
||||
// Check for "--": end of argument list
|
||||
if (args->curarg[0] == '-' && args->curarg[1] == '\0')
|
||||
goto endofargs;
|
||||
}
|
||||
|
||||
arg = (unsigned char) *args->curarg;
|
||||
args->curarg++;
|
||||
return arg;
|
||||
|
||||
endofargs:
|
||||
args->curarg = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *
|
||||
argvalue(struct Argstate *args)
|
||||
{
|
||||
return (char*) (args->argvalue ? args->argvalue : argnextvalue(args));
|
||||
}
|
||||
|
||||
char *
|
||||
argnextvalue(struct Argstate *args)
|
||||
{
|
||||
if (!args->curarg)
|
||||
return 0;
|
||||
if (*args->curarg) {
|
||||
args->argvalue = args->curarg;
|
||||
args->curarg = "";
|
||||
} else if (*args->argc > 1) {
|
||||
args->argvalue = args->argv[1];
|
||||
memmove(args->argv + 1, args->argv + 2, sizeof(const char *) * (*args->argc - 1));
|
||||
(*args->argc)--;
|
||||
} else {
|
||||
args->argvalue = 0;
|
||||
args->curarg = 0;
|
||||
}
|
||||
return (char*) args->argvalue;
|
||||
}
|
||||
|
||||
109
lib/console.c
109
lib/console.c
@@ -15,11 +15,114 @@ cputchar(int ch)
|
||||
int
|
||||
getchar(void)
|
||||
{
|
||||
unsigned char c;
|
||||
int r;
|
||||
// sys_cgetc does not block, but getchar should.
|
||||
while ((r = sys_cgetc()) == 0)
|
||||
;
|
||||
|
||||
// JOS does, however, support standard _input_ redirection,
|
||||
// allowing the user to redirect script files to the shell and such.
|
||||
// getchar() reads a character from file descriptor 0.
|
||||
r = read(0, &c, 1);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r < 1)
|
||||
return -E_EOF;
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
// "Real" console file descriptor implementation.
|
||||
// The putchar/getchar functions above will still come here by default,
|
||||
// but now can be redirected to files, pipes, etc., via the fd layer.
|
||||
|
||||
static ssize_t devcons_read(struct Fd*, void*, size_t);
|
||||
static ssize_t devcons_write(struct Fd*, const void*, size_t);
|
||||
static int devcons_close(struct Fd*);
|
||||
static int devcons_stat(struct Fd*, struct Stat*);
|
||||
|
||||
struct Dev devcons =
|
||||
{
|
||||
.dev_id = 'c',
|
||||
.dev_name = "cons",
|
||||
.dev_read = devcons_read,
|
||||
.dev_write = devcons_write,
|
||||
.dev_close = devcons_close,
|
||||
.dev_stat = devcons_stat
|
||||
};
|
||||
|
||||
int
|
||||
iscons(int fdnum)
|
||||
{
|
||||
int r;
|
||||
struct Fd *fd;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0)
|
||||
return r;
|
||||
return fd->fd_dev_id == devcons.dev_id;
|
||||
}
|
||||
|
||||
int
|
||||
opencons(void)
|
||||
{
|
||||
int r;
|
||||
struct Fd* fd;
|
||||
|
||||
if ((r = fd_alloc(&fd)) < 0)
|
||||
return r;
|
||||
if ((r = sys_page_alloc(0, fd, PTE_P|PTE_U|PTE_W|PTE_SHARE)) < 0)
|
||||
return r;
|
||||
fd->fd_dev_id = devcons.dev_id;
|
||||
fd->fd_omode = O_RDWR;
|
||||
return fd2num(fd);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
devcons_read(struct Fd *fd, void *vbuf, size_t n)
|
||||
{
|
||||
int c;
|
||||
|
||||
if (n == 0)
|
||||
return 0;
|
||||
|
||||
while ((c = sys_cgetc()) == 0)
|
||||
sys_yield();
|
||||
if (c < 0)
|
||||
return c;
|
||||
if (c == 0x04) // ctl-d is eof
|
||||
return 0;
|
||||
*(char*)vbuf = c;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
devcons_write(struct Fd *fd, const void *vbuf, size_t n)
|
||||
{
|
||||
int tot, m;
|
||||
char buf[128];
|
||||
|
||||
// mistake: have to nul-terminate arg to sys_cputs,
|
||||
// so we have to copy vbuf into buf in chunks and nul-terminate.
|
||||
for (tot = 0; tot < n; tot += m) {
|
||||
m = n - tot;
|
||||
if (m > sizeof(buf) - 1)
|
||||
m = sizeof(buf) - 1;
|
||||
memmove(buf, (char*)vbuf + tot, m);
|
||||
sys_cputs(buf, m);
|
||||
}
|
||||
return tot;
|
||||
}
|
||||
|
||||
static int
|
||||
devcons_close(struct Fd *fd)
|
||||
{
|
||||
USED(fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
devcons_stat(struct Fd *fd, struct Stat *stat)
|
||||
{
|
||||
strcpy(stat->st_name, "<cons>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
void
|
||||
exit(void)
|
||||
{
|
||||
close_all();
|
||||
sys_env_destroy(0);
|
||||
}
|
||||
|
||||
|
||||
320
lib/fd.c
Normal file
320
lib/fd.c
Normal file
@@ -0,0 +1,320 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
#define debug 0
|
||||
|
||||
// Maximum number of file descriptors a program may hold open concurrently
|
||||
#define MAXFD 32
|
||||
// Bottom of file descriptor area
|
||||
#define FDTABLE 0xD0000000
|
||||
// Bottom of file data area. We reserve one data page for each FD,
|
||||
// which devices can use if they choose.
|
||||
#define FILEDATA (FDTABLE + MAXFD*PGSIZE)
|
||||
|
||||
// Return the 'struct Fd*' for file descriptor index i
|
||||
#define INDEX2FD(i) ((struct Fd*) (FDTABLE + (i)*PGSIZE))
|
||||
// Return the file data page for file descriptor index i
|
||||
#define INDEX2DATA(i) ((char*) (FILEDATA + (i)*PGSIZE))
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// File descriptor manipulators
|
||||
// --------------------------------------------------------------
|
||||
|
||||
int
|
||||
fd2num(struct Fd *fd)
|
||||
{
|
||||
return ((uintptr_t) fd - FDTABLE) / PGSIZE;
|
||||
}
|
||||
|
||||
char*
|
||||
fd2data(struct Fd *fd)
|
||||
{
|
||||
return INDEX2DATA(fd2num(fd));
|
||||
}
|
||||
|
||||
// Finds the smallest i from 0 to MAXFD-1 that doesn't have
|
||||
// its fd page mapped.
|
||||
// Sets *fd_store to the corresponding fd page virtual address.
|
||||
//
|
||||
// fd_alloc does NOT actually allocate an fd page.
|
||||
// It is up to the caller to allocate the page somehow.
|
||||
// This means that if someone calls fd_alloc twice in a row
|
||||
// without allocating the first page we return, we'll return the same
|
||||
// page the second time.
|
||||
//
|
||||
// Hint: Use INDEX2FD.
|
||||
//
|
||||
// Returns 0 on success, < 0 on error. Errors are:
|
||||
// -E_MAX_FD: no more file descriptors
|
||||
// On error, *fd_store is set to 0.
|
||||
int
|
||||
fd_alloc(struct Fd **fd_store)
|
||||
{
|
||||
int i;
|
||||
struct Fd *fd;
|
||||
|
||||
for (i = 0; i < MAXFD; i++) {
|
||||
fd = INDEX2FD(i);
|
||||
if ((uvpd[PDX(fd)] & PTE_P) == 0 || (uvpt[PGNUM(fd)] & PTE_P) == 0) {
|
||||
*fd_store = fd;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*fd_store = 0;
|
||||
return -E_MAX_OPEN;
|
||||
}
|
||||
|
||||
// Check that fdnum is in range and mapped.
|
||||
// If it is, set *fd_store to the fd page virtual address.
|
||||
//
|
||||
// Returns 0 on success (the page is in range and mapped), < 0 on error.
|
||||
// Errors are:
|
||||
// -E_INVAL: fdnum was either not in range or not mapped.
|
||||
int
|
||||
fd_lookup(int fdnum, struct Fd **fd_store)
|
||||
{
|
||||
struct Fd *fd;
|
||||
|
||||
if (fdnum < 0 || fdnum >= MAXFD) {
|
||||
if (debug)
|
||||
cprintf("[%08x] bad fd %d\n", thisenv->env_id, fdnum);
|
||||
return -E_INVAL;
|
||||
}
|
||||
fd = INDEX2FD(fdnum);
|
||||
if (!(uvpd[PDX(fd)] & PTE_P) || !(uvpt[PGNUM(fd)] & PTE_P)) {
|
||||
if (debug)
|
||||
cprintf("[%08x] closed fd %d\n", thisenv->env_id, fdnum);
|
||||
return -E_INVAL;
|
||||
}
|
||||
*fd_store = fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Frees file descriptor 'fd' by closing the corresponding file
|
||||
// and unmapping the file descriptor page.
|
||||
// If 'must_exist' is 0, then fd can be a closed or nonexistent file
|
||||
// descriptor; the function will return 0 and have no other effect.
|
||||
// If 'must_exist' is 1, then fd_close returns -E_INVAL when passed a
|
||||
// closed or nonexistent file descriptor.
|
||||
// Returns 0 on success, < 0 on error.
|
||||
int
|
||||
fd_close(struct Fd *fd, bool must_exist)
|
||||
{
|
||||
struct Fd *fd2;
|
||||
struct Dev *dev;
|
||||
int r;
|
||||
if ((r = fd_lookup(fd2num(fd), &fd2)) < 0
|
||||
|| fd != fd2)
|
||||
return (must_exist ? r : 0);
|
||||
if ((r = dev_lookup(fd->fd_dev_id, &dev)) >= 0) {
|
||||
if (dev->dev_close)
|
||||
r = (*dev->dev_close)(fd);
|
||||
else
|
||||
r = 0;
|
||||
}
|
||||
// Make sure fd is unmapped. Might be a no-op if
|
||||
// (*dev->dev_close)(fd) already unmapped it.
|
||||
(void) sys_page_unmap(0, fd);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// File functions
|
||||
// --------------------------------------------------------------
|
||||
|
||||
static struct Dev *devtab[] =
|
||||
{
|
||||
&devfile,
|
||||
&devpipe,
|
||||
&devcons,
|
||||
0
|
||||
};
|
||||
|
||||
int
|
||||
dev_lookup(int dev_id, struct Dev **dev)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; devtab[i]; i++)
|
||||
if (devtab[i]->dev_id == dev_id) {
|
||||
*dev = devtab[i];
|
||||
return 0;
|
||||
}
|
||||
cprintf("[%08x] unknown device type %d\n", thisenv->env_id, dev_id);
|
||||
*dev = 0;
|
||||
return -E_INVAL;
|
||||
}
|
||||
|
||||
int
|
||||
close(int fdnum)
|
||||
{
|
||||
struct Fd *fd;
|
||||
int r;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0)
|
||||
return r;
|
||||
else
|
||||
return fd_close(fd, 1);
|
||||
}
|
||||
|
||||
void
|
||||
close_all(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < MAXFD; i++)
|
||||
close(i);
|
||||
}
|
||||
|
||||
// Make file descriptor 'newfdnum' a duplicate of file descriptor 'oldfdnum'.
|
||||
// For instance, writing onto either file descriptor will affect the
|
||||
// file and the file offset of the other.
|
||||
// Closes any previously open file descriptor at 'newfdnum'.
|
||||
// This is implemented using virtual memory tricks (of course!).
|
||||
int
|
||||
dup(int oldfdnum, int newfdnum)
|
||||
{
|
||||
int r;
|
||||
char *ova, *nva;
|
||||
pte_t pte;
|
||||
struct Fd *oldfd, *newfd;
|
||||
|
||||
if ((r = fd_lookup(oldfdnum, &oldfd)) < 0)
|
||||
return r;
|
||||
close(newfdnum);
|
||||
|
||||
newfd = INDEX2FD(newfdnum);
|
||||
ova = fd2data(oldfd);
|
||||
nva = fd2data(newfd);
|
||||
|
||||
if ((uvpd[PDX(ova)] & PTE_P) && (uvpt[PGNUM(ova)] & PTE_P))
|
||||
if ((r = sys_page_map(0, ova, 0, nva, uvpt[PGNUM(ova)] & PTE_SYSCALL)) < 0)
|
||||
goto err;
|
||||
if ((r = sys_page_map(0, oldfd, 0, newfd, uvpt[PGNUM(oldfd)] & PTE_SYSCALL)) < 0)
|
||||
goto err;
|
||||
|
||||
return newfdnum;
|
||||
|
||||
err:
|
||||
sys_page_unmap(0, newfd);
|
||||
sys_page_unmap(0, nva);
|
||||
return r;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
read(int fdnum, void *buf, size_t n)
|
||||
{
|
||||
int r;
|
||||
struct Dev *dev;
|
||||
struct Fd *fd;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0
|
||||
|| (r = dev_lookup(fd->fd_dev_id, &dev)) < 0)
|
||||
return r;
|
||||
if ((fd->fd_omode & O_ACCMODE) == O_WRONLY) {
|
||||
cprintf("[%08x] read %d -- bad mode\n", thisenv->env_id, fdnum);
|
||||
return -E_INVAL;
|
||||
}
|
||||
if (!dev->dev_read)
|
||||
return -E_NOT_SUPP;
|
||||
return (*dev->dev_read)(fd, buf, n);
|
||||
}
|
||||
|
||||
ssize_t
|
||||
readn(int fdnum, void *buf, size_t n)
|
||||
{
|
||||
int m, tot;
|
||||
|
||||
for (tot = 0; tot < n; tot += m) {
|
||||
m = read(fdnum, (char*)buf + tot, n - tot);
|
||||
if (m < 0)
|
||||
return m;
|
||||
if (m == 0)
|
||||
break;
|
||||
}
|
||||
return tot;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
write(int fdnum, const void *buf, size_t n)
|
||||
{
|
||||
int r;
|
||||
struct Dev *dev;
|
||||
struct Fd *fd;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0
|
||||
|| (r = dev_lookup(fd->fd_dev_id, &dev)) < 0)
|
||||
return r;
|
||||
if ((fd->fd_omode & O_ACCMODE) == O_RDONLY) {
|
||||
cprintf("[%08x] write %d -- bad mode\n", thisenv->env_id, fdnum);
|
||||
return -E_INVAL;
|
||||
}
|
||||
if (debug)
|
||||
cprintf("write %d %p %d via dev %s\n",
|
||||
fdnum, buf, n, dev->dev_name);
|
||||
if (!dev->dev_write)
|
||||
return -E_NOT_SUPP;
|
||||
return (*dev->dev_write)(fd, buf, n);
|
||||
}
|
||||
|
||||
int
|
||||
seek(int fdnum, off_t offset)
|
||||
{
|
||||
int r;
|
||||
struct Fd *fd;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0)
|
||||
return r;
|
||||
fd->fd_offset = offset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ftruncate(int fdnum, off_t newsize)
|
||||
{
|
||||
int r;
|
||||
struct Dev *dev;
|
||||
struct Fd *fd;
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0
|
||||
|| (r = dev_lookup(fd->fd_dev_id, &dev)) < 0)
|
||||
return r;
|
||||
if ((fd->fd_omode & O_ACCMODE) == O_RDONLY) {
|
||||
cprintf("[%08x] ftruncate %d -- bad mode\n",
|
||||
thisenv->env_id, fdnum);
|
||||
return -E_INVAL;
|
||||
}
|
||||
if (!dev->dev_trunc)
|
||||
return -E_NOT_SUPP;
|
||||
return (*dev->dev_trunc)(fd, newsize);
|
||||
}
|
||||
|
||||
int
|
||||
fstat(int fdnum, struct Stat *stat)
|
||||
{
|
||||
int r;
|
||||
struct Dev *dev;
|
||||
struct Fd *fd;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0
|
||||
|| (r = dev_lookup(fd->fd_dev_id, &dev)) < 0)
|
||||
return r;
|
||||
if (!dev->dev_stat)
|
||||
return -E_NOT_SUPP;
|
||||
stat->st_name[0] = 0;
|
||||
stat->st_size = 0;
|
||||
stat->st_isdir = 0;
|
||||
stat->st_dev = dev;
|
||||
return (*dev->dev_stat)(fd, stat);
|
||||
}
|
||||
|
||||
int
|
||||
stat(const char *path, struct Stat *stat)
|
||||
{
|
||||
int fd, r;
|
||||
|
||||
if ((fd = open(path, O_RDONLY)) < 0)
|
||||
return fd;
|
||||
r = fstat(fd, stat);
|
||||
close(fd);
|
||||
return r;
|
||||
}
|
||||
|
||||
186
lib/file.c
Normal file
186
lib/file.c
Normal file
@@ -0,0 +1,186 @@
|
||||
#include <inc/fs.h>
|
||||
#include <inc/string.h>
|
||||
#include <inc/lib.h>
|
||||
|
||||
#define debug 0
|
||||
|
||||
union Fsipc fsipcbuf __attribute__((aligned(PGSIZE)));
|
||||
|
||||
// Send an inter-environment request to the file server, and wait for
|
||||
// a reply. The request body should be in fsipcbuf, and parts of the
|
||||
// response may be written back to fsipcbuf.
|
||||
// type: request code, passed as the simple integer IPC value.
|
||||
// dstva: virtual address at which to receive reply page, 0 if none.
|
||||
// Returns result from the file server.
|
||||
static int
|
||||
fsipc(unsigned type, void *dstva)
|
||||
{
|
||||
static envid_t fsenv;
|
||||
if (fsenv == 0)
|
||||
fsenv = ipc_find_env(ENV_TYPE_FS);
|
||||
|
||||
static_assert(sizeof(fsipcbuf) == PGSIZE);
|
||||
|
||||
if (debug)
|
||||
cprintf("[%08x] fsipc %d %08x\n", thisenv->env_id, type, *(uint32_t *)&fsipcbuf);
|
||||
|
||||
ipc_send(fsenv, type, &fsipcbuf, PTE_P | PTE_W | PTE_U);
|
||||
return ipc_recv(NULL, dstva, NULL);
|
||||
}
|
||||
|
||||
static int devfile_flush(struct Fd *fd);
|
||||
static ssize_t devfile_read(struct Fd *fd, void *buf, size_t n);
|
||||
static ssize_t devfile_write(struct Fd *fd, const void *buf, size_t n);
|
||||
static int devfile_stat(struct Fd *fd, struct Stat *stat);
|
||||
static int devfile_trunc(struct Fd *fd, off_t newsize);
|
||||
|
||||
struct Dev devfile =
|
||||
{
|
||||
.dev_id = 'f',
|
||||
.dev_name = "file",
|
||||
.dev_read = devfile_read,
|
||||
.dev_close = devfile_flush,
|
||||
.dev_stat = devfile_stat,
|
||||
.dev_write = devfile_write,
|
||||
.dev_trunc = devfile_trunc
|
||||
};
|
||||
|
||||
// Open a file (or directory).
|
||||
//
|
||||
// Returns:
|
||||
// The file descriptor index on success
|
||||
// -E_BAD_PATH if the path is too long (>= MAXPATHLEN)
|
||||
// < 0 for other errors.
|
||||
int
|
||||
open(const char *path, int mode)
|
||||
{
|
||||
// Find an unused file descriptor page using fd_alloc.
|
||||
// Then send a file-open request to the file server.
|
||||
// Include 'path' and 'omode' in request,
|
||||
// and map the returned file descriptor page
|
||||
// at the appropriate fd address.
|
||||
// FSREQ_OPEN returns 0 on success, < 0 on failure.
|
||||
//
|
||||
// (fd_alloc does not allocate a page, it just returns an
|
||||
// unused fd address. Do you need to allocate a page?)
|
||||
//
|
||||
// Return the file descriptor index.
|
||||
// If any step after fd_alloc fails, use fd_close to free the
|
||||
// file descriptor.
|
||||
|
||||
int r;
|
||||
struct Fd *fd;
|
||||
|
||||
if (strlen(path) >= MAXPATHLEN)
|
||||
return -E_BAD_PATH;
|
||||
|
||||
if ((r = fd_alloc(&fd)) < 0)
|
||||
return r;
|
||||
|
||||
strcpy(fsipcbuf.open.req_path, path);
|
||||
fsipcbuf.open.req_omode = mode;
|
||||
|
||||
if ((r = fsipc(FSREQ_OPEN, fd)) < 0) {
|
||||
fd_close(fd, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
return fd2num(fd);
|
||||
}
|
||||
|
||||
// Flush the file descriptor. After this the fileid is invalid.
|
||||
//
|
||||
// This function is called by fd_close. fd_close will take care of
|
||||
// unmapping the FD page from this environment. Since the server uses
|
||||
// the reference counts on the FD pages to detect which files are
|
||||
// open, unmapping it is enough to free up server-side resources.
|
||||
// Other than that, we just have to make sure our changes are flushed
|
||||
// to disk.
|
||||
static int
|
||||
devfile_flush(struct Fd *fd)
|
||||
{
|
||||
fsipcbuf.flush.req_fileid = fd->fd_file.id;
|
||||
return fsipc(FSREQ_FLUSH, NULL);
|
||||
}
|
||||
|
||||
// Read at most 'n' bytes from 'fd' at the current position into 'buf'.
|
||||
//
|
||||
// Returns:
|
||||
// The number of bytes successfully read.
|
||||
// < 0 on error.
|
||||
static ssize_t
|
||||
devfile_read(struct Fd *fd, void *buf, size_t n)
|
||||
{
|
||||
// Make an FSREQ_READ request to the file system server after
|
||||
// filling fsipcbuf.read with the request arguments. The
|
||||
// bytes read will be written back to fsipcbuf by the file
|
||||
// system server.
|
||||
int r;
|
||||
|
||||
fsipcbuf.read.req_fileid = fd->fd_file.id;
|
||||
fsipcbuf.read.req_n = n;
|
||||
if ((r = fsipc(FSREQ_READ, NULL)) < 0)
|
||||
return r;
|
||||
assert(r <= n);
|
||||
assert(r <= PGSIZE);
|
||||
memmove(buf, fsipcbuf.readRet.ret_buf, r);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
// Write at most 'n' bytes from 'buf' to 'fd' at the current seek position.
|
||||
//
|
||||
// Returns:
|
||||
// The number of bytes successfully written.
|
||||
// < 0 on error.
|
||||
static ssize_t
|
||||
devfile_write(struct Fd *fd, const void *buf, size_t n)
|
||||
{
|
||||
// Make an FSREQ_WRITE request to the file system server. Be
|
||||
// careful: fsipcbuf.write.req_buf is only so large, but
|
||||
// remember that write is always allowed to write *fewer*
|
||||
// bytes than requested.
|
||||
// LAB 5: Your code here
|
||||
size_t writesize = MIN(sizeof(fsipcbuf.write.req_buf), n);
|
||||
|
||||
fsipcbuf.write.req_fileid = fd->fd_file.id;
|
||||
fsipcbuf.write.req_n = writesize;
|
||||
memcpy(fsipcbuf.write.req_buf, buf, writesize);
|
||||
|
||||
return fsipc(FSREQ_WRITE, NULL);
|
||||
}
|
||||
|
||||
static int
|
||||
devfile_stat(struct Fd *fd, struct Stat *st)
|
||||
{
|
||||
int r;
|
||||
|
||||
fsipcbuf.stat.req_fileid = fd->fd_file.id;
|
||||
if ((r = fsipc(FSREQ_STAT, NULL)) < 0)
|
||||
return r;
|
||||
strcpy(st->st_name, fsipcbuf.statRet.ret_name);
|
||||
st->st_size = fsipcbuf.statRet.ret_size;
|
||||
st->st_isdir = fsipcbuf.statRet.ret_isdir;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Truncate or extend an open file to 'size' bytes
|
||||
static int
|
||||
devfile_trunc(struct Fd *fd, off_t newsize)
|
||||
{
|
||||
fsipcbuf.set_size.req_fileid = fd->fd_file.id;
|
||||
fsipcbuf.set_size.req_size = newsize;
|
||||
return fsipc(FSREQ_SET_SIZE, NULL);
|
||||
}
|
||||
|
||||
|
||||
// Synchronize disk with buffer cache
|
||||
int
|
||||
sync(void)
|
||||
{
|
||||
// Ask the file server to update the disk
|
||||
// by writing any dirty blocks in the buffer cache.
|
||||
|
||||
return fsipc(FSREQ_SYNC, NULL);
|
||||
}
|
||||
|
||||
147
lib/fork.c
Normal file
147
lib/fork.c
Normal file
@@ -0,0 +1,147 @@
|
||||
// implement fork from user space
|
||||
|
||||
#include <inc/string.h>
|
||||
#include <inc/lib.h>
|
||||
|
||||
// PTE_COW marks copy-on-write page table entries.
|
||||
// It is one of the bits explicitly allocated to user processes (PTE_AVAIL).
|
||||
#define PTE_COW 0x800
|
||||
|
||||
//
|
||||
// Custom page fault handler - if faulting page is copy-on-write,
|
||||
// map in our own private writable copy.
|
||||
//
|
||||
static void
|
||||
pgfault(struct UTrapframe *utf)
|
||||
{
|
||||
void *addr = (void *) utf->utf_fault_va;
|
||||
uint32_t err = utf->utf_err;
|
||||
int r;
|
||||
|
||||
// Check that the faulting access was (1) a write, and (2) to a
|
||||
// copy-on-write page. If not, panic.
|
||||
// Hint:
|
||||
// Use the read-only page table mappings at uvpt
|
||||
// (see <inc/memlayout.h>).
|
||||
if(!((err & FEC_WR) && (uvpt[(uintptr_t) addr >> PGSHIFT] & PTE_COW)))
|
||||
panic("page fault (addr %p)! %c", addr, (err & FEC_WR) ? 'w' : 'r');
|
||||
|
||||
// Allocate a new page, map it at a temporary location (PFTEMP),
|
||||
// copy the data from the old page to the new page, then move the new
|
||||
// page to the old page's address.
|
||||
// Hint:
|
||||
// You should make three system calls.
|
||||
void* temp_addr = (void*) PFTEMP;
|
||||
void* fault_addr = ROUNDDOWN(addr, PGSIZE);
|
||||
if(sys_page_alloc(0, temp_addr, PTE_P | PTE_W | PTE_U) < 0)
|
||||
panic("failed to allocate new page");
|
||||
|
||||
memcpy(temp_addr, fault_addr, PGSIZE);
|
||||
sys_page_map(0, temp_addr, 0, fault_addr, PTE_P | PTE_U | PTE_W);
|
||||
sys_page_unmap(0, temp_addr);
|
||||
}
|
||||
|
||||
//
|
||||
// Map our virtual page pn (address pn*PGSIZE) into the target envid
|
||||
// at the same virtual address. If the page is writable or copy-on-write,
|
||||
// the new mapping must be created copy-on-write, and then our mapping must be
|
||||
// marked copy-on-write as well. (Exercise: Why do we need to mark ours
|
||||
// copy-on-write again if it was already copy-on-write at the beginning of
|
||||
// this function?)
|
||||
//
|
||||
// Returns: 0 on success, < 0 on error.
|
||||
// It is also OK to panic on error.
|
||||
//
|
||||
static int
|
||||
duppage(envid_t envid, unsigned pn)
|
||||
{
|
||||
int r;
|
||||
bool change_own = false;
|
||||
pte_t new_pte = uvpt[pn];
|
||||
pte_t perms = new_pte & (PTE_P | PTE_U | PTE_W | PTE_AVAIL);
|
||||
void* addr = (void*) (pn * PGSIZE);
|
||||
|
||||
// If we're writable, remove write permission
|
||||
if(((new_pte & PTE_W) && !(new_pte & PTE_SHARE)) || (new_pte & PTE_COW)) {
|
||||
perms = (perms & ~PTE_W) | PTE_COW;
|
||||
change_own = true;
|
||||
}
|
||||
|
||||
// Map either with the same permissions or with COW.
|
||||
if((r = sys_page_map(0, addr, envid, addr, perms)) < 0)
|
||||
return r;
|
||||
|
||||
// Update our own permissions if necessary
|
||||
if(change_own) {
|
||||
if((r = sys_page_map(0, addr, 0, addr, perms)) < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// User-level fork with copy-on-write.
|
||||
// Set up our page fault handler appropriately.
|
||||
// Create a child.
|
||||
// Copy our address space and page fault handler setup to the child.
|
||||
// Then mark the child as runnable and return.
|
||||
//
|
||||
// Returns: child's envid to the parent, 0 to the child, < 0 on error.
|
||||
// It is also OK to panic on error.
|
||||
//
|
||||
// Hint:
|
||||
// Use uvpd, uvpt, and duppage.
|
||||
// Remember to fix "thisenv" in the child process.
|
||||
// Neither user exception stack should ever be marked copy-on-write,
|
||||
// so you must allocate a new page for the child's user exception stack.
|
||||
//
|
||||
envid_t
|
||||
fork(void)
|
||||
{
|
||||
set_pgfault_handler(pgfault);
|
||||
|
||||
int return_code;
|
||||
envid_t forked;
|
||||
|
||||
forked = sys_exofork();
|
||||
if(forked < 0) return forked;
|
||||
if(forked == 0) { thisenv = &envs[ENVX(sys_getenvid())]; return 0; }
|
||||
|
||||
// Map all accessible page directory entries
|
||||
for(int pde_i = 0; pde_i < PDX(UTOP); pde_i++) {
|
||||
pde_t pde = uvpd[pde_i];
|
||||
if(!(pde & PTE_P)) continue;
|
||||
|
||||
// For each PDE, map all the underlying PTEs
|
||||
for(int pte_i = 0; pte_i < NPTENTRIES; pte_i++) {
|
||||
int pn = pde_i * NPTENTRIES + pte_i;
|
||||
pte_t pte = uvpt[pn];
|
||||
if(!(pte & PTE_P)) continue;
|
||||
|
||||
// Do not map user exception stack, though
|
||||
if(pn == ((UXSTACKTOP - PGSIZE) >> PGSHIFT)) continue;
|
||||
|
||||
if((return_code = duppage(forked, pn)) < 0) return return_code;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate new page for the exception stack
|
||||
return_code = sys_page_alloc(forked, (void*) UXSTACKTOP - PGSIZE,
|
||||
PTE_P | PTE_U | PTE_W);
|
||||
if(return_code < 0) return return_code;
|
||||
|
||||
// Set the upcall entry point
|
||||
sys_env_set_pgfault_upcall(forked, thisenv->env_pgfault_upcall);
|
||||
sys_env_set_status(forked, ENV_RUNNABLE);
|
||||
|
||||
return forked;
|
||||
}
|
||||
|
||||
// Challenge!
|
||||
int
|
||||
sfork(void)
|
||||
{
|
||||
panic("sfork not implemented");
|
||||
return -E_INVAL;
|
||||
}
|
||||
81
lib/fprintf.c
Normal file
81
lib/fprintf.c
Normal file
@@ -0,0 +1,81 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
// Collect up to 256 characters into a buffer
|
||||
// and perform ONE system call to print all of them,
|
||||
// in order to make the lines output to the console atomic
|
||||
// and prevent interrupts from causing context switches
|
||||
// in the middle of a console output line and such.
|
||||
struct printbuf {
|
||||
int fd; // file descriptor
|
||||
int idx; // current buffer index
|
||||
ssize_t result; // accumulated results from write
|
||||
int error; // first error that occurred
|
||||
char buf[256];
|
||||
};
|
||||
|
||||
|
||||
static void
|
||||
writebuf(struct printbuf *b)
|
||||
{
|
||||
if (b->error > 0) {
|
||||
ssize_t result = write(b->fd, b->buf, b->idx);
|
||||
if (result > 0)
|
||||
b->result += result;
|
||||
if (result != b->idx) // error, or wrote less than supplied
|
||||
b->error = (result < 0 ? result : 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
putch(int ch, void *thunk)
|
||||
{
|
||||
struct printbuf *b = (struct printbuf *) thunk;
|
||||
b->buf[b->idx++] = ch;
|
||||
if (b->idx == 256) {
|
||||
writebuf(b);
|
||||
b->idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
vfprintf(int fd, const char *fmt, va_list ap)
|
||||
{
|
||||
struct printbuf b;
|
||||
|
||||
b.fd = fd;
|
||||
b.idx = 0;
|
||||
b.result = 0;
|
||||
b.error = 1;
|
||||
vprintfmt(putch, &b, fmt, ap);
|
||||
if (b.idx > 0)
|
||||
writebuf(&b);
|
||||
|
||||
return (b.result ? b.result : b.error);
|
||||
}
|
||||
|
||||
int
|
||||
fprintf(int fd, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int cnt;
|
||||
|
||||
va_start(ap, fmt);
|
||||
cnt = vfprintf(fd, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
int
|
||||
printf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
int cnt;
|
||||
|
||||
va_start(ap, fmt);
|
||||
cnt = vfprintf(1, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return cnt;
|
||||
}
|
||||
|
||||
65
lib/ipc.c
Normal file
65
lib/ipc.c
Normal file
@@ -0,0 +1,65 @@
|
||||
// User-level IPC library routines
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
// Receive a value via IPC and return it.
|
||||
// If 'pg' is nonnull, then any page sent by the sender will be mapped at
|
||||
// that address.
|
||||
// If 'from_env_store' is nonnull, then store the IPC sender's envid in
|
||||
// *from_env_store.
|
||||
// If 'perm_store' is nonnull, then store the IPC sender's page permission
|
||||
// in *perm_store (this is nonzero iff a page was successfully
|
||||
// transferred to 'pg').
|
||||
// If the system call fails, then store 0 in *fromenv and *perm (if
|
||||
// they're nonnull) and return the error.
|
||||
// Otherwise, return the value sent by the sender
|
||||
//
|
||||
// Hint:
|
||||
// Use 'thisenv' to discover the value and who sent it.
|
||||
// If 'pg' is null, pass sys_ipc_recv a value that it will understand
|
||||
// as meaning "no page". (Zero is not the right value, since that's
|
||||
// a perfectly valid place to map a page.)
|
||||
int32_t
|
||||
ipc_recv(envid_t *from_env_store, void *pg, int *perm_store)
|
||||
{
|
||||
int return_code;
|
||||
if((return_code = sys_ipc_recv(pg ? pg : (void*) ~0)) < 0)
|
||||
return return_code;
|
||||
|
||||
if(from_env_store) *from_env_store = thisenv->env_ipc_from;
|
||||
if(perm_store) *perm_store = thisenv->env_ipc_perm;
|
||||
|
||||
return thisenv->env_ipc_value;
|
||||
}
|
||||
|
||||
// Send 'val' (and 'pg' with 'perm', if 'pg' is nonnull) to 'toenv'.
|
||||
// This function keeps trying until it succeeds.
|
||||
// It should panic() on any error other than -E_IPC_NOT_RECV.
|
||||
//
|
||||
// Hint:
|
||||
// Use sys_yield() to be CPU-friendly.
|
||||
// If 'pg' is null, pass sys_ipc_try_send a value that it will understand
|
||||
// as meaning "no page". (Zero is not the right value.)
|
||||
void
|
||||
ipc_send(envid_t to_env, uint32_t val, void *pg, int perm)
|
||||
{
|
||||
int return_code = -E_IPC_NOT_RECV;
|
||||
while(return_code == -E_IPC_NOT_RECV) {
|
||||
return_code = sys_ipc_try_send(to_env, val, pg ? pg : (void*) ~0, perm);
|
||||
sys_yield();
|
||||
}
|
||||
if(return_code != 0) panic("failed to send\n");
|
||||
}
|
||||
|
||||
// Find the first environment of the given type. We'll use this to
|
||||
// find special environments.
|
||||
// Returns 0 if no such environment exists.
|
||||
envid_t
|
||||
ipc_find_env(enum EnvType type)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < NENV; i++)
|
||||
if (envs[i].env_type == type)
|
||||
return envs[i].env_id;
|
||||
return 0;
|
||||
}
|
||||
14
lib/pageref.c
Normal file
14
lib/pageref.c
Normal file
@@ -0,0 +1,14 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
int
|
||||
pageref(void *v)
|
||||
{
|
||||
pte_t pte;
|
||||
|
||||
if (!(uvpd[PDX(v)] & PTE_P))
|
||||
return 0;
|
||||
pte = uvpt[PGNUM(v)];
|
||||
if (!(pte & PTE_P))
|
||||
return 0;
|
||||
return pages[PGNUM(pte)].pp_ref;
|
||||
}
|
||||
93
lib/pfentry.S
Normal file
93
lib/pfentry.S
Normal file
@@ -0,0 +1,93 @@
|
||||
#include <inc/mmu.h>
|
||||
#include <inc/memlayout.h>
|
||||
|
||||
// Page fault upcall entrypoint.
|
||||
|
||||
// This is where we ask the kernel to redirect us to whenever we cause
|
||||
// a page fault in user space (see the call to sys_set_pgfault_handler
|
||||
// in pgfault.c).
|
||||
//
|
||||
// When a page fault actually occurs, the kernel switches our ESP to
|
||||
// point to the user exception stack if we're not already on the user
|
||||
// exception stack, and then it pushes a UTrapframe onto our user
|
||||
// exception stack:
|
||||
//
|
||||
// trap-time esp
|
||||
// trap-time eflags
|
||||
// trap-time eip
|
||||
// utf_regs.reg_eax
|
||||
// ...
|
||||
// utf_regs.reg_esi
|
||||
// utf_regs.reg_edi
|
||||
// utf_err (error code)
|
||||
// utf_fault_va <-- %esp
|
||||
//
|
||||
// If this is a recursive fault, the kernel will reserve for us a
|
||||
// blank word above the trap-time esp for scratch work when we unwind
|
||||
// the recursive call.
|
||||
//
|
||||
// We then have call up to the appropriate page fault handler in C
|
||||
// code, pointed to by the global variable '_pgfault_handler'.
|
||||
|
||||
.text
|
||||
.globl _pgfault_upcall
|
||||
_pgfault_upcall:
|
||||
// Call the C page fault handler.
|
||||
pushl %esp // function argument: pointer to UTF
|
||||
movl _pgfault_handler, %eax
|
||||
call *%eax
|
||||
addl $4, %esp // pop function argument
|
||||
|
||||
// Now the C page fault handler has returned and you must return
|
||||
// to the trap time state.
|
||||
// Push trap-time %eip onto the trap-time stack.
|
||||
//
|
||||
// Explanation:
|
||||
// We must prepare the trap-time stack for our eventual return to
|
||||
// re-execute the instruction that faulted.
|
||||
// Unfortunately, we can't return directly from the exception stack:
|
||||
// We can't call 'jmp', since that requires that we load the address
|
||||
// into a register, and all registers must have their trap-time
|
||||
// values after the return.
|
||||
// We can't call 'ret' from the exception stack either, since if we
|
||||
// did, %esp would have the wrong value.
|
||||
// So instead, we push the trap-time %eip onto the *trap-time* stack!
|
||||
// Below we'll switch to that stack and call 'ret', which will
|
||||
// restore %eip to its pre-fault value.
|
||||
//
|
||||
// In the case of a recursive fault on the exception stack,
|
||||
// note that the word we're pushing now will fit in the
|
||||
// blank word that the kernel reserved for us.
|
||||
//
|
||||
// Throughout the remaining code, think carefully about what
|
||||
// registers are available for intermediate calculations. You
|
||||
// may find that you have to rearrange your code in non-obvious
|
||||
// ways as registers become unavailable as scratch space.
|
||||
//
|
||||
// LAB 4: Your code here.
|
||||
mov 40(%esp), %eax // Take the EIP from memory
|
||||
mov 48(%esp), %ebp // Take the ESP from memory
|
||||
sub $4, %ebp // Push onto trap-time ESP
|
||||
mov %eax, (%ebp)
|
||||
mov %ebp, 48(%esp) // Put ESP back
|
||||
|
||||
// Restore the trap-time registers. After you do this, you
|
||||
// can no longer modify any general-purpose registers.
|
||||
// LAB 4: Your code here.
|
||||
add $0x8, %esp
|
||||
popal
|
||||
|
||||
// Restore eflags from the stack. After you do this, you can
|
||||
// no longer use arithmetic operations or anything else that
|
||||
// modifies eflags.
|
||||
// LAB 4: Your code here.
|
||||
add $0x4, %esp
|
||||
popfl
|
||||
|
||||
// Switch back to the adjusted trap-time stack.
|
||||
// LAB 4: Your code here.
|
||||
pop %esp
|
||||
|
||||
// Return to re-execute the instruction that faulted.
|
||||
// LAB 4: Your code here.
|
||||
ret
|
||||
37
lib/pgfault.c
Normal file
37
lib/pgfault.c
Normal file
@@ -0,0 +1,37 @@
|
||||
// User-level page fault handler support.
|
||||
// Rather than register the C page fault handler directly with the
|
||||
// kernel as the page fault handler, we register the assembly language
|
||||
// wrapper in pfentry.S, which in turns calls the registered C
|
||||
// function.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
|
||||
// Assembly language pgfault entrypoint defined in lib/pfentry.S.
|
||||
extern void _pgfault_upcall(void);
|
||||
|
||||
// Pointer to currently installed C-language pgfault handler.
|
||||
void (*_pgfault_handler)(struct UTrapframe *utf);
|
||||
|
||||
//
|
||||
// Set the page fault handler function.
|
||||
// If there isn't one yet, _pgfault_handler will be 0.
|
||||
// The first time we register a handler, we need to
|
||||
// allocate an exception stack (one page of memory with its top
|
||||
// at UXSTACKTOP), and tell the kernel to call the assembly-language
|
||||
// _pgfault_upcall routine when a page fault occurs.
|
||||
//
|
||||
void
|
||||
set_pgfault_handler(void (*handler)(struct UTrapframe *utf))
|
||||
{
|
||||
int r;
|
||||
|
||||
if (_pgfault_handler == 0) {
|
||||
if(sys_page_alloc(0, (void*) UXSTACKTOP - PGSIZE, PTE_U | PTE_P | PTE_W) < 0)
|
||||
panic("set_pgfault_handler");
|
||||
sys_env_set_pgfault_upcall(0, _pgfault_upcall);
|
||||
}
|
||||
|
||||
// Save handler pointer for assembly to call.
|
||||
_pgfault_handler = handler;
|
||||
}
|
||||
191
lib/pipe.c
Normal file
191
lib/pipe.c
Normal file
@@ -0,0 +1,191 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
#define debug 0
|
||||
|
||||
static ssize_t devpipe_read(struct Fd *fd, void *buf, size_t n);
|
||||
static ssize_t devpipe_write(struct Fd *fd, const void *buf, size_t n);
|
||||
static int devpipe_stat(struct Fd *fd, struct Stat *stat);
|
||||
static int devpipe_close(struct Fd *fd);
|
||||
|
||||
struct Dev devpipe =
|
||||
{
|
||||
.dev_id = 'p',
|
||||
.dev_name = "pipe",
|
||||
.dev_read = devpipe_read,
|
||||
.dev_write = devpipe_write,
|
||||
.dev_close = devpipe_close,
|
||||
.dev_stat = devpipe_stat,
|
||||
};
|
||||
|
||||
#define PIPEBUFSIZ 32 // small to provoke races
|
||||
|
||||
struct Pipe {
|
||||
off_t p_rpos; // read position
|
||||
off_t p_wpos; // write position
|
||||
uint8_t p_buf[PIPEBUFSIZ]; // data buffer
|
||||
};
|
||||
|
||||
int
|
||||
pipe(int pfd[2])
|
||||
{
|
||||
int r;
|
||||
struct Fd *fd0, *fd1;
|
||||
void *va;
|
||||
|
||||
// allocate the file descriptor table entries
|
||||
if ((r = fd_alloc(&fd0)) < 0
|
||||
|| (r = sys_page_alloc(0, fd0, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
|
||||
goto err;
|
||||
|
||||
if ((r = fd_alloc(&fd1)) < 0
|
||||
|| (r = sys_page_alloc(0, fd1, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
|
||||
goto err1;
|
||||
|
||||
// allocate the pipe structure as first data page in both
|
||||
va = fd2data(fd0);
|
||||
if ((r = sys_page_alloc(0, va, PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
|
||||
goto err2;
|
||||
if ((r = sys_page_map(0, va, 0, fd2data(fd1), PTE_P|PTE_W|PTE_U|PTE_SHARE)) < 0)
|
||||
goto err3;
|
||||
|
||||
// set up fd structures
|
||||
fd0->fd_dev_id = devpipe.dev_id;
|
||||
fd0->fd_omode = O_RDONLY;
|
||||
|
||||
fd1->fd_dev_id = devpipe.dev_id;
|
||||
fd1->fd_omode = O_WRONLY;
|
||||
|
||||
if (debug)
|
||||
cprintf("[%08x] pipecreate %08x\n", thisenv->env_id, uvpt[PGNUM(va)]);
|
||||
|
||||
pfd[0] = fd2num(fd0);
|
||||
pfd[1] = fd2num(fd1);
|
||||
return 0;
|
||||
|
||||
err3:
|
||||
sys_page_unmap(0, va);
|
||||
err2:
|
||||
sys_page_unmap(0, fd1);
|
||||
err1:
|
||||
sys_page_unmap(0, fd0);
|
||||
err:
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
_pipeisclosed(struct Fd *fd, struct Pipe *p)
|
||||
{
|
||||
int n, nn, ret;
|
||||
|
||||
while (1) {
|
||||
n = thisenv->env_runs;
|
||||
ret = pageref(fd) == pageref(p);
|
||||
nn = thisenv->env_runs;
|
||||
if (n == nn)
|
||||
return ret;
|
||||
if (n != nn && ret == 1)
|
||||
cprintf("pipe race avoided\n", n, thisenv->env_runs, ret);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
pipeisclosed(int fdnum)
|
||||
{
|
||||
struct Fd *fd;
|
||||
struct Pipe *p;
|
||||
int r;
|
||||
|
||||
if ((r = fd_lookup(fdnum, &fd)) < 0)
|
||||
return r;
|
||||
p = (struct Pipe*) fd2data(fd);
|
||||
return _pipeisclosed(fd, p);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
devpipe_read(struct Fd *fd, void *vbuf, size_t n)
|
||||
{
|
||||
uint8_t *buf;
|
||||
size_t i;
|
||||
struct Pipe *p;
|
||||
|
||||
p = (struct Pipe*)fd2data(fd);
|
||||
if (debug)
|
||||
cprintf("[%08x] devpipe_read %08x %d rpos %d wpos %d\n",
|
||||
thisenv->env_id, uvpt[PGNUM(p)], n, p->p_rpos, p->p_wpos);
|
||||
|
||||
buf = vbuf;
|
||||
for (i = 0; i < n; i++) {
|
||||
while (p->p_rpos == p->p_wpos) {
|
||||
// pipe is empty
|
||||
// if we got any data, return it
|
||||
if (i > 0)
|
||||
return i;
|
||||
// if all the writers are gone, note eof
|
||||
if (_pipeisclosed(fd, p))
|
||||
return 0;
|
||||
// yield and see what happens
|
||||
if (debug)
|
||||
cprintf("devpipe_read yield\n");
|
||||
sys_yield();
|
||||
}
|
||||
// there's a byte. take it.
|
||||
// wait to increment rpos until the byte is taken!
|
||||
buf[i] = p->p_buf[p->p_rpos % PIPEBUFSIZ];
|
||||
p->p_rpos++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
devpipe_write(struct Fd *fd, const void *vbuf, size_t n)
|
||||
{
|
||||
const uint8_t *buf;
|
||||
size_t i;
|
||||
struct Pipe *p;
|
||||
|
||||
p = (struct Pipe*) fd2data(fd);
|
||||
if (debug)
|
||||
cprintf("[%08x] devpipe_write %08x %d rpos %d wpos %d\n",
|
||||
thisenv->env_id, uvpt[PGNUM(p)], n, p->p_rpos, p->p_wpos);
|
||||
|
||||
buf = vbuf;
|
||||
for (i = 0; i < n; i++) {
|
||||
while (p->p_wpos >= p->p_rpos + sizeof(p->p_buf)) {
|
||||
// pipe is full
|
||||
// if all the readers are gone
|
||||
// (it's only writers like us now),
|
||||
// note eof
|
||||
if (_pipeisclosed(fd, p))
|
||||
return 0;
|
||||
// yield and see what happens
|
||||
if (debug)
|
||||
cprintf("devpipe_write yield\n");
|
||||
sys_yield();
|
||||
}
|
||||
// there's room for a byte. store it.
|
||||
// wait to increment wpos until the byte is stored!
|
||||
p->p_buf[p->p_wpos % PIPEBUFSIZ] = buf[i];
|
||||
p->p_wpos++;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int
|
||||
devpipe_stat(struct Fd *fd, struct Stat *stat)
|
||||
{
|
||||
struct Pipe *p = (struct Pipe*) fd2data(fd);
|
||||
strcpy(stat->st_name, "<pipe>");
|
||||
stat->st_size = p->p_wpos - p->p_rpos;
|
||||
stat->st_isdir = 0;
|
||||
stat->st_dev = &devpipe;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
devpipe_close(struct Fd *fd)
|
||||
{
|
||||
(void) sys_page_unmap(0, fd);
|
||||
return sys_page_unmap(0, fd2data(fd));
|
||||
}
|
||||
|
||||
@@ -26,6 +26,15 @@ static const char * const error_string[MAXERROR] =
|
||||
[E_NO_MEM] = "out of memory",
|
||||
[E_NO_FREE_ENV] = "out of environments",
|
||||
[E_FAULT] = "segmentation fault",
|
||||
[E_IPC_NOT_RECV]= "env is not recving",
|
||||
[E_EOF] = "unexpected end of file",
|
||||
[E_NO_DISK] = "no free space on disk",
|
||||
[E_MAX_OPEN] = "too many files are open",
|
||||
[E_NOT_FOUND] = "file or block not found",
|
||||
[E_BAD_PATH] = "invalid path",
|
||||
[E_FILE_EXISTS] = "file already exists",
|
||||
[E_NOT_EXEC] = "file is not a valid executable",
|
||||
[E_NOT_SUPP] = "operation not supported",
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -9,14 +9,20 @@ readline(const char *prompt)
|
||||
{
|
||||
int i, c, echoing;
|
||||
|
||||
#if JOS_KERNEL
|
||||
if (prompt != NULL)
|
||||
cprintf("%s", prompt);
|
||||
#else
|
||||
if (prompt != NULL)
|
||||
fprintf(1, "%s", prompt);
|
||||
#endif
|
||||
|
||||
i = 0;
|
||||
echoing = iscons(0);
|
||||
while (1) {
|
||||
c = getchar();
|
||||
if (c < 0) {
|
||||
if (c != -E_EOF)
|
||||
cprintf("read error: %e\n", c);
|
||||
return NULL;
|
||||
} else if ((c == '\b' || c == '\x7f') && i > 0) {
|
||||
|
||||
322
lib/spawn.c
Normal file
322
lib/spawn.c
Normal file
@@ -0,0 +1,322 @@
|
||||
#include <inc/lib.h>
|
||||
#include <inc/elf.h>
|
||||
|
||||
#define UTEMP2USTACK(addr) ((void*) (addr) + (USTACKTOP - PGSIZE) - UTEMP)
|
||||
#define UTEMP2 (UTEMP + PGSIZE)
|
||||
#define UTEMP3 (UTEMP2 + PGSIZE)
|
||||
|
||||
// Helper functions for spawn.
|
||||
static int init_stack(envid_t child, const char **argv, uintptr_t *init_esp);
|
||||
static int map_segment(envid_t child, uintptr_t va, size_t memsz,
|
||||
int fd, size_t filesz, off_t fileoffset, int perm);
|
||||
static int copy_shared_pages(envid_t child);
|
||||
|
||||
// Spawn a child process from a program image loaded from the file system.
|
||||
// prog: the pathname of the program to run.
|
||||
// argv: pointer to null-terminated array of pointers to strings,
|
||||
// which will be passed to the child as its command-line arguments.
|
||||
// Returns child envid on success, < 0 on failure.
|
||||
int
|
||||
spawn(const char *prog, const char **argv)
|
||||
{
|
||||
unsigned char elf_buf[512];
|
||||
struct Trapframe child_tf;
|
||||
envid_t child;
|
||||
|
||||
int fd, i, r;
|
||||
struct Elf *elf;
|
||||
struct Proghdr *ph;
|
||||
int perm;
|
||||
|
||||
// This code follows this procedure:
|
||||
//
|
||||
// - Open the program file.
|
||||
//
|
||||
// - Read the ELF header, as you have before, and sanity check its
|
||||
// magic number. (Check out your load_icode!)
|
||||
//
|
||||
// - Use sys_exofork() to create a new environment.
|
||||
//
|
||||
// - Set child_tf to an initial struct Trapframe for the child.
|
||||
//
|
||||
// - Call the init_stack() function above to set up
|
||||
// the initial stack page for the child environment.
|
||||
//
|
||||
// - Map all of the program's segments that are of p_type
|
||||
// ELF_PROG_LOAD into the new environment's address space.
|
||||
// Use the p_flags field in the Proghdr for each segment
|
||||
// to determine how to map the segment:
|
||||
//
|
||||
// * If the ELF flags do not include ELF_PROG_FLAG_WRITE,
|
||||
// then the segment contains text and read-only data.
|
||||
// Use read_map() to read the contents of this segment,
|
||||
// and map the pages it returns directly into the child
|
||||
// so that multiple instances of the same program
|
||||
// will share the same copy of the program text.
|
||||
// Be sure to map the program text read-only in the child.
|
||||
// Read_map is like read but returns a pointer to the data in
|
||||
// *blk rather than copying the data into another buffer.
|
||||
//
|
||||
// * If the ELF segment flags DO include ELF_PROG_FLAG_WRITE,
|
||||
// then the segment contains read/write data and bss.
|
||||
// As with load_icode() in Lab 3, such an ELF segment
|
||||
// occupies p_memsz bytes in memory, but only the FIRST
|
||||
// p_filesz bytes of the segment are actually loaded
|
||||
// from the executable file - you must clear the rest to zero.
|
||||
// For each page to be mapped for a read/write segment,
|
||||
// allocate a page in the parent temporarily at UTEMP,
|
||||
// read() the appropriate portion of the file into that page
|
||||
// and/or use memset() to zero non-loaded portions.
|
||||
// (You can avoid calling memset(), if you like, if
|
||||
// page_alloc() returns zeroed pages already.)
|
||||
// Then insert the page mapping into the child.
|
||||
// Look at init_stack() for inspiration.
|
||||
// Be sure you understand why you can't use read_map() here.
|
||||
//
|
||||
// Note: None of the segment addresses or lengths above
|
||||
// are guaranteed to be page-aligned, so you must deal with
|
||||
// these non-page-aligned values appropriately.
|
||||
// The ELF linker does, however, guarantee that no two segments
|
||||
// will overlap on the same page; and it guarantees that
|
||||
// PGOFF(ph->p_offset) == PGOFF(ph->p_va).
|
||||
//
|
||||
// - Call sys_env_set_trapframe(child, &child_tf) to set up the
|
||||
// correct initial eip and esp values in the child.
|
||||
//
|
||||
// - Start the child process running with sys_env_set_status().
|
||||
|
||||
if ((r = open(prog, O_RDONLY)) < 0)
|
||||
return r;
|
||||
fd = r;
|
||||
|
||||
// Read elf header
|
||||
elf = (struct Elf*) elf_buf;
|
||||
if (readn(fd, elf_buf, sizeof(elf_buf)) != sizeof(elf_buf)
|
||||
|| elf->e_magic != ELF_MAGIC) {
|
||||
close(fd);
|
||||
cprintf("elf magic %08x want %08x\n", elf->e_magic, ELF_MAGIC);
|
||||
return -E_NOT_EXEC;
|
||||
}
|
||||
|
||||
// Create new child environment
|
||||
if ((r = sys_exofork()) < 0)
|
||||
return r;
|
||||
child = r;
|
||||
|
||||
// Set up trap frame, including initial stack.
|
||||
child_tf = envs[ENVX(child)].env_tf;
|
||||
child_tf.tf_eip = elf->e_entry;
|
||||
|
||||
if ((r = init_stack(child, argv, &child_tf.tf_esp)) < 0)
|
||||
return r;
|
||||
|
||||
// Set up program segments as defined in ELF header.
|
||||
ph = (struct Proghdr*) (elf_buf + elf->e_phoff);
|
||||
for (i = 0; i < elf->e_phnum; i++, ph++) {
|
||||
if (ph->p_type != ELF_PROG_LOAD)
|
||||
continue;
|
||||
perm = PTE_P | PTE_U;
|
||||
if (ph->p_flags & ELF_PROG_FLAG_WRITE)
|
||||
perm |= PTE_W;
|
||||
if ((r = map_segment(child, ph->p_va, ph->p_memsz,
|
||||
fd, ph->p_filesz, ph->p_offset, perm)) < 0)
|
||||
goto error;
|
||||
}
|
||||
close(fd);
|
||||
fd = -1;
|
||||
|
||||
// Copy shared library state.
|
||||
if ((r = copy_shared_pages(child)) < 0)
|
||||
panic("copy_shared_pages: %e", r);
|
||||
|
||||
child_tf.tf_eflags |= FL_IOPL_3; // devious: see user/faultio.c
|
||||
if ((r = sys_env_set_trapframe(child, &child_tf)) < 0)
|
||||
panic("sys_env_set_trapframe: %e", r);
|
||||
|
||||
if ((r = sys_env_set_status(child, ENV_RUNNABLE)) < 0)
|
||||
panic("sys_env_set_status: %e", r);
|
||||
|
||||
return child;
|
||||
|
||||
error:
|
||||
sys_env_destroy(child);
|
||||
close(fd);
|
||||
return r;
|
||||
}
|
||||
|
||||
// Spawn, taking command-line arguments array directly on the stack.
|
||||
// NOTE: Must have a sentinal of NULL at the end of the args
|
||||
// (none of the args may be NULL).
|
||||
int
|
||||
spawnl(const char *prog, const char *arg0, ...)
|
||||
{
|
||||
// We calculate argc by advancing the args until we hit NULL.
|
||||
// The contract of the function guarantees that the last
|
||||
// argument will always be NULL, and that none of the other
|
||||
// arguments will be NULL.
|
||||
int argc=0;
|
||||
va_list vl;
|
||||
va_start(vl, arg0);
|
||||
while(va_arg(vl, void *) != NULL)
|
||||
argc++;
|
||||
va_end(vl);
|
||||
|
||||
// Now that we have the size of the args, do a second pass
|
||||
// and store the values in a VLA, which has the format of argv
|
||||
const char *argv[argc+2];
|
||||
argv[0] = arg0;
|
||||
argv[argc+1] = NULL;
|
||||
|
||||
va_start(vl, arg0);
|
||||
unsigned i;
|
||||
for(i=0;i<argc;i++)
|
||||
argv[i+1] = va_arg(vl, const char *);
|
||||
va_end(vl);
|
||||
return spawn(prog, argv);
|
||||
}
|
||||
|
||||
|
||||
// Set up the initial stack page for the new child process with envid 'child'
|
||||
// using the arguments array pointed to by 'argv',
|
||||
// which is a null-terminated array of pointers to null-terminated strings.
|
||||
//
|
||||
// On success, returns 0 and sets *init_esp
|
||||
// to the initial stack pointer with which the child should start.
|
||||
// Returns < 0 on failure.
|
||||
static int
|
||||
init_stack(envid_t child, const char **argv, uintptr_t *init_esp)
|
||||
{
|
||||
size_t string_size;
|
||||
int argc, i, r;
|
||||
char *string_store;
|
||||
uintptr_t *argv_store;
|
||||
|
||||
// Count the number of arguments (argc)
|
||||
// and the total amount of space needed for strings (string_size).
|
||||
string_size = 0;
|
||||
for (argc = 0; argv[argc] != 0; argc++)
|
||||
string_size += strlen(argv[argc]) + 1;
|
||||
|
||||
// Determine where to place the strings and the argv array.
|
||||
// Set up pointers into the temporary page 'UTEMP'; we'll map a page
|
||||
// there later, then remap that page into the child environment
|
||||
// at (USTACKTOP - PGSIZE).
|
||||
// strings is the topmost thing on the stack.
|
||||
string_store = (char*) UTEMP + PGSIZE - string_size;
|
||||
// argv is below that. There's one argument pointer per argument, plus
|
||||
// a null pointer.
|
||||
argv_store = (uintptr_t*) (ROUNDDOWN(string_store, 4) - 4 * (argc + 1));
|
||||
|
||||
// Make sure that argv, strings, and the 2 words that hold 'argc'
|
||||
// and 'argv' themselves will all fit in a single stack page.
|
||||
if ((void*) (argv_store - 2) < (void*) UTEMP)
|
||||
return -E_NO_MEM;
|
||||
|
||||
// Allocate the single stack page at UTEMP.
|
||||
if ((r = sys_page_alloc(0, (void*) UTEMP, PTE_P|PTE_U|PTE_W)) < 0)
|
||||
return r;
|
||||
|
||||
|
||||
// * Initialize 'argv_store[i]' to point to argument string i,
|
||||
// for all 0 <= i < argc.
|
||||
// Also, copy the argument strings from 'argv' into the
|
||||
// newly-allocated stack page.
|
||||
//
|
||||
// * Set 'argv_store[argc]' to 0 to null-terminate the args array.
|
||||
//
|
||||
// * Push two more words onto the child's stack below 'args',
|
||||
// containing the argc and argv parameters to be passed
|
||||
// to the child's umain() function.
|
||||
// argv should be below argc on the stack.
|
||||
// (Again, argv should use an address valid in the child's
|
||||
// environment.)
|
||||
//
|
||||
// * Set *init_esp to the initial stack pointer for the child,
|
||||
// (Again, use an address valid in the child's environment.)
|
||||
for (i = 0; i < argc; i++) {
|
||||
argv_store[i] = UTEMP2USTACK(string_store);
|
||||
strcpy(string_store, argv[i]);
|
||||
string_store += strlen(argv[i]) + 1;
|
||||
}
|
||||
argv_store[argc] = 0;
|
||||
assert(string_store == (char*)UTEMP + PGSIZE);
|
||||
|
||||
argv_store[-1] = UTEMP2USTACK(argv_store);
|
||||
argv_store[-2] = argc;
|
||||
|
||||
*init_esp = UTEMP2USTACK(&argv_store[-2]);
|
||||
|
||||
// After completing the stack, map it into the child's address space
|
||||
// and unmap it from ours!
|
||||
if ((r = sys_page_map(0, UTEMP, child, (void*) (USTACKTOP - PGSIZE), PTE_P | PTE_U | PTE_W)) < 0)
|
||||
goto error;
|
||||
if ((r = sys_page_unmap(0, UTEMP)) < 0)
|
||||
goto error;
|
||||
|
||||
return 0;
|
||||
|
||||
error:
|
||||
sys_page_unmap(0, UTEMP);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
map_segment(envid_t child, uintptr_t va, size_t memsz,
|
||||
int fd, size_t filesz, off_t fileoffset, int perm)
|
||||
{
|
||||
int i, r;
|
||||
void *blk;
|
||||
|
||||
//cprintf("map_segment %x+%x\n", va, memsz);
|
||||
|
||||
if ((i = PGOFF(va))) {
|
||||
va -= i;
|
||||
memsz += i;
|
||||
filesz += i;
|
||||
fileoffset -= i;
|
||||
}
|
||||
|
||||
for (i = 0; i < memsz; i += PGSIZE) {
|
||||
if (i >= filesz) {
|
||||
// allocate a blank page
|
||||
if ((r = sys_page_alloc(child, (void*) (va + i), perm)) < 0)
|
||||
return r;
|
||||
} else {
|
||||
// from file
|
||||
if ((r = sys_page_alloc(0, UTEMP, PTE_P|PTE_U|PTE_W)) < 0)
|
||||
return r;
|
||||
if ((r = seek(fd, fileoffset + i)) < 0)
|
||||
return r;
|
||||
if ((r = readn(fd, UTEMP, MIN(PGSIZE, filesz-i))) < 0)
|
||||
return r;
|
||||
if ((r = sys_page_map(0, UTEMP, child, (void*) (va + i), perm)) < 0)
|
||||
panic("spawn: sys_page_map data: %e", r);
|
||||
sys_page_unmap(0, UTEMP);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the mappings for shared pages into the child address space.
|
||||
static int
|
||||
copy_shared_pages(envid_t child)
|
||||
{
|
||||
// LAB 5: Your code here.
|
||||
int r;
|
||||
for(int pde_i = 0; pde_i < PDX(UTOP); pde_i++) {
|
||||
pde_t pde = uvpd[pde_i];
|
||||
if(!(pde & PTE_P)) continue;
|
||||
|
||||
for(int pte_i = 0; pte_i < NPTENTRIES; pte_i++) {
|
||||
int pn = pde_i * NPTENTRIES + pte_i;
|
||||
pte_t pte = uvpt[pn];
|
||||
|
||||
if((pte & (PTE_P | PTE_SHARE)) != (PTE_P | PTE_SHARE)) continue;
|
||||
void* addr = (void*) (pn * PGSIZE);
|
||||
if((r = sys_page_map(0, addr, child, addr, pte & PTE_SYSCALL)) < 0)
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ fast_syscall(int num, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4) {
|
||||
void
|
||||
sys_cputs(const char *s, size_t len)
|
||||
{
|
||||
fast_syscall(SYS_cputs, (uint32_t)s, len, 0, 0);
|
||||
syscall(SYS_cputs, 0, (uint32_t)s, len, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_cgetc(void)
|
||||
{
|
||||
return fast_syscall(SYS_cgetc, 0, 0, 0, 0);
|
||||
return syscall(SYS_cgetc, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
@@ -76,3 +76,59 @@ sys_getenvid(void)
|
||||
return syscall(SYS_getenvid, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void
|
||||
sys_yield(void)
|
||||
{
|
||||
syscall(SYS_yield, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_page_alloc(envid_t envid, void *va, int perm)
|
||||
{
|
||||
return syscall(SYS_page_alloc, 1, envid, (uint32_t) va, perm, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_page_map(envid_t srcenv, void *srcva, envid_t dstenv, void *dstva, int perm)
|
||||
{
|
||||
return syscall(SYS_page_map, 1, srcenv, (uint32_t) srcva, dstenv, (uint32_t) dstva, perm);
|
||||
}
|
||||
|
||||
int
|
||||
sys_page_unmap(envid_t envid, void *va)
|
||||
{
|
||||
return syscall(SYS_page_unmap, 1, envid, (uint32_t) va, 0, 0, 0);
|
||||
}
|
||||
|
||||
// sys_exofork is inlined in lib.h
|
||||
|
||||
int
|
||||
sys_env_set_status(envid_t envid, int status)
|
||||
{
|
||||
return syscall(SYS_env_set_status, 1, envid, status, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_env_set_trapframe(envid_t envid, struct Trapframe *tf)
|
||||
{
|
||||
return syscall(SYS_env_set_trapframe, 1, envid, (uint32_t) tf, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_env_set_pgfault_upcall(envid_t envid, void *upcall)
|
||||
{
|
||||
return syscall(SYS_env_set_pgfault_upcall, 1, envid, (uint32_t) upcall, 0, 0, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_ipc_try_send(envid_t envid, uint32_t value, void *srcva, int perm)
|
||||
{
|
||||
return syscall(SYS_ipc_try_send, 0, envid, value, (uint32_t) srcva, perm, 0);
|
||||
}
|
||||
|
||||
int
|
||||
sys_ipc_recv(void *dstva)
|
||||
{
|
||||
return syscall(SYS_ipc_recv, 1, (uint32_t)dstva, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
13
lib/wait.c
Normal file
13
lib/wait.c
Normal file
@@ -0,0 +1,13 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
// Waits until 'envid' exits.
|
||||
void
|
||||
wait(envid_t envid)
|
||||
{
|
||||
const volatile struct Env *e;
|
||||
|
||||
assert(envid != 0);
|
||||
e = &envs[ENVX(envid)];
|
||||
while (e->env_id == envid && e->env_status != ENV_FREE)
|
||||
sys_yield();
|
||||
}
|
||||
@@ -10,7 +10,8 @@ $(OBJDIR)/user/%.o: user/%.c $(OBJDIR)/.vars.USER_CFLAGS
|
||||
|
||||
$(OBJDIR)/user/%: $(OBJDIR)/user/%.o $(OBJDIR)/lib/entry.o $(USERLIBS:%=$(OBJDIR)/lib/lib%.a) user/user.ld
|
||||
@echo + ld $@
|
||||
$(V)$(LD) -o $@ $(ULDFLAGS) $(LDFLAGS) -nostdlib $(OBJDIR)/lib/entry.o $@.o -L$(OBJDIR)/lib $(USERLIBS:%=-l%) $(GCC_LIB)
|
||||
$(V)$(OBJDUMP) -S $@ > $@.asm
|
||||
$(V)$(NM) -n $@ > $@.sym
|
||||
$(V)$(LD) -o $@.debug $(ULDFLAGS) $(LDFLAGS) -nostdlib $(OBJDIR)/lib/entry.o $@.o -L$(OBJDIR)/lib $(USERLIBS:%=-l%) $(GCC_LIB)
|
||||
$(V)$(OBJDUMP) -S $@.debug > $@.asm
|
||||
$(V)$(NM) -n $@.debug > $@.sym
|
||||
$(V)$(OBJCOPY) -R .stab -R .stabstr --add-gnu-debuglink=$(basename $@.debug) $@.debug $@
|
||||
|
||||
|
||||
36
user/cat.c
Normal file
36
user/cat.c
Normal file
@@ -0,0 +1,36 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
char buf[8192];
|
||||
|
||||
void
|
||||
cat(int f, char *s)
|
||||
{
|
||||
long n;
|
||||
int r;
|
||||
|
||||
while ((n = read(f, buf, (long)sizeof(buf))) > 0)
|
||||
if ((r = write(1, buf, n)) != n)
|
||||
panic("write error copying %s: %e", s, r);
|
||||
if (n < 0)
|
||||
panic("error reading %s: %e", s, n);
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int f, i;
|
||||
|
||||
binaryname = "cat";
|
||||
if (argc == 1)
|
||||
cat(0, "<stdin>");
|
||||
else
|
||||
for (i = 1; i < argc; i++) {
|
||||
f = open(argv[i], O_RDONLY);
|
||||
if (f < 0)
|
||||
printf("can't open %s: %e\n", argv[i], f);
|
||||
else {
|
||||
cat(f, argv[i]);
|
||||
close(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
80
user/dumbfork.c
Normal file
80
user/dumbfork.c
Normal file
@@ -0,0 +1,80 @@
|
||||
// Ping-pong a counter between two processes.
|
||||
// Only need to start one of these -- splits into two, crudely.
|
||||
|
||||
#include <inc/string.h>
|
||||
#include <inc/lib.h>
|
||||
|
||||
envid_t dumbfork(void);
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
envid_t who;
|
||||
int i;
|
||||
|
||||
// fork a child process
|
||||
who = dumbfork();
|
||||
|
||||
// print a message and yield to the other a few times
|
||||
for (i = 0; i < (who ? 10 : 20); i++) {
|
||||
cprintf("%d: I am the %s!\n", i, who ? "parent" : "child");
|
||||
sys_yield();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
duppage(envid_t dstenv, void *addr)
|
||||
{
|
||||
int r;
|
||||
|
||||
// This is NOT what you should do in your fork.
|
||||
if ((r = sys_page_alloc(dstenv, addr, PTE_P|PTE_U|PTE_W)) < 0)
|
||||
panic("sys_page_alloc: %e", r);
|
||||
if ((r = sys_page_map(dstenv, addr, 0, UTEMP, PTE_P|PTE_U|PTE_W)) < 0)
|
||||
panic("sys_page_map: %e", r);
|
||||
memmove(UTEMP, addr, PGSIZE);
|
||||
if ((r = sys_page_unmap(0, UTEMP)) < 0)
|
||||
panic("sys_page_unmap: %e", r);
|
||||
}
|
||||
|
||||
envid_t
|
||||
dumbfork(void)
|
||||
{
|
||||
envid_t envid;
|
||||
uint8_t *addr;
|
||||
int r;
|
||||
extern unsigned char end[];
|
||||
|
||||
// Allocate a new child environment.
|
||||
// The kernel will initialize it with a copy of our register state,
|
||||
// so that the child will appear to have called sys_exofork() too -
|
||||
// except that in the child, this "fake" call to sys_exofork()
|
||||
// will return 0 instead of the envid of the child.
|
||||
envid = sys_exofork();
|
||||
if (envid < 0)
|
||||
panic("sys_exofork: %e", envid);
|
||||
if (envid == 0) {
|
||||
// We're the child.
|
||||
// The copied value of the global variable 'thisenv'
|
||||
// is no longer valid (it refers to the parent!).
|
||||
// Fix it and return 0.
|
||||
thisenv = &envs[ENVX(sys_getenvid())];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We're the parent.
|
||||
// Eagerly copy our entire address space into the child.
|
||||
// This is NOT what you should do in your fork implementation.
|
||||
for (addr = (uint8_t*) UTEXT; addr < end; addr += PGSIZE)
|
||||
duppage(envid, addr);
|
||||
|
||||
// Also copy the stack we are currently running on.
|
||||
duppage(envid, ROUNDDOWN(&addr, PGSIZE));
|
||||
|
||||
// Start the child environment running
|
||||
if ((r = sys_env_set_status(envid, ENV_RUNNABLE)) < 0)
|
||||
panic("sys_env_set_status: %e", r);
|
||||
|
||||
return envid;
|
||||
}
|
||||
|
||||
21
user/echo.c
Normal file
21
user/echo.c
Normal file
@@ -0,0 +1,21 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, nflag;
|
||||
|
||||
nflag = 0;
|
||||
if (argc > 1 && strcmp(argv[1], "-n") == 0) {
|
||||
nflag = 1;
|
||||
argc--;
|
||||
argv++;
|
||||
}
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (i > 1)
|
||||
write(1, " ", 1);
|
||||
write(1, argv[i], strlen(argv[i]));
|
||||
}
|
||||
if (!nflag)
|
||||
write(1, "\n", 1);
|
||||
}
|
||||
25
user/fairness.c
Normal file
25
user/fairness.c
Normal file
@@ -0,0 +1,25 @@
|
||||
// Demonstrate lack of fairness in IPC.
|
||||
// Start three instances of this program as envs 1, 2, and 3.
|
||||
// (user/idle is env 0).
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
envid_t who, id;
|
||||
|
||||
id = sys_getenvid();
|
||||
|
||||
if (thisenv == &envs[1]) {
|
||||
while (1) {
|
||||
ipc_recv(&who, 0, 0);
|
||||
cprintf("%x recv from %x\n", id, who);
|
||||
}
|
||||
} else {
|
||||
cprintf("%x loop sending to %x\n", id, envs[1].env_id);
|
||||
while (1)
|
||||
ipc_send(envs[1].env_id, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
24
user/faultalloc.c
Normal file
24
user/faultalloc.c
Normal file
@@ -0,0 +1,24 @@
|
||||
// test user-level fault handler -- alloc pages to fix faults
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
handler(struct UTrapframe *utf)
|
||||
{
|
||||
int r;
|
||||
void *addr = (void*)utf->utf_fault_va;
|
||||
|
||||
cprintf("fault %x\n", addr);
|
||||
if ((r = sys_page_alloc(0, ROUNDDOWN(addr, PGSIZE),
|
||||
PTE_P|PTE_U|PTE_W)) < 0)
|
||||
panic("allocating at %x in page fault handler: %e", addr, r);
|
||||
snprintf((char*) addr, 100, "this string was faulted in at %x", addr);
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
set_pgfault_handler(handler);
|
||||
cprintf("%s\n", (char*)0xDeadBeef);
|
||||
cprintf("%s\n", (char*)0xCafeBffe);
|
||||
}
|
||||
24
user/faultallocbad.c
Normal file
24
user/faultallocbad.c
Normal file
@@ -0,0 +1,24 @@
|
||||
// test user-level fault handler -- alloc pages to fix faults
|
||||
// doesn't work because we sys_cputs instead of cprintf (exercise: why?)
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
handler(struct UTrapframe *utf)
|
||||
{
|
||||
int r;
|
||||
void *addr = (void*)utf->utf_fault_va;
|
||||
|
||||
cprintf("fault %x\n", addr);
|
||||
if ((r = sys_page_alloc(0, ROUNDDOWN(addr, PGSIZE),
|
||||
PTE_P|PTE_U|PTE_W)) < 0)
|
||||
panic("allocating at %x in page fault handler: %e", addr, r);
|
||||
snprintf((char*) addr, 100, "this string was faulted in at %x", addr);
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
set_pgfault_handler(handler);
|
||||
sys_cputs((char*)0xDEADBEEF, 4);
|
||||
}
|
||||
14
user/faultbadhandler.c
Normal file
14
user/faultbadhandler.c
Normal file
@@ -0,0 +1,14 @@
|
||||
// test bad pointer for user-level fault handler
|
||||
// this is going to fault in the fault handler accessing eip (always!)
|
||||
// so eventually the kernel kills it (PFM_KILL) because
|
||||
// we outrun the stack with invocations of the user-level handler
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
sys_page_alloc(0, (void*) (UXSTACKTOP - PGSIZE), PTE_P|PTE_U|PTE_W);
|
||||
sys_env_set_pgfault_upcall(0, (void*) 0xDeadBeef);
|
||||
*(int*)0 = 0;
|
||||
}
|
||||
19
user/faultdie.c
Normal file
19
user/faultdie.c
Normal file
@@ -0,0 +1,19 @@
|
||||
// test user-level fault handler -- just exit when we fault
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
handler(struct UTrapframe *utf)
|
||||
{
|
||||
void *addr = (void*)utf->utf_fault_va;
|
||||
uint32_t err = utf->utf_err;
|
||||
cprintf("i faulted at va %x, err %x\n", addr, err & 7);
|
||||
sys_env_destroy(sys_getenvid());
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
set_pgfault_handler(handler);
|
||||
*(int*)0xDeadBeef = 0;
|
||||
}
|
||||
11
user/faultevilhandler.c
Normal file
11
user/faultevilhandler.c
Normal file
@@ -0,0 +1,11 @@
|
||||
// test evil pointer for user-level fault handler
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
sys_page_alloc(0, (void*) (UXSTACKTOP - PGSIZE), PTE_P|PTE_U|PTE_W);
|
||||
sys_env_set_pgfault_upcall(0, (void*) 0xF0100020);
|
||||
*(int*)0 = 0;
|
||||
}
|
||||
22
user/faultio.c
Normal file
22
user/faultio.c
Normal file
@@ -0,0 +1,22 @@
|
||||
// test user-level fault handler -- alloc pages to fix faults
|
||||
|
||||
#include <inc/lib.h>
|
||||
#include <inc/x86.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int x, r;
|
||||
int nsecs = 1;
|
||||
int secno = 0;
|
||||
int diskno = 1;
|
||||
|
||||
if (read_eflags() & FL_IOPL_3)
|
||||
cprintf("eflags wrong\n");
|
||||
|
||||
// this outb to select disk 1 should result in a general protection
|
||||
// fault, because user-level code shouldn't be able to use the io space.
|
||||
outb(0x1F6, 0xE0 | (1<<4));
|
||||
|
||||
cprintf("%s: made it here --- bug\n");
|
||||
}
|
||||
12
user/faultnostack.c
Normal file
12
user/faultnostack.c
Normal file
@@ -0,0 +1,12 @@
|
||||
// test user fault handler being called with no exception stack mapped
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void _pgfault_upcall();
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
sys_env_set_pgfault_upcall(0, (void*) _pgfault_upcall);
|
||||
*(int*)0 = 0;
|
||||
}
|
||||
146
user/faultregs.c
Normal file
146
user/faultregs.c
Normal file
@@ -0,0 +1,146 @@
|
||||
// test register restore on user-level page fault return
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
struct regs
|
||||
{
|
||||
struct PushRegs regs;
|
||||
uintptr_t eip;
|
||||
uint32_t eflags;
|
||||
uintptr_t esp;
|
||||
};
|
||||
|
||||
#define SAVE_REGS(base) \
|
||||
"\tmovl %%edi, "base"+0x00\n" \
|
||||
"\tmovl %%esi, "base"+0x04\n" \
|
||||
"\tmovl %%ebp, "base"+0x08\n" \
|
||||
"\tmovl %%ebx, "base"+0x10\n" \
|
||||
"\tmovl %%edx, "base"+0x14\n" \
|
||||
"\tmovl %%ecx, "base"+0x18\n" \
|
||||
"\tmovl %%eax, "base"+0x1c\n" \
|
||||
"\tmovl %%esp, "base"+0x28\n"
|
||||
|
||||
#define LOAD_REGS(base) \
|
||||
"\tmovl "base"+0x00, %%edi\n" \
|
||||
"\tmovl "base"+0x04, %%esi\n" \
|
||||
"\tmovl "base"+0x08, %%ebp\n" \
|
||||
"\tmovl "base"+0x10, %%ebx\n" \
|
||||
"\tmovl "base"+0x14, %%edx\n" \
|
||||
"\tmovl "base"+0x18, %%ecx\n" \
|
||||
"\tmovl "base"+0x1c, %%eax\n" \
|
||||
"\tmovl "base"+0x28, %%esp\n"
|
||||
|
||||
static struct regs before, during, after;
|
||||
|
||||
static void
|
||||
check_regs(struct regs* a, const char *an, struct regs* b, const char *bn,
|
||||
const char *testname)
|
||||
{
|
||||
int mismatch = 0;
|
||||
|
||||
cprintf("%-6s %-8s %-8s\n", "", an, bn);
|
||||
|
||||
#define CHECK(name, field) \
|
||||
do { \
|
||||
cprintf("%-6s %08x %08x ", #name, a->field, b->field); \
|
||||
if (a->field == b->field) \
|
||||
cprintf("OK\n"); \
|
||||
else { \
|
||||
cprintf("MISMATCH\n"); \
|
||||
mismatch = 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
CHECK(edi, regs.reg_edi);
|
||||
CHECK(esi, regs.reg_esi);
|
||||
CHECK(ebp, regs.reg_ebp);
|
||||
CHECK(ebx, regs.reg_ebx);
|
||||
CHECK(edx, regs.reg_edx);
|
||||
CHECK(ecx, regs.reg_ecx);
|
||||
CHECK(eax, regs.reg_eax);
|
||||
CHECK(eip, eip);
|
||||
CHECK(eflags, eflags);
|
||||
CHECK(esp, esp);
|
||||
|
||||
#undef CHECK
|
||||
|
||||
cprintf("Registers %s ", testname);
|
||||
if (!mismatch)
|
||||
cprintf("OK\n");
|
||||
else
|
||||
cprintf("MISMATCH\n");
|
||||
}
|
||||
|
||||
static void
|
||||
pgfault(struct UTrapframe *utf)
|
||||
{
|
||||
int r;
|
||||
|
||||
if (utf->utf_fault_va != (uint32_t)UTEMP)
|
||||
panic("pgfault expected at UTEMP, got 0x%08x (eip %08x)",
|
||||
utf->utf_fault_va, utf->utf_eip);
|
||||
|
||||
// Check registers in UTrapframe
|
||||
during.regs = utf->utf_regs;
|
||||
during.eip = utf->utf_eip;
|
||||
during.eflags = utf->utf_eflags & ~FL_RF;
|
||||
during.esp = utf->utf_esp;
|
||||
check_regs(&before, "before", &during, "during", "in UTrapframe");
|
||||
|
||||
// Map UTEMP so the write succeeds
|
||||
if ((r = sys_page_alloc(0, UTEMP, PTE_U|PTE_P|PTE_W)) < 0)
|
||||
panic("sys_page_alloc: %e", r);
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
set_pgfault_handler(pgfault);
|
||||
|
||||
asm volatile(
|
||||
// Light up eflags to catch more errors
|
||||
"\tpushl %%eax\n"
|
||||
"\tpushfl\n"
|
||||
"\tpopl %%eax\n"
|
||||
"\torl $0x8d5, %%eax\n"
|
||||
"\tpushl %%eax\n"
|
||||
"\tpopfl\n"
|
||||
|
||||
// Save before registers
|
||||
// eflags
|
||||
"\tmov %%eax, %0+0x24\n"
|
||||
// eip
|
||||
"\tleal 0f, %%eax\n"
|
||||
"\tmovl %%eax, %0+0x20\n"
|
||||
"\tpopl %%eax\n"
|
||||
// others
|
||||
SAVE_REGS("%0")
|
||||
|
||||
// Fault at UTEMP
|
||||
"\t0: movl $42, 0x400000\n"
|
||||
|
||||
// Save after registers (except eip and eflags)
|
||||
SAVE_REGS("%1")
|
||||
// Restore registers (except eip and eflags). This
|
||||
// way, the test will run even if EIP is the *only*
|
||||
// thing restored correctly.
|
||||
LOAD_REGS("%0")
|
||||
// Save after eflags (now that stack is back); note
|
||||
// that we were very careful not to modify eflags in
|
||||
// since we saved it
|
||||
"\tpushl %%eax\n"
|
||||
"\tpushfl\n"
|
||||
"\tpopl %%eax\n"
|
||||
"\tmov %%eax, %1+0x24\n"
|
||||
"\tpopl %%eax\n"
|
||||
: : "m" (before), "m" (after) : "memory", "cc");
|
||||
|
||||
// Check UTEMP to roughly determine that EIP was restored
|
||||
// correctly (of course, we probably wouldn't get this far if
|
||||
// it weren't)
|
||||
if (*(int*)UTEMP != 42)
|
||||
cprintf("EIP after page-fault MISMATCH\n");
|
||||
after.eip = before.eip;
|
||||
|
||||
check_regs(&before, "before", &after, "after", "after page-fault");
|
||||
}
|
||||
38
user/forktree.c
Normal file
38
user/forktree.c
Normal file
@@ -0,0 +1,38 @@
|
||||
// Fork a binary tree of processes and display their structure.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
#define DEPTH 3
|
||||
|
||||
void forktree(const char *cur);
|
||||
|
||||
void
|
||||
forkchild(const char *cur, char branch)
|
||||
{
|
||||
char nxt[DEPTH+1];
|
||||
|
||||
if (strlen(cur) >= DEPTH)
|
||||
return;
|
||||
|
||||
snprintf(nxt, DEPTH+1, "%s%c", cur, branch);
|
||||
if (fork() == 0) {
|
||||
forktree(nxt);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
forktree(const char *cur)
|
||||
{
|
||||
cprintf("%04x: I am '%s'\n", sys_getenvid(), cur);
|
||||
|
||||
forkchild(cur, '0');
|
||||
forkchild(cur, '1');
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
forktree("");
|
||||
}
|
||||
|
||||
29
user/icode.c
Normal file
29
user/icode.c
Normal file
@@ -0,0 +1,29 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int fd, n, r;
|
||||
char buf[512+1];
|
||||
|
||||
binaryname = "icode";
|
||||
|
||||
cprintf("icode startup\n");
|
||||
|
||||
cprintf("icode: open /motd\n");
|
||||
if ((fd = open("/motd", O_RDONLY)) < 0)
|
||||
panic("icode: open /motd: %e", fd);
|
||||
|
||||
cprintf("icode: read /motd\n");
|
||||
while ((n = read(fd, buf, sizeof buf-1)) > 0)
|
||||
sys_cputs(buf, n);
|
||||
|
||||
cprintf("icode: close /motd\n");
|
||||
close(fd);
|
||||
|
||||
cprintf("icode: spawn /init\n");
|
||||
if ((r = spawnl("/init", "init", "initarg1", "initarg2", (char*)0)) < 0)
|
||||
panic("icode: spawn /init: %e", r);
|
||||
|
||||
cprintf("icode: exiting\n");
|
||||
}
|
||||
20
user/idle.c
Normal file
20
user/idle.c
Normal file
@@ -0,0 +1,20 @@
|
||||
// idle loop
|
||||
|
||||
#include <inc/x86.h>
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
binaryname = "idle";
|
||||
|
||||
// Loop forever, simply trying to yield to a different environment.
|
||||
// Instead of busy-waiting like this,
|
||||
// a better way would be to use the processor's HLT instruction
|
||||
// to cause the processor to stop executing until the next interrupt -
|
||||
// doing so allows the processor to conserve power more effectively.
|
||||
while (1) {
|
||||
sys_yield();
|
||||
}
|
||||
}
|
||||
|
||||
69
user/init.c
Normal file
69
user/init.c
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
struct {
|
||||
char msg1[5000];
|
||||
char msg2[1000];
|
||||
} data = {
|
||||
"this is initialized data",
|
||||
"so is this"
|
||||
};
|
||||
|
||||
char bss[6000];
|
||||
|
||||
int
|
||||
sum(const char *s, int n)
|
||||
{
|
||||
int i, tot = 0;
|
||||
for (i = 0; i < n; i++)
|
||||
tot ^= i * s[i];
|
||||
return tot;
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, r, x, want;
|
||||
char args[256];
|
||||
|
||||
cprintf("init: running\n");
|
||||
|
||||
want = 0xf989e;
|
||||
if ((x = sum((char*)&data, sizeof data)) != want)
|
||||
cprintf("init: data is not initialized: got sum %08x wanted %08x\n",
|
||||
x, want);
|
||||
else
|
||||
cprintf("init: data seems okay\n");
|
||||
if ((x = sum(bss, sizeof bss)) != 0)
|
||||
cprintf("bss is not initialized: wanted sum 0 got %08x\n", x);
|
||||
else
|
||||
cprintf("init: bss seems okay\n");
|
||||
|
||||
// output in one syscall per line to avoid output interleaving
|
||||
strcat(args, "init: args:");
|
||||
for (i = 0; i < argc; i++) {
|
||||
strcat(args, " '");
|
||||
strcat(args, argv[i]);
|
||||
strcat(args, "'");
|
||||
}
|
||||
cprintf("%s\n", args);
|
||||
|
||||
cprintf("init: running sh\n");
|
||||
|
||||
// being run directly from kernel, so no file descriptors open yet
|
||||
close(0);
|
||||
if ((r = opencons()) < 0)
|
||||
panic("opencons: %e", r);
|
||||
if (r != 0)
|
||||
panic("first opencons used fd %d", r);
|
||||
if ((r = dup(0, 1)) < 0)
|
||||
panic("dup: %e", r);
|
||||
while (1) {
|
||||
cprintf("init: starting sh\n");
|
||||
r = spawnl("/sh", "sh", (char*)0);
|
||||
if (r < 0) {
|
||||
cprintf("init: spawn sh: %e\n", r);
|
||||
continue;
|
||||
}
|
||||
wait(r);
|
||||
}
|
||||
}
|
||||
27
user/initsh.c
Normal file
27
user/initsh.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, r, x, want;
|
||||
|
||||
cprintf("initsh: running sh\n");
|
||||
|
||||
// being run directly from kernel, so no file descriptors open yet
|
||||
close(0);
|
||||
if ((r = opencons()) < 0)
|
||||
panic("opencons: %e", r);
|
||||
if (r != 0)
|
||||
panic("first opencons used fd %d", r);
|
||||
if ((r = dup(0, 1)) < 0)
|
||||
panic("dup: %e", r);
|
||||
while (1) {
|
||||
cprintf("init: starting sh\n");
|
||||
r = spawnl("/sh", "sh", (char*)0);
|
||||
if (r < 0) {
|
||||
cprintf("init: spawn sh: %e\n", r);
|
||||
continue;
|
||||
}
|
||||
wait(r);
|
||||
}
|
||||
}
|
||||
91
user/ls.c
Normal file
91
user/ls.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
int flag[256];
|
||||
|
||||
void lsdir(const char*, const char*);
|
||||
void ls1(const char*, bool, off_t, const char*);
|
||||
|
||||
void
|
||||
ls(const char *path, const char *prefix)
|
||||
{
|
||||
int r;
|
||||
struct Stat st;
|
||||
|
||||
if ((r = stat(path, &st)) < 0)
|
||||
panic("stat %s: %e", path, r);
|
||||
if (st.st_isdir && !flag['d'])
|
||||
lsdir(path, prefix);
|
||||
else
|
||||
ls1(0, st.st_isdir, st.st_size, path);
|
||||
}
|
||||
|
||||
void
|
||||
lsdir(const char *path, const char *prefix)
|
||||
{
|
||||
int fd, n;
|
||||
struct File f;
|
||||
|
||||
if ((fd = open(path, O_RDONLY)) < 0)
|
||||
panic("open %s: %e", path, fd);
|
||||
while ((n = readn(fd, &f, sizeof f)) == sizeof f)
|
||||
if (f.f_name[0])
|
||||
ls1(prefix, f.f_type==FTYPE_DIR, f.f_size, f.f_name);
|
||||
if (n > 0)
|
||||
panic("short read in directory %s", path);
|
||||
if (n < 0)
|
||||
panic("error reading directory %s: %e", path, n);
|
||||
}
|
||||
|
||||
void
|
||||
ls1(const char *prefix, bool isdir, off_t size, const char *name)
|
||||
{
|
||||
const char *sep;
|
||||
|
||||
if(flag['l'])
|
||||
printf("%11d %c ", size, isdir ? 'd' : '-');
|
||||
if(prefix) {
|
||||
if (prefix[0] && prefix[strlen(prefix)-1] != '/')
|
||||
sep = "/";
|
||||
else
|
||||
sep = "";
|
||||
printf("%s%s", prefix, sep);
|
||||
}
|
||||
printf("%s", name);
|
||||
if(flag['F'] && isdir)
|
||||
printf("/");
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
printf("usage: ls [-dFl] [file...]\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i;
|
||||
struct Argstate args;
|
||||
|
||||
argstart(&argc, argv, &args);
|
||||
while ((i = argnext(&args)) >= 0)
|
||||
switch (i) {
|
||||
case 'd':
|
||||
case 'F':
|
||||
case 'l':
|
||||
flag[i]++;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
|
||||
if (argc == 1)
|
||||
ls("/", "");
|
||||
else {
|
||||
for (i = 1; i < argc; i++)
|
||||
ls(argv[i], argv[i]);
|
||||
}
|
||||
}
|
||||
|
||||
35
user/lsfd.c
Normal file
35
user/lsfd.c
Normal file
@@ -0,0 +1,35 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
cprintf("usage: lsfd [-1]\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, usefprint = 0;
|
||||
struct Stat st;
|
||||
struct Argstate args;
|
||||
|
||||
argstart(&argc, argv, &args);
|
||||
while ((i = argnext(&args)) >= 0)
|
||||
if (i == '1')
|
||||
usefprint = 1;
|
||||
else
|
||||
usage();
|
||||
|
||||
for (i = 0; i < 32; i++)
|
||||
if (fstat(i, &st) >= 0) {
|
||||
if (usefprint)
|
||||
fprintf(1, "fd %d: name %s isdir %d size %d dev %s\n",
|
||||
i, st.st_name, st.st_isdir,
|
||||
st.st_size, st.st_dev->dev_name);
|
||||
else
|
||||
cprintf("fd %d: name %s isdir %d size %d dev %s\n",
|
||||
i, st.st_name, st.st_isdir,
|
||||
st.st_size, st.st_dev->dev_name);
|
||||
}
|
||||
}
|
||||
47
user/num.c
Normal file
47
user/num.c
Normal file
@@ -0,0 +1,47 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
int bol = 1;
|
||||
int line = 0;
|
||||
|
||||
void
|
||||
num(int f, const char *s)
|
||||
{
|
||||
long n;
|
||||
int r;
|
||||
char c;
|
||||
|
||||
while ((n = read(f, &c, 1)) > 0) {
|
||||
if (bol) {
|
||||
printf("%5d ", ++line);
|
||||
bol = 0;
|
||||
}
|
||||
if ((r = write(1, &c, 1)) != 1)
|
||||
panic("write error copying %s: %e", s, r);
|
||||
if (c == '\n')
|
||||
bol = 1;
|
||||
}
|
||||
if (n < 0)
|
||||
panic("error reading %s: %e", s, n);
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int f, i;
|
||||
|
||||
binaryname = "num";
|
||||
if (argc == 1)
|
||||
num(0, "<stdin>");
|
||||
else
|
||||
for (i = 1; i < argc; i++) {
|
||||
f = open(argv[i], O_RDONLY);
|
||||
if (f < 0)
|
||||
panic("can't open %s: %e", argv[i], f);
|
||||
else {
|
||||
num(f, argv[i]);
|
||||
close(f);
|
||||
}
|
||||
}
|
||||
exit();
|
||||
}
|
||||
|
||||
29
user/pingpong.c
Normal file
29
user/pingpong.c
Normal file
@@ -0,0 +1,29 @@
|
||||
// Ping-pong a counter between two processes.
|
||||
// Only need to start one of these -- splits into two with fork.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
envid_t who;
|
||||
|
||||
if ((who = fork()) != 0) {
|
||||
// get the ball rolling
|
||||
cprintf("send 0 from %x to %x\n", sys_getenvid(), who);
|
||||
ipc_send(who, 0, 0, 0);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
uint32_t i = ipc_recv(&who, 0, 0);
|
||||
cprintf("%x got %d from %x\n", sys_getenvid(), i, who);
|
||||
if (i == 10)
|
||||
return;
|
||||
i++;
|
||||
ipc_send(who, i, 0, 0);
|
||||
if (i == 10)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
33
user/pingpongs.c
Normal file
33
user/pingpongs.c
Normal file
@@ -0,0 +1,33 @@
|
||||
// Ping-pong a counter between two shared-memory processes.
|
||||
// Only need to start one of these -- splits into two with sfork.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
uint32_t val;
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
envid_t who;
|
||||
uint32_t i;
|
||||
|
||||
i = 0;
|
||||
if ((who = sfork()) != 0) {
|
||||
cprintf("i am %08x; thisenv is %p\n", sys_getenvid(), thisenv);
|
||||
// get the ball rolling
|
||||
cprintf("send 0 from %x to %x\n", sys_getenvid(), who);
|
||||
ipc_send(who, 0, 0, 0);
|
||||
}
|
||||
|
||||
while (1) {
|
||||
ipc_recv(&who, 0, 0);
|
||||
cprintf("%x got %d from %x (thisenv is %p %x)\n", sys_getenvid(), val, who, thisenv, thisenv->env_id);
|
||||
if (val == 10)
|
||||
return;
|
||||
++val;
|
||||
ipc_send(who, 0, 0, 0);
|
||||
if (val == 10)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
53
user/primes.c
Normal file
53
user/primes.c
Normal file
@@ -0,0 +1,53 @@
|
||||
// Concurrent version of prime sieve of Eratosthenes.
|
||||
// Invented by Doug McIlroy, inventor of Unix pipes.
|
||||
// See http://swtch.com/~rsc/thread/.
|
||||
// The picture halfway down the page and the text surrounding it
|
||||
// explain what's going on here.
|
||||
//
|
||||
// Since NENV is 1024, we can print 1022 primes before running out.
|
||||
// The remaining two environments are the integer generator at the bottom
|
||||
// of main and user/idle.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
unsigned
|
||||
primeproc(void)
|
||||
{
|
||||
int i, id, p;
|
||||
envid_t envid;
|
||||
|
||||
// fetch a prime from our left neighbor
|
||||
top:
|
||||
p = ipc_recv(&envid, 0, 0);
|
||||
cprintf("CPU %d: %d ", thisenv->env_cpunum, p);
|
||||
|
||||
// fork a right neighbor to continue the chain
|
||||
if ((id = fork()) < 0)
|
||||
panic("fork: %e", id);
|
||||
if (id == 0)
|
||||
goto top;
|
||||
|
||||
// filter out multiples of our prime
|
||||
while (1) {
|
||||
i = ipc_recv(&envid, 0, 0);
|
||||
if (i % p)
|
||||
ipc_send(id, i, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, id;
|
||||
|
||||
// fork the first prime process in the chain
|
||||
if ((id = fork()) < 0)
|
||||
panic("fork: %e", id);
|
||||
if (id == 0)
|
||||
primeproc();
|
||||
|
||||
// feed all the integers through
|
||||
for (i = 2; ; i++)
|
||||
ipc_send(id, i, 0, 0);
|
||||
}
|
||||
|
||||
76
user/primespipe.c
Normal file
76
user/primespipe.c
Normal file
@@ -0,0 +1,76 @@
|
||||
// Concurrent version of prime sieve of Eratosthenes.
|
||||
// Invented by Doug McIlroy, inventor of Unix pipes.
|
||||
// See http://swtch.com/~rsc/thread/.
|
||||
// The picture halfway down the page and the text surrounding it
|
||||
// explain what's going on here.
|
||||
//
|
||||
// Since NENV is 1024, we can print 1022 primes before running out.
|
||||
// The remaining two environments are the integer generator at the bottom
|
||||
// of main and user/idle.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
unsigned
|
||||
primeproc(int fd)
|
||||
{
|
||||
int i, id, p, pfd[2], wfd, r;
|
||||
|
||||
// fetch a prime from our left neighbor
|
||||
top:
|
||||
if ((r = readn(fd, &p, 4)) != 4)
|
||||
panic("primeproc could not read initial prime: %d, %e", r, r >= 0 ? 0 : r);
|
||||
|
||||
cprintf("%d\n", p);
|
||||
|
||||
// fork a right neighbor to continue the chain
|
||||
if ((i=pipe(pfd)) < 0)
|
||||
panic("pipe: %e", i);
|
||||
if ((id = fork()) < 0)
|
||||
panic("fork: %e", id);
|
||||
if (id == 0) {
|
||||
close(fd);
|
||||
close(pfd[1]);
|
||||
fd = pfd[0];
|
||||
goto top;
|
||||
}
|
||||
|
||||
close(pfd[0]);
|
||||
wfd = pfd[1];
|
||||
|
||||
// filter out multiples of our prime
|
||||
for (;;) {
|
||||
if ((r=readn(fd, &i, 4)) != 4)
|
||||
panic("primeproc %d readn %d %d %e", p, fd, r, r >= 0 ? 0 : r);
|
||||
if (i%p)
|
||||
if ((r=write(wfd, &i, 4)) != 4)
|
||||
panic("primeproc %d write: %d %e", p, r, r >= 0 ? 0 : r);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, id, p[2], r;
|
||||
|
||||
binaryname = "primespipe";
|
||||
|
||||
if ((i=pipe(p)) < 0)
|
||||
panic("pipe: %e", i);
|
||||
|
||||
// fork the first prime process in the chain
|
||||
if ((id=fork()) < 0)
|
||||
panic("fork: %e", id);
|
||||
|
||||
if (id == 0) {
|
||||
close(p[1]);
|
||||
primeproc(p[0]);
|
||||
}
|
||||
|
||||
close(p[0]);
|
||||
|
||||
// feed all the integers through
|
||||
for (i=2;; i++)
|
||||
if ((r=write(p[1], &i, 4)) != 4)
|
||||
panic("generator write: %d, %e", r, r >= 0 ? 0 : r);
|
||||
}
|
||||
|
||||
329
user/sh.c
Normal file
329
user/sh.c
Normal file
@@ -0,0 +1,329 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
#define BUFSIZ 1024 /* Find the buffer overrun bug! */
|
||||
int debug = 0;
|
||||
|
||||
|
||||
// gettoken(s, 0) prepares gettoken for subsequent calls and returns 0.
|
||||
// gettoken(0, token) parses a shell token from the previously set string,
|
||||
// null-terminates that token, stores the token pointer in '*token',
|
||||
// and returns a token ID (0, '<', '>', '|', or 'w').
|
||||
// Subsequent calls to 'gettoken(0, token)' will return subsequent
|
||||
// tokens from the string.
|
||||
int gettoken(char *s, char **token);
|
||||
|
||||
|
||||
// Parse a shell command from string 's' and execute it.
|
||||
// Do not return until the shell command is finished.
|
||||
// runcmd() is called in a forked child,
|
||||
// so it's OK to manipulate file descriptor state.
|
||||
#define MAXARGS 16
|
||||
void
|
||||
runcmd(char* s)
|
||||
{
|
||||
char *argv[MAXARGS], *t, argv0buf[BUFSIZ];
|
||||
int argc, c, i, r, p[2], fd, pipe_child;
|
||||
|
||||
pipe_child = 0;
|
||||
gettoken(s, 0);
|
||||
|
||||
again:
|
||||
argc = 0;
|
||||
while (1) {
|
||||
switch ((c = gettoken(0, &t))) {
|
||||
|
||||
case 'w': // Add an argument
|
||||
if (argc == MAXARGS) {
|
||||
cprintf("too many arguments\n");
|
||||
exit();
|
||||
}
|
||||
argv[argc++] = t;
|
||||
break;
|
||||
|
||||
case '<': // Input redirection
|
||||
// Grab the filename from the argument list
|
||||
if (gettoken(0, &t) != 'w') {
|
||||
cprintf("syntax error: < not followed by word\n");
|
||||
exit();
|
||||
}
|
||||
// Open 't' for reading as file descriptor 0
|
||||
// (which environments use as standard input).
|
||||
// We can't open a file onto a particular descriptor,
|
||||
// so open the file as 'fd',
|
||||
// then check whether 'fd' is 0.
|
||||
// If not, dup 'fd' onto file descriptor 0,
|
||||
// then close the original 'fd'.
|
||||
// LAB 5: Your code here.
|
||||
if ((fd = open(t, O_RDONLY)) < 0) {
|
||||
cprintf("open %s for read: %e", t, fd);
|
||||
}
|
||||
|
||||
if(fd != 0) {
|
||||
dup(fd, 0);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case '>': // Output redirection
|
||||
// Grab the filename from the argument list
|
||||
if (gettoken(0, &t) != 'w') {
|
||||
cprintf("syntax error: > not followed by word\n");
|
||||
exit();
|
||||
}
|
||||
if ((fd = open(t, O_WRONLY|O_CREAT|O_TRUNC)) < 0) {
|
||||
cprintf("open %s for write: %e", t, fd);
|
||||
exit();
|
||||
}
|
||||
if (fd != 1) {
|
||||
dup(fd, 1);
|
||||
close(fd);
|
||||
}
|
||||
break;
|
||||
|
||||
case '|': // Pipe
|
||||
if ((r = pipe(p)) < 0) {
|
||||
cprintf("pipe: %e", r);
|
||||
exit();
|
||||
}
|
||||
if (debug)
|
||||
cprintf("PIPE: %d %d\n", p[0], p[1]);
|
||||
if ((r = fork()) < 0) {
|
||||
cprintf("fork: %e", r);
|
||||
exit();
|
||||
}
|
||||
if (r == 0) {
|
||||
if (p[0] != 0) {
|
||||
dup(p[0], 0);
|
||||
close(p[0]);
|
||||
}
|
||||
close(p[1]);
|
||||
goto again;
|
||||
} else {
|
||||
pipe_child = r;
|
||||
if (p[1] != 1) {
|
||||
dup(p[1], 1);
|
||||
close(p[1]);
|
||||
}
|
||||
close(p[0]);
|
||||
goto runit;
|
||||
}
|
||||
panic("| not implemented");
|
||||
break;
|
||||
|
||||
case 0: // String is complete
|
||||
// Run the current command!
|
||||
goto runit;
|
||||
|
||||
default:
|
||||
panic("bad return %d from gettoken", c);
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
runit:
|
||||
// Return immediately if command line was empty.
|
||||
if(argc == 0) {
|
||||
if (debug)
|
||||
cprintf("EMPTY COMMAND\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up command line.
|
||||
// Read all commands from the filesystem: add an initial '/' to
|
||||
// the command name.
|
||||
// This essentially acts like 'PATH=/'.
|
||||
if (argv[0][0] != '/') {
|
||||
argv0buf[0] = '/';
|
||||
strcpy(argv0buf + 1, argv[0]);
|
||||
argv[0] = argv0buf;
|
||||
}
|
||||
argv[argc] = 0;
|
||||
|
||||
// Print the command.
|
||||
if (debug) {
|
||||
cprintf("[%08x] SPAWN:", thisenv->env_id);
|
||||
for (i = 0; argv[i]; i++)
|
||||
cprintf(" %s", argv[i]);
|
||||
cprintf("\n");
|
||||
}
|
||||
|
||||
// Spawn the command!
|
||||
if ((r = spawn(argv[0], (const char**) argv)) < 0)
|
||||
cprintf("spawn %s: %e\n", argv[0], r);
|
||||
|
||||
// In the parent, close all file descriptors and wait for the
|
||||
// spawned command to exit.
|
||||
close_all();
|
||||
if (r >= 0) {
|
||||
if (debug)
|
||||
cprintf("[%08x] WAIT %s %08x\n", thisenv->env_id, argv[0], r);
|
||||
wait(r);
|
||||
if (debug)
|
||||
cprintf("[%08x] wait finished\n", thisenv->env_id);
|
||||
}
|
||||
|
||||
// If we were the left-hand part of a pipe,
|
||||
// wait for the right-hand part to finish.
|
||||
if (pipe_child) {
|
||||
if (debug)
|
||||
cprintf("[%08x] WAIT pipe_child %08x\n", thisenv->env_id, pipe_child);
|
||||
wait(pipe_child);
|
||||
if (debug)
|
||||
cprintf("[%08x] wait finished\n", thisenv->env_id);
|
||||
}
|
||||
|
||||
// Done!
|
||||
exit();
|
||||
}
|
||||
|
||||
|
||||
// Get the next token from string s.
|
||||
// Set *p1 to the beginning of the token and *p2 just past the token.
|
||||
// Returns
|
||||
// 0 for end-of-string;
|
||||
// < for <;
|
||||
// > for >;
|
||||
// | for |;
|
||||
// w for a word.
|
||||
//
|
||||
// Eventually (once we parse the space where the \0 will go),
|
||||
// words get nul-terminated.
|
||||
#define WHITESPACE " \t\r\n"
|
||||
#define SYMBOLS "<|>&;()"
|
||||
|
||||
int
|
||||
_gettoken(char *s, char **p1, char **p2)
|
||||
{
|
||||
int t;
|
||||
|
||||
if (s == 0) {
|
||||
if (debug > 1)
|
||||
cprintf("GETTOKEN NULL\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (debug > 1)
|
||||
cprintf("GETTOKEN: %s\n", s);
|
||||
|
||||
*p1 = 0;
|
||||
*p2 = 0;
|
||||
|
||||
while (strchr(WHITESPACE, *s))
|
||||
*s++ = 0;
|
||||
if (*s == 0) {
|
||||
if (debug > 1)
|
||||
cprintf("EOL\n");
|
||||
return 0;
|
||||
}
|
||||
if (strchr(SYMBOLS, *s)) {
|
||||
t = *s;
|
||||
*p1 = s;
|
||||
*s++ = 0;
|
||||
*p2 = s;
|
||||
if (debug > 1)
|
||||
cprintf("TOK %c\n", t);
|
||||
return t;
|
||||
}
|
||||
*p1 = s;
|
||||
while (*s && !strchr(WHITESPACE SYMBOLS, *s))
|
||||
s++;
|
||||
*p2 = s;
|
||||
if (debug > 1) {
|
||||
t = **p2;
|
||||
**p2 = 0;
|
||||
cprintf("WORD: %s\n", *p1);
|
||||
**p2 = t;
|
||||
}
|
||||
return 'w';
|
||||
}
|
||||
|
||||
int
|
||||
gettoken(char *s, char **p1)
|
||||
{
|
||||
static int c, nc;
|
||||
static char* np1, *np2;
|
||||
|
||||
if (s) {
|
||||
nc = _gettoken(s, &np1, &np2);
|
||||
return 0;
|
||||
}
|
||||
c = nc;
|
||||
*p1 = np1;
|
||||
nc = _gettoken(np2, &np1, &np2);
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
usage(void)
|
||||
{
|
||||
cprintf("usage: sh [-dix] [command-file]\n");
|
||||
exit();
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int r, interactive, echocmds;
|
||||
struct Argstate args;
|
||||
|
||||
interactive = '?';
|
||||
echocmds = 0;
|
||||
argstart(&argc, argv, &args);
|
||||
while ((r = argnext(&args)) >= 0)
|
||||
switch (r) {
|
||||
case 'd':
|
||||
debug++;
|
||||
break;
|
||||
case 'i':
|
||||
interactive = 1;
|
||||
break;
|
||||
case 'x':
|
||||
echocmds = 1;
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
}
|
||||
|
||||
if (argc > 2)
|
||||
usage();
|
||||
if (argc == 2) {
|
||||
close(0);
|
||||
if ((r = open(argv[1], O_RDONLY)) < 0)
|
||||
panic("open %s: %e", argv[1], r);
|
||||
assert(r == 0);
|
||||
}
|
||||
if (interactive == '?')
|
||||
interactive = iscons(0);
|
||||
|
||||
while (1) {
|
||||
char *buf;
|
||||
|
||||
buf = readline(interactive ? "$ " : NULL);
|
||||
if (buf == NULL) {
|
||||
if (debug)
|
||||
cprintf("EXITING\n");
|
||||
exit(); // end of file
|
||||
}
|
||||
if (debug)
|
||||
cprintf("LINE: %s\n", buf);
|
||||
if (buf[0] == '#')
|
||||
continue;
|
||||
if (echocmds)
|
||||
printf("# %s\n", buf);
|
||||
if (debug)
|
||||
cprintf("BEFORE FORK\n");
|
||||
if ((r = fork()) < 0)
|
||||
panic("fork: %e", r);
|
||||
if (debug)
|
||||
cprintf("FORK: %d\n", r);
|
||||
if (r == 0) {
|
||||
runcmd(buf);
|
||||
exit();
|
||||
} else
|
||||
wait(r);
|
||||
}
|
||||
}
|
||||
|
||||
10
user/spawnfaultio.c
Normal file
10
user/spawnfaultio.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int r;
|
||||
cprintf("i am parent environment %08x\n", thisenv->env_id);
|
||||
if ((r = spawnl("faultio", "faultio", 0)) < 0)
|
||||
panic("spawn(faultio) failed: %e", r);
|
||||
}
|
||||
10
user/spawnhello.c
Normal file
10
user/spawnhello.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int r;
|
||||
cprintf("i am parent environment %08x\n", thisenv->env_id);
|
||||
if ((r = spawnl("hello", "hello", 0)) < 0)
|
||||
panic("spawn(hello) failed: %e", r);
|
||||
}
|
||||
10
user/spawninit.c
Normal file
10
user/spawninit.c
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int r;
|
||||
cprintf("i am parent environment %08x\n", thisenv->env_id);
|
||||
if ((r = spawnl("init", "init", "one", "two", 0)) < 0)
|
||||
panic("spawnl(init) failed: %e", r);
|
||||
}
|
||||
31
user/spin.c
Normal file
31
user/spin.c
Normal file
@@ -0,0 +1,31 @@
|
||||
// Test preemption by forking off a child process that just spins forever.
|
||||
// Let it run for a couple time slices, then kill it.
|
||||
|
||||
#include <inc/lib.h>
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
envid_t env;
|
||||
|
||||
cprintf("I am the parent. Forking the child...\n");
|
||||
if ((env = fork()) == 0) {
|
||||
cprintf("I am the child. Spinning...\n");
|
||||
while (1)
|
||||
/* do nothing */;
|
||||
}
|
||||
|
||||
cprintf("I am the parent. Running the child...\n");
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
sys_yield();
|
||||
|
||||
cprintf("I am the parent. Killing the child...\n");
|
||||
sys_env_destroy(env);
|
||||
}
|
||||
|
||||
39
user/stresssched.c
Normal file
39
user/stresssched.c
Normal file
@@ -0,0 +1,39 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
volatile int counter;
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int i, j;
|
||||
int seen;
|
||||
envid_t parent = sys_getenvid();
|
||||
|
||||
// Fork several environments
|
||||
for (i = 0; i < 20; i++)
|
||||
if (fork() == 0)
|
||||
break;
|
||||
if (i == 20) {
|
||||
sys_yield();
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the parent to finish forking
|
||||
while (envs[ENVX(parent)].env_status != ENV_FREE)
|
||||
asm volatile("pause");
|
||||
|
||||
// Check that one environment doesn't run on two CPUs at once
|
||||
for (i = 0; i < 10; i++) {
|
||||
sys_yield();
|
||||
for (j = 0; j < 10000; j++)
|
||||
counter++;
|
||||
}
|
||||
|
||||
if (counter != 10*10000)
|
||||
panic("ran on two CPUs at once (counter is %d)", counter);
|
||||
|
||||
// Check that we see environments running on different CPUs
|
||||
cprintf("[%08x] stresssched on CPU %d\n", thisenv->env_id, thisenv->env_cpunum);
|
||||
|
||||
}
|
||||
|
||||
38
user/testfdsharing.c
Normal file
38
user/testfdsharing.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <inc/x86.h>
|
||||
#include <inc/lib.h>
|
||||
|
||||
char buf[512], buf2[512];
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int fd, r, n, n2;
|
||||
|
||||
if ((fd = open("motd", O_RDONLY)) < 0)
|
||||
panic("open motd: %e", fd);
|
||||
seek(fd, 0);
|
||||
if ((n = readn(fd, buf, sizeof buf)) <= 0)
|
||||
panic("readn: %e", n);
|
||||
|
||||
if ((r = fork()) < 0)
|
||||
panic("fork: %e", r);
|
||||
if (r == 0) {
|
||||
seek(fd, 0);
|
||||
cprintf("going to read in child (might page fault if your sharing is buggy)\n");
|
||||
if ((n2 = readn(fd, buf2, sizeof buf2)) != n)
|
||||
panic("read in parent got %d, read in child got %d", n, n2);
|
||||
if (memcmp(buf, buf2, n) != 0)
|
||||
panic("read in parent got different bytes from read in child");
|
||||
cprintf("read in child succeeded\n");
|
||||
seek(fd, 0);
|
||||
close(fd);
|
||||
exit();
|
||||
}
|
||||
wait(r);
|
||||
if ((n2 = readn(fd, buf2, sizeof buf2)) != n)
|
||||
panic("read in parent got %d, then got %d", n, n2);
|
||||
cprintf("read in parent succeeded\n");
|
||||
close(fd);
|
||||
|
||||
breakpoint();
|
||||
}
|
||||
128
user/testfile.c
Normal file
128
user/testfile.c
Normal file
@@ -0,0 +1,128 @@
|
||||
#include <inc/lib.h>
|
||||
|
||||
const char *msg = "This is the NEW message of the day!\n\n";
|
||||
|
||||
#define FVA ((struct Fd*)0xCCCCC000)
|
||||
|
||||
static int
|
||||
xopen(const char *path, int mode)
|
||||
{
|
||||
extern union Fsipc fsipcbuf;
|
||||
envid_t fsenv;
|
||||
|
||||
strcpy(fsipcbuf.open.req_path, path);
|
||||
fsipcbuf.open.req_omode = mode;
|
||||
|
||||
fsenv = ipc_find_env(ENV_TYPE_FS);
|
||||
ipc_send(fsenv, FSREQ_OPEN, &fsipcbuf, PTE_P | PTE_W | PTE_U);
|
||||
return ipc_recv(NULL, FVA, NULL);
|
||||
}
|
||||
|
||||
void
|
||||
umain(int argc, char **argv)
|
||||
{
|
||||
int r, f, i;
|
||||
struct Fd *fd;
|
||||
struct Fd fdcopy;
|
||||
struct Stat st;
|
||||
char buf[512];
|
||||
|
||||
// We open files manually first, to avoid the FD layer
|
||||
if ((r = xopen("/not-found", O_RDONLY)) < 0 && r != -E_NOT_FOUND)
|
||||
panic("serve_open /not-found: %e", r);
|
||||
else if (r >= 0)
|
||||
panic("serve_open /not-found succeeded!");
|
||||
|
||||
if ((r = xopen("/newmotd", O_RDONLY)) < 0)
|
||||
panic("serve_open /newmotd: %e", r);
|
||||
if (FVA->fd_dev_id != 'f' || FVA->fd_offset != 0 || FVA->fd_omode != O_RDONLY)
|
||||
panic("serve_open did not fill struct Fd correctly\n");
|
||||
cprintf("serve_open is good\n");
|
||||
|
||||
if ((r = devfile.dev_stat(FVA, &st)) < 0)
|
||||
panic("file_stat: %e", r);
|
||||
if (strlen(msg) != st.st_size)
|
||||
panic("file_stat returned size %d wanted %d\n", st.st_size, strlen(msg));
|
||||
cprintf("file_stat is good\n");
|
||||
|
||||
memset(buf, 0, sizeof buf);
|
||||
if ((r = devfile.dev_read(FVA, buf, sizeof buf)) < 0)
|
||||
panic("file_read: %e", r);
|
||||
if (strcmp(buf, msg) != 0)
|
||||
panic("file_read returned wrong data");
|
||||
cprintf("file_read is good\n");
|
||||
|
||||
if ((r = devfile.dev_close(FVA)) < 0)
|
||||
panic("file_close: %e", r);
|
||||
cprintf("file_close is good\n");
|
||||
|
||||
// We're about to unmap the FD, but still need a way to get
|
||||
// the stale filenum to serve_read, so we make a local copy.
|
||||
// The file server won't think it's stale until we unmap the
|
||||
// FD page.
|
||||
fdcopy = *FVA;
|
||||
sys_page_unmap(0, FVA);
|
||||
|
||||
if ((r = devfile.dev_read(&fdcopy, buf, sizeof buf)) != -E_INVAL)
|
||||
panic("serve_read does not handle stale fileids correctly: %e", r);
|
||||
cprintf("stale fileid is good\n");
|
||||
|
||||
// Try writing
|
||||
if ((r = xopen("/new-file", O_RDWR|O_CREAT)) < 0)
|
||||
panic("serve_open /new-file: %e", r);
|
||||
|
||||
if ((r = devfile.dev_write(FVA, msg, strlen(msg))) != strlen(msg))
|
||||
panic("file_write: %e", r);
|
||||
cprintf("file_write is good\n");
|
||||
|
||||
FVA->fd_offset = 0;
|
||||
memset(buf, 0, sizeof buf);
|
||||
if ((r = devfile.dev_read(FVA, buf, sizeof buf)) < 0)
|
||||
panic("file_read after file_write: %e", r);
|
||||
if (r != strlen(msg))
|
||||
panic("file_read after file_write returned wrong length: %d", r);
|
||||
if (strcmp(buf, msg) != 0)
|
||||
panic("file_read after file_write returned wrong data");
|
||||
cprintf("file_read after file_write is good\n");
|
||||
|
||||
// Now we'll try out open
|
||||
if ((r = open("/not-found", O_RDONLY)) < 0 && r != -E_NOT_FOUND)
|
||||
panic("open /not-found: %e", r);
|
||||
else if (r >= 0)
|
||||
panic("open /not-found succeeded!");
|
||||
|
||||
if ((r = open("/newmotd", O_RDONLY)) < 0)
|
||||
panic("open /newmotd: %e", r);
|
||||
fd = (struct Fd*) (0xD0000000 + r*PGSIZE);
|
||||
if (fd->fd_dev_id != 'f' || fd->fd_offset != 0 || fd->fd_omode != O_RDONLY)
|
||||
panic("open did not fill struct Fd correctly\n");
|
||||
cprintf("open is good\n");
|
||||
|
||||
// Try files with indirect blocks
|
||||
if ((f = open("/big", O_WRONLY|O_CREAT)) < 0)
|
||||
panic("creat /big: %e", f);
|
||||
memset(buf, 0, sizeof(buf));
|
||||
for (i = 0; i < (NDIRECT*3)*BLKSIZE; i += sizeof(buf)) {
|
||||
*(int*)buf = i;
|
||||
if ((r = write(f, buf, sizeof(buf))) < 0)
|
||||
panic("write /big@%d: %e", i, r);
|
||||
}
|
||||
close(f);
|
||||
|
||||
if ((f = open("/big", O_RDONLY)) < 0)
|
||||
panic("open /big: %e", f);
|
||||
for (i = 0; i < (NDIRECT*3)*BLKSIZE; i += sizeof(buf)) {
|
||||
*(int*)buf = i;
|
||||
if ((r = readn(f, buf, sizeof(buf))) < 0)
|
||||
panic("read /big@%d: %e", i, r);
|
||||
if (r != sizeof(buf))
|
||||
panic("read /big from %d returned %d < %d bytes",
|
||||
i, r, sizeof(buf));
|
||||
if (*(int*)buf != i)
|
||||
panic("read /big from %d returned bad data %d",
|
||||
i, *(int*)buf);
|
||||
}
|
||||
close(f);
|
||||
cprintf("large file is good\n");
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user