diff --git a/1/xv6-riscv-riscv.zip b/1/xv6-riscv-riscv.zip new file mode 100644 index 0000000000000000000000000000000000000000..abdd88b4aafd755850169de4b30a440f9eba6026 Binary files /dev/null and b/1/xv6-riscv-riscv.zip differ diff --git a/1/xv6-riscv-riscv/.dir-locals.el b/1/xv6-riscv-riscv/.dir-locals.el new file mode 100644 index 0000000000000000000000000000000000000000..da72247468dd5f76ca87363995ec75b9c5977d73 --- /dev/null +++ b/1/xv6-riscv-riscv/.dir-locals.el @@ -0,0 +1,4 @@ +((c-mode + (indent-tabs-mode . nil) + (c-file-style . "bsd") + (c-basic-offset . 2))) diff --git a/1/xv6-riscv-riscv/.editorconfig b/1/xv6-riscv-riscv/.editorconfig new file mode 100644 index 0000000000000000000000000000000000000000..c47611efeabeba18e79604bdf82c086374657f7a --- /dev/null +++ b/1/xv6-riscv-riscv/.editorconfig @@ -0,0 +1,22 @@ +; https://editorconfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 + +[*.{c,h}] +indent_size = 2 + +[*.S] +indent_size = 8 + +[*.ld] +indent_size = 2 + +[Makefile] +indent_style = tab +indent_size = 8 diff --git a/1/xv6-riscv-riscv/.gdbinit.tmpl-riscv b/1/xv6-riscv-riscv/.gdbinit.tmpl-riscv new file mode 100644 index 0000000000000000000000000000000000000000..a2bfde3cfc66b3fc60af9c6c2010473fd9a7fcf9 --- /dev/null +++ b/1/xv6-riscv-riscv/.gdbinit.tmpl-riscv @@ -0,0 +1,6 @@ +set confirm off +set architecture riscv:rv64 +target remote 127.0.0.1:1234 +symbol-file kernel/kernel +set disassemble-next-line auto +set riscv use-compressed-breakpoints yes diff --git a/1/xv6-riscv-riscv/.gitignore b/1/xv6-riscv-riscv/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..07216f3ad0a9625c54f24d990f00719a93605aac --- /dev/null +++ b/1/xv6-riscv-riscv/.gitignore @@ -0,0 +1,17 @@ +*~ +_* +*.o +*.d +*.asm +*.sym +*.img +vectors.S +bootblock +entryother +initcode +initcode.out +kernelmemfs +mkfs +kernel/kernel +user/usys.S +.gdbinit diff --git a/1/xv6-riscv-riscv/LICENSE b/1/xv6-riscv-riscv/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..af50cb2508331d748e7fa5340ec882720f24f1af --- /dev/null +++ b/1/xv6-riscv-riscv/LICENSE @@ -0,0 +1,24 @@ +The xv6 software is: + +Copyright (c) 2006-2024 Frans Kaashoek, Robert Morris, Russ Cox, + Massachusetts Institute of Technology + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/1/xv6-riscv-riscv/Makefile b/1/xv6-riscv-riscv/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..f8c820ebd63eb8805c7cb8e164a4190fd8a88968 --- /dev/null +++ b/1/xv6-riscv-riscv/Makefile @@ -0,0 +1,180 @@ +K=kernel +U=user + +OBJS = \ + $K/entry.o \ + $K/start.o \ + $K/console.o \ + $K/printf.o \ + $K/uart.o \ + $K/kalloc.o \ + $K/spinlock.o \ + $K/string.o \ + $K/main.o \ + $K/vm.o \ + $K/proc.o \ + $K/swtch.o \ + $K/trampoline.o \ + $K/trap.o \ + $K/syscall.o \ + $K/sysproc.o \ + $K/bio.o \ + $K/fs.o \ + $K/log.o \ + $K/sleeplock.o \ + $K/file.o \ + $K/pipe.o \ + $K/exec.o \ + $K/sysfile.o \ + $K/kernelvec.o \ + $K/plic.o \ + $K/virtio_disk.o + +# riscv64-unknown-elf- or riscv64-linux-gnu- +# perhaps in /opt/riscv/bin +#TOOLPREFIX = + +# Try to infer the correct TOOLPREFIX if not set +ifndef TOOLPREFIX +TOOLPREFIX := $(shell if riscv64-unknown-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \ + then echo 'riscv64-unknown-elf-'; \ + elif riscv64-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \ + then echo 'riscv64-linux-gnu-'; \ + elif riscv64-unknown-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \ + then echo 'riscv64-unknown-linux-gnu-'; \ + else echo "***" 1>&2; \ + echo "*** Error: Couldn't find a riscv64 version of GCC/binutils." 1>&2; \ + echo "*** To turn off this error, run 'gmake TOOLPREFIX= ...'." 1>&2; \ + echo "***" 1>&2; exit 1; fi) +endif + +QEMU = qemu-system-riscv64 + +CC = $(TOOLPREFIX)gcc +AS = $(TOOLPREFIX)gas +LD = $(TOOLPREFIX)ld +OBJCOPY = $(TOOLPREFIX)objcopy +OBJDUMP = $(TOOLPREFIX)objdump + +CFLAGS = -Wall -Werror -O -fno-omit-frame-pointer -ggdb -gdwarf-2 +CFLAGS += -MD +CFLAGS += -mcmodel=medany +# CFLAGS += -ffreestanding -fno-common -nostdlib -mno-relax +CFLAGS += -fno-common -nostdlib +CFLAGS += -fno-builtin-strncpy -fno-builtin-strncmp -fno-builtin-strlen -fno-builtin-memset +CFLAGS += -fno-builtin-memmove -fno-builtin-memcmp -fno-builtin-log -fno-builtin-bzero +CFLAGS += -fno-builtin-strchr -fno-builtin-exit -fno-builtin-malloc -fno-builtin-putc +CFLAGS += -fno-builtin-free +CFLAGS += -fno-builtin-memcpy -Wno-main +CFLAGS += -fno-builtin-printf -fno-builtin-fprintf -fno-builtin-vprintf +CFLAGS += -I. +CFLAGS += $(shell $(CC) -fno-stack-protector -E -x c /dev/null >/dev/null 2>&1 && echo -fno-stack-protector) + +# Disable PIE when possible (for Ubuntu 16.10 toolchain) +ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]no-pie'),) +CFLAGS += -fno-pie -no-pie +endif +ifneq ($(shell $(CC) -dumpspecs 2>/dev/null | grep -e '[^f]nopie'),) +CFLAGS += -fno-pie -nopie +endif + +LDFLAGS = -z max-page-size=4096 + +$K/kernel: $(OBJS) $K/kernel.ld $U/initcode + $(LD) $(LDFLAGS) -T $K/kernel.ld -o $K/kernel $(OBJS) + $(OBJDUMP) -S $K/kernel > $K/kernel.asm + $(OBJDUMP) -t $K/kernel | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $K/kernel.sym + +$U/initcode: $U/initcode.S + $(CC) $(CFLAGS) -march=rv64g -nostdinc -I. -Ikernel -c $U/initcode.S -o $U/initcode.o + $(LD) $(LDFLAGS) -N -e start -Ttext 0 -o $U/initcode.out $U/initcode.o + $(OBJCOPY) -S -O binary $U/initcode.out $U/initcode + $(OBJDUMP) -S $U/initcode.o > $U/initcode.asm + +tags: $(OBJS) _init + etags *.S *.c + +ULIB = $U/ulib.o $U/usys.o $U/printf.o $U/umalloc.o + +_%: %.o $(ULIB) + $(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^ + $(OBJDUMP) -S $@ > $*.asm + $(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $*.sym + +$U/usys.S : $U/usys.pl + perl $U/usys.pl > $U/usys.S + +$U/usys.o : $U/usys.S + $(CC) $(CFLAGS) -c -o $U/usys.o $U/usys.S + +$U/_forktest: $U/forktest.o $(ULIB) + # forktest has less library code linked in - needs to be small + # in order to be able to max out the proc table. + $(LD) $(LDFLAGS) -N -e main -Ttext 0 -o $U/_forktest $U/forktest.o $U/ulib.o $U/usys.o + $(OBJDUMP) -S $U/_forktest > $U/forktest.asm + +mkfs/mkfs: mkfs/mkfs.c $K/fs.h $K/param.h + gcc -Werror -Wall -I. -o mkfs/mkfs mkfs/mkfs.c + +# Prevent deletion of intermediate files, e.g. cat.o, after first build, so +# that disk image changes after first build are persistent until clean. More +# details: +# http://www.gnu.org/software/make/manual/html_node/Chained-Rules.html +.PRECIOUS: %.o + +UPROGS=\ + $U/_cat\ + $U/_echo\ + $U/_forktest\ + $U/_grep\ + $U/_init\ + $U/_kill\ + $U/_ln\ + $U/_ls\ + $U/_mkdir\ + $U/_rm\ + $U/_sh\ + $U/_stressfs\ + $U/_usertests\ + $U/_grind\ + $U/_wc\ + $U/_zombie\ + +fs.img: mkfs/mkfs README $(UPROGS) + mkfs/mkfs fs.img README $(UPROGS) + +-include kernel/*.d user/*.d + +clean: + rm -f *.tex *.dvi *.idx *.aux *.log *.ind *.ilg \ + */*.o */*.d */*.asm */*.sym \ + $U/initcode $U/initcode.out $K/kernel fs.img \ + mkfs/mkfs .gdbinit \ + $U/usys.S \ + $(UPROGS) + +# try to generate a unique GDB port +GDBPORT = $(shell expr `id -u` % 5000 + 25000) +# QEMU's gdb stub command line changed in 0.11 +QEMUGDB = $(shell if $(QEMU) -help | grep -q '^-gdb'; \ + then echo "-gdb tcp::$(GDBPORT)"; \ + else echo "-s -p $(GDBPORT)"; fi) +ifndef CPUS +CPUS := 3 +endif + +QEMUOPTS = -machine virt -bios none -kernel $K/kernel -m 128M -smp $(CPUS) -nographic +QEMUOPTS += -global virtio-mmio.force-legacy=false +QEMUOPTS += -drive file=fs.img,if=none,format=raw,id=x0 +QEMUOPTS += -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 + +qemu: $K/kernel fs.img + $(QEMU) $(QEMUOPTS) + +.gdbinit: .gdbinit.tmpl-riscv + sed "s/:1234/:$(GDBPORT)/" < $^ > $@ + +qemu-gdb: $K/kernel .gdbinit fs.img + @echo "*** Now run 'gdb' in another window." 1>&2 + $(QEMU) $(QEMUOPTS) -S $(QEMUGDB) + diff --git a/1/xv6-riscv-riscv/README b/1/xv6-riscv-riscv/README new file mode 100644 index 0000000000000000000000000000000000000000..f583201a604de451b3f4ec6f395b53817d92cbaf --- /dev/null +++ b/1/xv6-riscv-riscv/README @@ -0,0 +1,46 @@ +xv6 is a re-implementation of Dennis Ritchie's and Ken Thompson's Unix +Version 6 (v6). xv6 loosely follows the structure and style of v6, +but is implemented for a modern RISC-V multiprocessor using ANSI C. + +ACKNOWLEDGMENTS + +xv6 is inspired by John Lions's Commentary on UNIX 6th Edition (Peer +to Peer Communications; ISBN: 1-57398-013-7; 1st edition (June 14, +2000)). See also https://pdos.csail.mit.edu/6.1810/, which provides +pointers to on-line resources for v6. + +The following people have made contributions: Russ Cox (context switching, +locking), Cliff Frey (MP), Xiao Yu (MP), Nickolai Zeldovich, and Austin +Clements. + +We are also grateful for the bug reports and patches contributed by +Takahiro Aoyagi, Marcelo Arroyo, Silas Boyd-Wickizer, Anton Burtsev, +carlclone, Ian Chen, Dan Cross, Cody Cutler, Mike CAT, Tej Chajed, +Asami Doi,Wenyang Duan, eyalz800, Nelson Elhage, Saar Ettinger, Alice +Ferrazzi, Nathaniel Filardo, flespark, Peter Froehlich, Yakir Goaron, +Shivam Handa, Matt Harvey, Bryan Henry, jaichenhengjie, Jim Huang, +Matúš Jókay, John Jolly, Alexander Kapshuk, Anders Kaseorg, kehao95, +Wolfgang Keller, Jungwoo Kim, Jonathan Kimmitt, Eddie Kohler, Vadim +Kolontsov, Austin Liew, l0stman, Pavan Maddamsetti, Imbar Marinescu, +Yandong Mao, Matan Shabtay, Hitoshi Mitake, Carmi Merimovich, Mark +Morrissey, mtasm, Joel Nider, Hayato Ohhashi, OptimisticSide, +phosphagos, Harry Porter, Greg Price, RayAndrew, Jude Rich, segfault, +Ayan Shafqat, Eldar Sehayek, Yongming Shen, Fumiya Shigemitsu, snoire, +Taojie, Cam Tenny, tyfkda, Warren Toomey, Stephen Tu, Alissa Tung, +Rafael Ubal, Amane Uehara, Pablo Ventura, Xi Wang, WaheedHafez, +Keiichi Watanabe, Lucas Wolf, Nicolas Wolovick, wxdao, Grant Wu, x653, +Jindong Zhang, Icenowy Zheng, ZhUyU1997, and Zou Chang Wei. + +ERROR REPORTS + +Please send errors and suggestions to Frans Kaashoek and Robert Morris +(kaashoek,rtm@mit.edu). The main purpose of xv6 is as a teaching +operating system for MIT's 6.1810, so we are more interested in +simplifications and clarifications than new features. + +BUILDING AND RUNNING XV6 + +You will need a RISC-V "newlib" tool chain from +https://github.com/riscv/riscv-gnu-toolchain, and qemu compiled for +riscv64-softmmu. Once they are installed, and in your shell +search path, you can run "make qemu". diff --git a/1/xv6-riscv-riscv/kernel/bio.c b/1/xv6-riscv-riscv/kernel/bio.c new file mode 100644 index 0000000000000000000000000000000000000000..60d91a6d74ca9f57f5920ec8d4f93591ab330b82 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/bio.c @@ -0,0 +1,153 @@ +// Buffer cache. +// +// The buffer cache is a linked list of buf structures holding +// cached copies of disk block contents. Caching disk blocks +// in memory reduces the number of disk reads and also provides +// a synchronization point for disk blocks used by multiple processes. +// +// Interface: +// * To get a buffer for a particular disk block, call bread. +// * After changing buffer data, call bwrite to write it to disk. +// * When done with the buffer, call brelse. +// * Do not use the buffer after calling brelse. +// * Only one process at a time can use a buffer, +// so do not keep them longer than necessary. + + +#include "types.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "riscv.h" +#include "defs.h" +#include "fs.h" +#include "buf.h" + +struct { + struct spinlock lock; + struct buf buf[NBUF]; + + // Linked list of all buffers, through prev/next. + // Sorted by how recently the buffer was used. + // head.next is most recent, head.prev is least. + struct buf head; +} bcache; + +void +binit(void) +{ + struct buf *b; + + initlock(&bcache.lock, "bcache"); + + // Create linked list of buffers + bcache.head.prev = &bcache.head; + bcache.head.next = &bcache.head; + for(b = bcache.buf; b < bcache.buf+NBUF; b++){ + b->next = bcache.head.next; + b->prev = &bcache.head; + initsleeplock(&b->lock, "buffer"); + bcache.head.next->prev = b; + bcache.head.next = b; + } +} + +// Look through buffer cache for block on device dev. +// If not found, allocate a buffer. +// In either case, return locked buffer. +static struct buf* +bget(uint dev, uint blockno) +{ + struct buf *b; + + acquire(&bcache.lock); + + // Is the block already cached? + for(b = bcache.head.next; b != &bcache.head; b = b->next){ + if(b->dev == dev && b->blockno == blockno){ + b->refcnt++; + release(&bcache.lock); + acquiresleep(&b->lock); + return b; + } + } + + // Not cached. + // Recycle the least recently used (LRU) unused buffer. + for(b = bcache.head.prev; b != &bcache.head; b = b->prev){ + if(b->refcnt == 0) { + b->dev = dev; + b->blockno = blockno; + b->valid = 0; + b->refcnt = 1; + release(&bcache.lock); + acquiresleep(&b->lock); + return b; + } + } + panic("bget: no buffers"); +} + +// Return a locked buf with the contents of the indicated block. +struct buf* +bread(uint dev, uint blockno) +{ + struct buf *b; + + b = bget(dev, blockno); + if(!b->valid) { + virtio_disk_rw(b, 0); + b->valid = 1; + } + return b; +} + +// Write b's contents to disk. Must be locked. +void +bwrite(struct buf *b) +{ + if(!holdingsleep(&b->lock)) + panic("bwrite"); + virtio_disk_rw(b, 1); +} + +// Release a locked buffer. +// Move to the head of the most-recently-used list. +void +brelse(struct buf *b) +{ + if(!holdingsleep(&b->lock)) + panic("brelse"); + + releasesleep(&b->lock); + + acquire(&bcache.lock); + b->refcnt--; + if (b->refcnt == 0) { + // no one is waiting for it. + b->next->prev = b->prev; + b->prev->next = b->next; + b->next = bcache.head.next; + b->prev = &bcache.head; + bcache.head.next->prev = b; + bcache.head.next = b; + } + + release(&bcache.lock); +} + +void +bpin(struct buf *b) { + acquire(&bcache.lock); + b->refcnt++; + release(&bcache.lock); +} + +void +bunpin(struct buf *b) { + acquire(&bcache.lock); + b->refcnt--; + release(&bcache.lock); +} + + diff --git a/1/xv6-riscv-riscv/kernel/buf.h b/1/xv6-riscv-riscv/kernel/buf.h new file mode 100644 index 0000000000000000000000000000000000000000..4616e9ecc92a57215daf1f5f118e0e759effeb33 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/buf.h @@ -0,0 +1,12 @@ +struct buf { + int valid; // has data been read from disk? + int disk; // does disk "own" buf? + uint dev; + uint blockno; + struct sleeplock lock; + uint refcnt; + struct buf *prev; // LRU cache list + struct buf *next; + uchar data[BSIZE]; +}; + diff --git a/1/xv6-riscv-riscv/kernel/console.c b/1/xv6-riscv-riscv/kernel/console.c new file mode 100644 index 0000000000000000000000000000000000000000..05dc5260dbc422b43281cccffa79b7cd5768db7d --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/console.c @@ -0,0 +1,192 @@ +// +// Console input and output, to the uart. +// Reads are line at a time. +// Implements special input characters: +// newline -- end of line +// control-h -- backspace +// control-u -- kill line +// control-d -- end of file +// control-p -- print process list +// + +#include <stdarg.h> + +#include "types.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "memlayout.h" +#include "riscv.h" +#include "defs.h" +#include "proc.h" + +#define BACKSPACE 0x100 +#define C(x) ((x)-'@') // Control-x + +// +// send one character to the uart. +// called by printf(), and to echo input characters, +// but not from write(). +// +void +consputc(int c) +{ + if(c == BACKSPACE){ + // if the user typed backspace, overwrite with a space. + uartputc_sync('\b'); uartputc_sync(' '); uartputc_sync('\b'); + } else { + uartputc_sync(c); + } +} + +struct { + struct spinlock lock; + + // input +#define INPUT_BUF_SIZE 128 + char buf[INPUT_BUF_SIZE]; + uint r; // Read index + uint w; // Write index + uint e; // Edit index +} cons; + +// +// user write()s to the console go here. +// +int +consolewrite(int user_src, uint64 src, int n) +{ + int i; + + for(i = 0; i < n; i++){ + char c; + if(either_copyin(&c, user_src, src+i, 1) == -1) + break; + uartputc(c); + } + + return i; +} + +// +// user read()s from the console go here. +// copy (up to) a whole input line to dst. +// user_dist indicates whether dst is a user +// or kernel address. +// +int +consoleread(int user_dst, uint64 dst, int n) +{ + uint target; + int c; + char cbuf; + + target = n; + acquire(&cons.lock); + while(n > 0){ + // wait until interrupt handler has put some + // input into cons.buffer. + while(cons.r == cons.w){ + if(killed(myproc())){ + release(&cons.lock); + return -1; + } + sleep(&cons.r, &cons.lock); + } + + c = cons.buf[cons.r++ % INPUT_BUF_SIZE]; + + if(c == C('D')){ // end-of-file + if(n < target){ + // Save ^D for next time, to make sure + // caller gets a 0-byte result. + cons.r--; + } + break; + } + + // copy the input byte to the user-space buffer. + cbuf = c; + if(either_copyout(user_dst, dst, &cbuf, 1) == -1) + break; + + dst++; + --n; + + if(c == '\n'){ + // a whole line has arrived, return to + // the user-level read(). + break; + } + } + release(&cons.lock); + + return target - n; +} + +// +// the console input interrupt handler. +// uartintr() calls this for input character. +// do erase/kill processing, append to cons.buf, +// wake up consoleread() if a whole line has arrived. +// +void +consoleintr(int c) +{ + acquire(&cons.lock); + + switch(c){ + case C('P'): // Print process list. + procdump(); + break; + case C('U'): // Kill line. + while(cons.e != cons.w && + cons.buf[(cons.e-1) % INPUT_BUF_SIZE] != '\n'){ + cons.e--; + consputc(BACKSPACE); + } + break; + case C('H'): // Backspace + case '\x7f': // Delete key + if(cons.e != cons.w){ + cons.e--; + consputc(BACKSPACE); + } + break; + default: + if(c != 0 && cons.e-cons.r < INPUT_BUF_SIZE){ + c = (c == '\r') ? '\n' : c; + + // echo back to the user. + consputc(c); + + // store for consumption by consoleread(). + cons.buf[cons.e++ % INPUT_BUF_SIZE] = c; + + if(c == '\n' || c == C('D') || cons.e-cons.r == INPUT_BUF_SIZE){ + // wake up consoleread() if a whole line (or end-of-file) + // has arrived. + cons.w = cons.e; + wakeup(&cons.r); + } + } + break; + } + + release(&cons.lock); +} + +void +consoleinit(void) +{ + initlock(&cons.lock, "cons"); + + uartinit(); + + // connect read and write system calls + // to consoleread and consolewrite. + devsw[CONSOLE].read = consoleread; + devsw[CONSOLE].write = consolewrite; +} diff --git a/1/xv6-riscv-riscv/kernel/defs.h b/1/xv6-riscv-riscv/kernel/defs.h new file mode 100644 index 0000000000000000000000000000000000000000..d1b6bb904946c7186a715f4abd06c6aa05996c5d --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/defs.h @@ -0,0 +1,189 @@ +struct buf; +struct context; +struct file; +struct inode; +struct pipe; +struct proc; +struct spinlock; +struct sleeplock; +struct stat; +struct superblock; + +// bio.c +void binit(void); +struct buf* bread(uint, uint); +void brelse(struct buf*); +void bwrite(struct buf*); +void bpin(struct buf*); +void bunpin(struct buf*); + +// console.c +void consoleinit(void); +void consoleintr(int); +void consputc(int); + +// exec.c +int exec(char*, char**); + +// file.c +struct file* filealloc(void); +void fileclose(struct file*); +struct file* filedup(struct file*); +void fileinit(void); +int fileread(struct file*, uint64, int n); +int filestat(struct file*, uint64 addr); +int filewrite(struct file*, uint64, int n); + +// fs.c +void fsinit(int); +int dirlink(struct inode*, char*, uint); +struct inode* dirlookup(struct inode*, char*, uint*); +struct inode* ialloc(uint, short); +struct inode* idup(struct inode*); +void iinit(); +void ilock(struct inode*); +void iput(struct inode*); +void iunlock(struct inode*); +void iunlockput(struct inode*); +void iupdate(struct inode*); +int namecmp(const char*, const char*); +struct inode* namei(char*); +struct inode* nameiparent(char*, char*); +int readi(struct inode*, int, uint64, uint, uint); +void stati(struct inode*, struct stat*); +int writei(struct inode*, int, uint64, uint, uint); +void itrunc(struct inode*); + +// ramdisk.c +void ramdiskinit(void); +void ramdiskintr(void); +void ramdiskrw(struct buf*); + +// kalloc.c +void* kalloc(void); +void kfree(void *); +void kinit(void); + +// log.c +void initlog(int, struct superblock*); +void log_write(struct buf*); +void begin_op(void); +void end_op(void); + +// pipe.c +int pipealloc(struct file**, struct file**); +void pipeclose(struct pipe*, int); +int piperead(struct pipe*, uint64, int); +int pipewrite(struct pipe*, uint64, int); + +// printf.c +int printf(char*, ...) __attribute__ ((format (printf, 1, 2))); +void panic(char*) __attribute__((noreturn)); +void printfinit(void); + +// proc.c +int cpuid(void); +void exit(int); +int fork(void); +int growproc(int); +void proc_mapstacks(pagetable_t); +pagetable_t proc_pagetable(struct proc *); +void proc_freepagetable(pagetable_t, uint64); +int kill(int); +int killed(struct proc*); +void setkilled(struct proc*); +struct cpu* mycpu(void); +struct cpu* getmycpu(void); +struct proc* myproc(); +void procinit(void); +void scheduler(void) __attribute__((noreturn)); +void sched(void); +void sleep(void*, struct spinlock*); +void userinit(void); +int wait(uint64); +void wakeup(void*); +void yield(void); +int either_copyout(int user_dst, uint64 dst, void *src, uint64 len); +int either_copyin(void *dst, int user_src, uint64 src, uint64 len); +void procdump(void); + +// swtch.S +void swtch(struct context*, struct context*); + +// spinlock.c +void acquire(struct spinlock*); +int holding(struct spinlock*); +void initlock(struct spinlock*, char*); +void release(struct spinlock*); +void push_off(void); +void pop_off(void); + +// sleeplock.c +void acquiresleep(struct sleeplock*); +void releasesleep(struct sleeplock*); +int holdingsleep(struct sleeplock*); +void initsleeplock(struct sleeplock*, char*); + +// string.c +int memcmp(const void*, const void*, uint); +void* memmove(void*, const void*, uint); +void* memset(void*, int, uint); +char* safestrcpy(char*, const char*, int); +int strlen(const char*); +int strncmp(const char*, const char*, uint); +char* strncpy(char*, const char*, int); + +// syscall.c +void argint(int, int*); +int argstr(int, char*, int); +void argaddr(int, uint64 *); +int fetchstr(uint64, char*, int); +int fetchaddr(uint64, uint64*); +void syscall(); + +// trap.c +extern uint ticks; +void trapinit(void); +void trapinithart(void); +extern struct spinlock tickslock; +void usertrapret(void); + +// uart.c +void uartinit(void); +void uartintr(void); +void uartputc(int); +void uartputc_sync(int); +int uartgetc(void); + +// vm.c +void kvminit(void); +void kvminithart(void); +void kvmmap(pagetable_t, uint64, uint64, uint64, int); +int mappages(pagetable_t, uint64, uint64, uint64, int); +pagetable_t uvmcreate(void); +void uvmfirst(pagetable_t, uchar *, uint); +uint64 uvmalloc(pagetable_t, uint64, uint64, int); +uint64 uvmdealloc(pagetable_t, uint64, uint64); +int uvmcopy(pagetable_t, pagetable_t, uint64); +void uvmfree(pagetable_t, uint64); +void uvmunmap(pagetable_t, uint64, uint64, int); +void uvmclear(pagetable_t, uint64); +pte_t * walk(pagetable_t, uint64, int); +uint64 walkaddr(pagetable_t, uint64); +int copyout(pagetable_t, uint64, char *, uint64); +int copyin(pagetable_t, char *, uint64, uint64); +int copyinstr(pagetable_t, char *, uint64, uint64); + +// plic.c +void plicinit(void); +void plicinithart(void); +int plic_claim(void); +void plic_complete(int); + +// virtio_disk.c +void virtio_disk_init(void); +void virtio_disk_rw(struct buf *, int); +void virtio_disk_intr(void); + +// number of elements in fixed-size array +#define NELEM(x) (sizeof(x)/sizeof((x)[0])) diff --git a/1/xv6-riscv-riscv/kernel/elf.h b/1/xv6-riscv-riscv/kernel/elf.h new file mode 100644 index 0000000000000000000000000000000000000000..84555fafd7481b3cf2e7cfe6d8daf9e9cb63c8c8 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/elf.h @@ -0,0 +1,42 @@ +// Format of an ELF executable file + +#define ELF_MAGIC 0x464C457FU // "\x7FELF" in little endian + +// File header +struct elfhdr { + uint magic; // must equal ELF_MAGIC + uchar elf[12]; + ushort type; + ushort machine; + uint version; + uint64 entry; + uint64 phoff; + uint64 shoff; + uint flags; + ushort ehsize; + ushort phentsize; + ushort phnum; + ushort shentsize; + ushort shnum; + ushort shstrndx; +}; + +// Program section header +struct proghdr { + uint32 type; + uint32 flags; + uint64 off; + uint64 vaddr; + uint64 paddr; + uint64 filesz; + uint64 memsz; + uint64 align; +}; + +// Values for Proghdr type +#define ELF_PROG_LOAD 1 + +// Flag bits for Proghdr flags +#define ELF_PROG_FLAG_EXEC 1 +#define ELF_PROG_FLAG_WRITE 2 +#define ELF_PROG_FLAG_READ 4 diff --git a/1/xv6-riscv-riscv/kernel/entry.S b/1/xv6-riscv-riscv/kernel/entry.S new file mode 100644 index 0000000000000000000000000000000000000000..5ab365eb3a67c3917e9ef5fa11eca645c30b7ff2 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/entry.S @@ -0,0 +1,21 @@ + # qemu -kernel loads the kernel at 0x80000000 + # and causes each hart (i.e. CPU) to jump there. + # kernel.ld causes the following code to + # be placed at 0x80000000. +.section .text +.global _entry +_entry: + # set up a stack for C. + # stack0 is declared in start.c, + # with a 4096-byte stack per CPU. + # sp = stack0 + (hartid * 4096) + la sp, stack0 + li a0, 1024*4 + csrr a1, mhartid + addi a1, a1, 1 + mul a0, a0, a1 + add sp, sp, a0 + # jump to start() in start.c + call start +spin: + j spin diff --git a/1/xv6-riscv-riscv/kernel/exec.c b/1/xv6-riscv-riscv/kernel/exec.c new file mode 100644 index 0000000000000000000000000000000000000000..6d7c452fef9631f57cc78fa1f0e78c395e5952e4 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/exec.c @@ -0,0 +1,166 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" +#include "elf.h" + +static int loadseg(pde_t *, uint64, struct inode *, uint, uint); + +int flags2perm(int flags) +{ + int perm = 0; + if(flags & 0x1) + perm = PTE_X; + if(flags & 0x2) + perm |= PTE_W; + return perm; +} + +int +exec(char *path, char **argv) +{ + char *s, *last; + int i, off; + uint64 argc, sz = 0, sp, ustack[MAXARG], stackbase; + struct elfhdr elf; + struct inode *ip; + struct proghdr ph; + pagetable_t pagetable = 0, oldpagetable; + struct proc *p = myproc(); + + begin_op(); + + if((ip = namei(path)) == 0){ + end_op(); + return -1; + } + ilock(ip); + + // Check ELF header + if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf)) + goto bad; + + if(elf.magic != ELF_MAGIC) + goto bad; + + if((pagetable = proc_pagetable(p)) == 0) + goto bad; + + // Load program into memory. + for(i=0, off=elf.phoff; i<elf.phnum; i++, off+=sizeof(ph)){ + if(readi(ip, 0, (uint64)&ph, off, sizeof(ph)) != sizeof(ph)) + goto bad; + if(ph.type != ELF_PROG_LOAD) + continue; + if(ph.memsz < ph.filesz) + goto bad; + if(ph.vaddr + ph.memsz < ph.vaddr) + goto bad; + if(ph.vaddr % PGSIZE != 0) + goto bad; + uint64 sz1; + if((sz1 = uvmalloc(pagetable, sz, ph.vaddr + ph.memsz, flags2perm(ph.flags))) == 0) + goto bad; + sz = sz1; + if(loadseg(pagetable, ph.vaddr, ip, ph.off, ph.filesz) < 0) + goto bad; + } + iunlockput(ip); + end_op(); + ip = 0; + + p = myproc(); + uint64 oldsz = p->sz; + + // Allocate some pages at the next page boundary. + // Make the first inaccessible as a stack guard. + // Use the rest as the user stack. + sz = PGROUNDUP(sz); + uint64 sz1; + if((sz1 = uvmalloc(pagetable, sz, sz + (USERSTACK+1)*PGSIZE, PTE_W)) == 0) + goto bad; + sz = sz1; + uvmclear(pagetable, sz-(USERSTACK+1)*PGSIZE); + sp = sz; + stackbase = sp - USERSTACK*PGSIZE; + + // Push argument strings, prepare rest of stack in ustack. + for(argc = 0; argv[argc]; argc++) { + if(argc >= MAXARG) + goto bad; + sp -= strlen(argv[argc]) + 1; + sp -= sp % 16; // riscv sp must be 16-byte aligned + if(sp < stackbase) + goto bad; + if(copyout(pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0) + goto bad; + ustack[argc] = sp; + } + ustack[argc] = 0; + + // push the array of argv[] pointers. + sp -= (argc+1) * sizeof(uint64); + sp -= sp % 16; + if(sp < stackbase) + goto bad; + if(copyout(pagetable, sp, (char *)ustack, (argc+1)*sizeof(uint64)) < 0) + goto bad; + + // arguments to user main(argc, argv) + // argc is returned via the system call return + // value, which goes in a0. + p->trapframe->a1 = sp; + + // Save program name for debugging. + for(last=s=path; *s; s++) + if(*s == '/') + last = s+1; + safestrcpy(p->name, last, sizeof(p->name)); + + // Commit to the user image. + oldpagetable = p->pagetable; + p->pagetable = pagetable; + p->sz = sz; + p->trapframe->epc = elf.entry; // initial program counter = main + p->trapframe->sp = sp; // initial stack pointer + proc_freepagetable(oldpagetable, oldsz); + + return argc; // this ends up in a0, the first argument to main(argc, argv) + + bad: + if(pagetable) + proc_freepagetable(pagetable, sz); + if(ip){ + iunlockput(ip); + end_op(); + } + return -1; +} + +// Load a program segment into pagetable at virtual address va. +// va must be page-aligned +// and the pages from va to va+sz must already be mapped. +// Returns 0 on success, -1 on failure. +static int +loadseg(pagetable_t pagetable, uint64 va, struct inode *ip, uint offset, uint sz) +{ + uint i, n; + uint64 pa; + + for(i = 0; i < sz; i += PGSIZE){ + pa = walkaddr(pagetable, va + i); + if(pa == 0) + panic("loadseg: address should exist"); + if(sz - i < PGSIZE) + n = sz - i; + else + n = PGSIZE; + if(readi(ip, 0, (uint64)pa, offset+i, n) != n) + return -1; + } + + return 0; +} diff --git a/1/xv6-riscv-riscv/kernel/fcntl.h b/1/xv6-riscv-riscv/kernel/fcntl.h new file mode 100644 index 0000000000000000000000000000000000000000..44861b9a23da4e414225cd6fa9465c2219412811 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/fcntl.h @@ -0,0 +1,5 @@ +#define O_RDONLY 0x000 +#define O_WRONLY 0x001 +#define O_RDWR 0x002 +#define O_CREATE 0x200 +#define O_TRUNC 0x400 diff --git a/1/xv6-riscv-riscv/kernel/file.c b/1/xv6-riscv-riscv/kernel/file.c new file mode 100644 index 0000000000000000000000000000000000000000..25fa2263ae9f452a2c12dd5cc9e3fe7a784ef25b --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/file.c @@ -0,0 +1,182 @@ +// +// Support functions for system calls that involve file descriptors. +// + +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "fs.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "file.h" +#include "stat.h" +#include "proc.h" + +struct devsw devsw[NDEV]; +struct { + struct spinlock lock; + struct file file[NFILE]; +} ftable; + +void +fileinit(void) +{ + initlock(&ftable.lock, "ftable"); +} + +// Allocate a file structure. +struct file* +filealloc(void) +{ + struct file *f; + + acquire(&ftable.lock); + for(f = ftable.file; f < ftable.file + NFILE; f++){ + if(f->ref == 0){ + f->ref = 1; + release(&ftable.lock); + return f; + } + } + release(&ftable.lock); + return 0; +} + +// Increment ref count for file f. +struct file* +filedup(struct file *f) +{ + acquire(&ftable.lock); + if(f->ref < 1) + panic("filedup"); + f->ref++; + release(&ftable.lock); + return f; +} + +// Close file f. (Decrement ref count, close when reaches 0.) +void +fileclose(struct file *f) +{ + struct file ff; + + acquire(&ftable.lock); + if(f->ref < 1) + panic("fileclose"); + if(--f->ref > 0){ + release(&ftable.lock); + return; + } + ff = *f; + f->ref = 0; + f->type = FD_NONE; + release(&ftable.lock); + + if(ff.type == FD_PIPE){ + pipeclose(ff.pipe, ff.writable); + } else if(ff.type == FD_INODE || ff.type == FD_DEVICE){ + begin_op(); + iput(ff.ip); + end_op(); + } +} + +// Get metadata about file f. +// addr is a user virtual address, pointing to a struct stat. +int +filestat(struct file *f, uint64 addr) +{ + struct proc *p = myproc(); + struct stat st; + + if(f->type == FD_INODE || f->type == FD_DEVICE){ + ilock(f->ip); + stati(f->ip, &st); + iunlock(f->ip); + if(copyout(p->pagetable, addr, (char *)&st, sizeof(st)) < 0) + return -1; + return 0; + } + return -1; +} + +// Read from file f. +// addr is a user virtual address. +int +fileread(struct file *f, uint64 addr, int n) +{ + int r = 0; + + if(f->readable == 0) + return -1; + + if(f->type == FD_PIPE){ + r = piperead(f->pipe, addr, n); + } else if(f->type == FD_DEVICE){ + if(f->major < 0 || f->major >= NDEV || !devsw[f->major].read) + return -1; + r = devsw[f->major].read(1, addr, n); + } else if(f->type == FD_INODE){ + ilock(f->ip); + if((r = readi(f->ip, 1, addr, f->off, n)) > 0) + f->off += r; + iunlock(f->ip); + } else { + panic("fileread"); + } + + return r; +} + +// Write to file f. +// addr is a user virtual address. +int +filewrite(struct file *f, uint64 addr, int n) +{ + int r, ret = 0; + + if(f->writable == 0) + return -1; + + if(f->type == FD_PIPE){ + ret = pipewrite(f->pipe, addr, n); + } else if(f->type == FD_DEVICE){ + if(f->major < 0 || f->major >= NDEV || !devsw[f->major].write) + return -1; + ret = devsw[f->major].write(1, addr, n); + } else if(f->type == FD_INODE){ + // write a few blocks at a time to avoid exceeding + // the maximum log transaction size, including + // i-node, indirect block, allocation blocks, + // and 2 blocks of slop for non-aligned writes. + // this really belongs lower down, since writei() + // might be writing a device like the console. + int max = ((MAXOPBLOCKS-1-1-2) / 2) * BSIZE; + int i = 0; + while(i < n){ + int n1 = n - i; + if(n1 > max) + n1 = max; + + begin_op(); + ilock(f->ip); + if ((r = writei(f->ip, 1, addr + i, f->off, n1)) > 0) + f->off += r; + iunlock(f->ip); + end_op(); + + if(r != n1){ + // error from writei + break; + } + i += r; + } + ret = (i == n ? n : -1); + } else { + panic("filewrite"); + } + + return ret; +} + diff --git a/1/xv6-riscv-riscv/kernel/file.h b/1/xv6-riscv-riscv/kernel/file.h new file mode 100644 index 0000000000000000000000000000000000000000..b076d1d8062ad0a416c0a7e0a21af33991495e26 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/file.h @@ -0,0 +1,40 @@ +struct file { + enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type; + int ref; // reference count + char readable; + char writable; + struct pipe *pipe; // FD_PIPE + struct inode *ip; // FD_INODE and FD_DEVICE + uint off; // FD_INODE + short major; // FD_DEVICE +}; + +#define major(dev) ((dev) >> 16 & 0xFFFF) +#define minor(dev) ((dev) & 0xFFFF) +#define mkdev(m,n) ((uint)((m)<<16| (n))) + +// in-memory copy of an inode +struct inode { + uint dev; // Device number + uint inum; // Inode number + int ref; // Reference count + struct sleeplock lock; // protects everything below here + int valid; // inode has been read from disk? + + short type; // copy of disk inode + short major; + short minor; + short nlink; + uint size; + uint addrs[NDIRECT+1]; +}; + +// map major device number to device functions. +struct devsw { + int (*read)(int, uint64, int); + int (*write)(int, uint64, int); +}; + +extern struct devsw devsw[]; + +#define CONSOLE 1 diff --git a/1/xv6-riscv-riscv/kernel/fs.c b/1/xv6-riscv-riscv/kernel/fs.c new file mode 100644 index 0000000000000000000000000000000000000000..c6bab157e3e2b6843d9e2335b5c00554e53a859e --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/fs.c @@ -0,0 +1,697 @@ +// File system implementation. Five layers: +// + Blocks: allocator for raw disk blocks. +// + Log: crash recovery for multi-step updates. +// + Files: inode allocator, reading, writing, metadata. +// + Directories: inode with special contents (list of other inodes!) +// + Names: paths like /usr/rtm/xv6/fs.c for convenient naming. +// +// This file contains the low-level file system manipulation +// routines. The (higher-level) system call implementations +// are in sysfile.c. + +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "stat.h" +#include "spinlock.h" +#include "proc.h" +#include "sleeplock.h" +#include "fs.h" +#include "buf.h" +#include "file.h" + +#define min(a, b) ((a) < (b) ? (a) : (b)) +// there should be one superblock per disk device, but we run with +// only one device +struct superblock sb; + +// Read the super block. +static void +readsb(int dev, struct superblock *sb) +{ + struct buf *bp; + + bp = bread(dev, 1); + memmove(sb, bp->data, sizeof(*sb)); + brelse(bp); +} + +// Init fs +void +fsinit(int dev) { + readsb(dev, &sb); + if(sb.magic != FSMAGIC) + panic("invalid file system"); + initlog(dev, &sb); +} + +// Zero a block. +static void +bzero(int dev, int bno) +{ + struct buf *bp; + + bp = bread(dev, bno); + memset(bp->data, 0, BSIZE); + log_write(bp); + brelse(bp); +} + +// Blocks. + +// Allocate a zeroed disk block. +// returns 0 if out of disk space. +static uint +balloc(uint dev) +{ + int b, bi, m; + struct buf *bp; + + bp = 0; + for(b = 0; b < sb.size; b += BPB){ + bp = bread(dev, BBLOCK(b, sb)); + for(bi = 0; bi < BPB && b + bi < sb.size; bi++){ + m = 1 << (bi % 8); + if((bp->data[bi/8] & m) == 0){ // Is block free? + bp->data[bi/8] |= m; // Mark block in use. + log_write(bp); + brelse(bp); + bzero(dev, b + bi); + return b + bi; + } + } + brelse(bp); + } + printf("balloc: out of blocks\n"); + return 0; +} + +// Free a disk block. +static void +bfree(int dev, uint b) +{ + struct buf *bp; + int bi, m; + + bp = bread(dev, BBLOCK(b, sb)); + bi = b % BPB; + m = 1 << (bi % 8); + if((bp->data[bi/8] & m) == 0) + panic("freeing free block"); + bp->data[bi/8] &= ~m; + log_write(bp); + brelse(bp); +} + +// Inodes. +// +// An inode describes a single unnamed file. +// The inode disk structure holds metadata: the file's type, +// its size, the number of links referring to it, and the +// list of blocks holding the file's content. +// +// The inodes are laid out sequentially on disk at block +// sb.inodestart. Each inode has a number, indicating its +// position on the disk. +// +// The kernel keeps a table of in-use inodes in memory +// to provide a place for synchronizing access +// to inodes used by multiple processes. The in-memory +// inodes include book-keeping information that is +// not stored on disk: ip->ref and ip->valid. +// +// An inode and its in-memory representation go through a +// sequence of states before they can be used by the +// rest of the file system code. +// +// * Allocation: an inode is allocated if its type (on disk) +// is non-zero. ialloc() allocates, and iput() frees if +// the reference and link counts have fallen to zero. +// +// * Referencing in table: an entry in the inode table +// is free if ip->ref is zero. Otherwise ip->ref tracks +// the number of in-memory pointers to the entry (open +// files and current directories). iget() finds or +// creates a table entry and increments its ref; iput() +// decrements ref. +// +// * Valid: the information (type, size, &c) in an inode +// table entry is only correct when ip->valid is 1. +// ilock() reads the inode from +// the disk and sets ip->valid, while iput() clears +// ip->valid if ip->ref has fallen to zero. +// +// * Locked: file system code may only examine and modify +// the information in an inode and its content if it +// has first locked the inode. +// +// Thus a typical sequence is: +// ip = iget(dev, inum) +// ilock(ip) +// ... examine and modify ip->xxx ... +// iunlock(ip) +// iput(ip) +// +// ilock() is separate from iget() so that system calls can +// get a long-term reference to an inode (as for an open file) +// and only lock it for short periods (e.g., in read()). +// The separation also helps avoid deadlock and races during +// pathname lookup. iget() increments ip->ref so that the inode +// stays in the table and pointers to it remain valid. +// +// Many internal file system functions expect the caller to +// have locked the inodes involved; this lets callers create +// multi-step atomic operations. +// +// The itable.lock spin-lock protects the allocation of itable +// entries. Since ip->ref indicates whether an entry is free, +// and ip->dev and ip->inum indicate which i-node an entry +// holds, one must hold itable.lock while using any of those fields. +// +// An ip->lock sleep-lock protects all ip-> fields other than ref, +// dev, and inum. One must hold ip->lock in order to +// read or write that inode's ip->valid, ip->size, ip->type, &c. + +struct { + struct spinlock lock; + struct inode inode[NINODE]; +} itable; + +void +iinit() +{ + int i = 0; + + initlock(&itable.lock, "itable"); + for(i = 0; i < NINODE; i++) { + initsleeplock(&itable.inode[i].lock, "inode"); + } +} + +static struct inode* iget(uint dev, uint inum); + +// Allocate an inode on device dev. +// Mark it as allocated by giving it type type. +// Returns an unlocked but allocated and referenced inode, +// or NULL if there is no free inode. +struct inode* +ialloc(uint dev, short type) +{ + int inum; + struct buf *bp; + struct dinode *dip; + + for(inum = 1; inum < sb.ninodes; inum++){ + bp = bread(dev, IBLOCK(inum, sb)); + dip = (struct dinode*)bp->data + inum%IPB; + if(dip->type == 0){ // a free inode + memset(dip, 0, sizeof(*dip)); + dip->type = type; + log_write(bp); // mark it allocated on the disk + brelse(bp); + return iget(dev, inum); + } + brelse(bp); + } + printf("ialloc: no inodes\n"); + return 0; +} + +// Copy a modified in-memory inode to disk. +// Must be called after every change to an ip->xxx field +// that lives on disk. +// Caller must hold ip->lock. +void +iupdate(struct inode *ip) +{ + struct buf *bp; + struct dinode *dip; + + bp = bread(ip->dev, IBLOCK(ip->inum, sb)); + dip = (struct dinode*)bp->data + ip->inum%IPB; + dip->type = ip->type; + dip->major = ip->major; + dip->minor = ip->minor; + dip->nlink = ip->nlink; + dip->size = ip->size; + memmove(dip->addrs, ip->addrs, sizeof(ip->addrs)); + log_write(bp); + brelse(bp); +} + +// Find the inode with number inum on device dev +// and return the in-memory copy. Does not lock +// the inode and does not read it from disk. +static struct inode* +iget(uint dev, uint inum) +{ + struct inode *ip, *empty; + + acquire(&itable.lock); + + // Is the inode already in the table? + empty = 0; + for(ip = &itable.inode[0]; ip < &itable.inode[NINODE]; ip++){ + if(ip->ref > 0 && ip->dev == dev && ip->inum == inum){ + ip->ref++; + release(&itable.lock); + return ip; + } + if(empty == 0 && ip->ref == 0) // Remember empty slot. + empty = ip; + } + + // Recycle an inode entry. + if(empty == 0) + panic("iget: no inodes"); + + ip = empty; + ip->dev = dev; + ip->inum = inum; + ip->ref = 1; + ip->valid = 0; + release(&itable.lock); + + return ip; +} + +// Increment reference count for ip. +// Returns ip to enable ip = idup(ip1) idiom. +struct inode* +idup(struct inode *ip) +{ + acquire(&itable.lock); + ip->ref++; + release(&itable.lock); + return ip; +} + +// Lock the given inode. +// Reads the inode from disk if necessary. +void +ilock(struct inode *ip) +{ + struct buf *bp; + struct dinode *dip; + + if(ip == 0 || ip->ref < 1) + panic("ilock"); + + acquiresleep(&ip->lock); + + if(ip->valid == 0){ + bp = bread(ip->dev, IBLOCK(ip->inum, sb)); + dip = (struct dinode*)bp->data + ip->inum%IPB; + ip->type = dip->type; + ip->major = dip->major; + ip->minor = dip->minor; + ip->nlink = dip->nlink; + ip->size = dip->size; + memmove(ip->addrs, dip->addrs, sizeof(ip->addrs)); + brelse(bp); + ip->valid = 1; + if(ip->type == 0) + panic("ilock: no type"); + } +} + +// Unlock the given inode. +void +iunlock(struct inode *ip) +{ + if(ip == 0 || !holdingsleep(&ip->lock) || ip->ref < 1) + panic("iunlock"); + + releasesleep(&ip->lock); +} + +// Drop a reference to an in-memory inode. +// If that was the last reference, the inode table entry can +// be recycled. +// If that was the last reference and the inode has no links +// to it, free the inode (and its content) on disk. +// All calls to iput() must be inside a transaction in +// case it has to free the inode. +void +iput(struct inode *ip) +{ + acquire(&itable.lock); + + if(ip->ref == 1 && ip->valid && ip->nlink == 0){ + // inode has no links and no other references: truncate and free. + + // ip->ref == 1 means no other process can have ip locked, + // so this acquiresleep() won't block (or deadlock). + acquiresleep(&ip->lock); + + release(&itable.lock); + + itrunc(ip); + ip->type = 0; + iupdate(ip); + ip->valid = 0; + + releasesleep(&ip->lock); + + acquire(&itable.lock); + } + + ip->ref--; + release(&itable.lock); +} + +// Common idiom: unlock, then put. +void +iunlockput(struct inode *ip) +{ + iunlock(ip); + iput(ip); +} + +// Inode content +// +// The content (data) associated with each inode is stored +// in blocks on the disk. The first NDIRECT block numbers +// are listed in ip->addrs[]. The next NINDIRECT blocks are +// listed in block ip->addrs[NDIRECT]. + +// Return the disk block address of the nth block in inode ip. +// If there is no such block, bmap allocates one. +// returns 0 if out of disk space. +static uint +bmap(struct inode *ip, uint bn) +{ + uint addr, *a; + struct buf *bp; + + if(bn < NDIRECT){ + if((addr = ip->addrs[bn]) == 0){ + addr = balloc(ip->dev); + if(addr == 0) + return 0; + ip->addrs[bn] = addr; + } + return addr; + } + bn -= NDIRECT; + + if(bn < NINDIRECT){ + // Load indirect block, allocating if necessary. + if((addr = ip->addrs[NDIRECT]) == 0){ + addr = balloc(ip->dev); + if(addr == 0) + return 0; + ip->addrs[NDIRECT] = addr; + } + bp = bread(ip->dev, addr); + a = (uint*)bp->data; + if((addr = a[bn]) == 0){ + addr = balloc(ip->dev); + if(addr){ + a[bn] = addr; + log_write(bp); + } + } + brelse(bp); + return addr; + } + + panic("bmap: out of range"); +} + +// Truncate inode (discard contents). +// Caller must hold ip->lock. +void +itrunc(struct inode *ip) +{ + int i, j; + struct buf *bp; + uint *a; + + for(i = 0; i < NDIRECT; i++){ + if(ip->addrs[i]){ + bfree(ip->dev, ip->addrs[i]); + ip->addrs[i] = 0; + } + } + + if(ip->addrs[NDIRECT]){ + bp = bread(ip->dev, ip->addrs[NDIRECT]); + a = (uint*)bp->data; + for(j = 0; j < NINDIRECT; j++){ + if(a[j]) + bfree(ip->dev, a[j]); + } + brelse(bp); + bfree(ip->dev, ip->addrs[NDIRECT]); + ip->addrs[NDIRECT] = 0; + } + + ip->size = 0; + iupdate(ip); +} + +// Copy stat information from inode. +// Caller must hold ip->lock. +void +stati(struct inode *ip, struct stat *st) +{ + st->dev = ip->dev; + st->ino = ip->inum; + st->type = ip->type; + st->nlink = ip->nlink; + st->size = ip->size; +} + +// Read data from inode. +// Caller must hold ip->lock. +// If user_dst==1, then dst is a user virtual address; +// otherwise, dst is a kernel address. +int +readi(struct inode *ip, int user_dst, uint64 dst, uint off, uint n) +{ + uint tot, m; + struct buf *bp; + + if(off > ip->size || off + n < off) + return 0; + if(off + n > ip->size) + n = ip->size - off; + + for(tot=0; tot<n; tot+=m, off+=m, dst+=m){ + uint addr = bmap(ip, off/BSIZE); + if(addr == 0) + break; + bp = bread(ip->dev, addr); + m = min(n - tot, BSIZE - off%BSIZE); + if(either_copyout(user_dst, dst, bp->data + (off % BSIZE), m) == -1) { + brelse(bp); + tot = -1; + break; + } + brelse(bp); + } + return tot; +} + +// Write data to inode. +// Caller must hold ip->lock. +// If user_src==1, then src is a user virtual address; +// otherwise, src is a kernel address. +// Returns the number of bytes successfully written. +// If the return value is less than the requested n, +// there was an error of some kind. +int +writei(struct inode *ip, int user_src, uint64 src, uint off, uint n) +{ + uint tot, m; + struct buf *bp; + + if(off > ip->size || off + n < off) + return -1; + if(off + n > MAXFILE*BSIZE) + return -1; + + for(tot=0; tot<n; tot+=m, off+=m, src+=m){ + uint addr = bmap(ip, off/BSIZE); + if(addr == 0) + break; + bp = bread(ip->dev, addr); + m = min(n - tot, BSIZE - off%BSIZE); + if(either_copyin(bp->data + (off % BSIZE), user_src, src, m) == -1) { + brelse(bp); + break; + } + log_write(bp); + brelse(bp); + } + + if(off > ip->size) + ip->size = off; + + // write the i-node back to disk even if the size didn't change + // because the loop above might have called bmap() and added a new + // block to ip->addrs[]. + iupdate(ip); + + return tot; +} + +// Directories + +int +namecmp(const char *s, const char *t) +{ + return strncmp(s, t, DIRSIZ); +} + +// Look for a directory entry in a directory. +// If found, set *poff to byte offset of entry. +struct inode* +dirlookup(struct inode *dp, char *name, uint *poff) +{ + uint off, inum; + struct dirent de; + + if(dp->type != T_DIR) + panic("dirlookup not DIR"); + + for(off = 0; off < dp->size; off += sizeof(de)){ + if(readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) + panic("dirlookup read"); + if(de.inum == 0) + continue; + if(namecmp(name, de.name) == 0){ + // entry matches path element + if(poff) + *poff = off; + inum = de.inum; + return iget(dp->dev, inum); + } + } + + return 0; +} + +// Write a new directory entry (name, inum) into the directory dp. +// Returns 0 on success, -1 on failure (e.g. out of disk blocks). +int +dirlink(struct inode *dp, char *name, uint inum) +{ + int off; + struct dirent de; + struct inode *ip; + + // Check that name is not present. + if((ip = dirlookup(dp, name, 0)) != 0){ + iput(ip); + return -1; + } + + // Look for an empty dirent. + for(off = 0; off < dp->size; off += sizeof(de)){ + if(readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) + panic("dirlink read"); + if(de.inum == 0) + break; + } + + strncpy(de.name, name, DIRSIZ); + de.inum = inum; + if(writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) + return -1; + + return 0; +} + +// Paths + +// Copy the next path element from path into name. +// Return a pointer to the element following the copied one. +// The returned path has no leading slashes, +// so the caller can check *path=='\0' to see if the name is the last one. +// If no name to remove, return 0. +// +// Examples: +// skipelem("a/bb/c", name) = "bb/c", setting name = "a" +// skipelem("///a//bb", name) = "bb", setting name = "a" +// skipelem("a", name) = "", setting name = "a" +// skipelem("", name) = skipelem("////", name) = 0 +// +static char* +skipelem(char *path, char *name) +{ + char *s; + int len; + + while(*path == '/') + path++; + if(*path == 0) + return 0; + s = path; + while(*path != '/' && *path != 0) + path++; + len = path - s; + if(len >= DIRSIZ) + memmove(name, s, DIRSIZ); + else { + memmove(name, s, len); + name[len] = 0; + } + while(*path == '/') + path++; + return path; +} + +// Look up and return the inode for a path name. +// If parent != 0, return the inode for the parent and copy the final +// path element into name, which must have room for DIRSIZ bytes. +// Must be called inside a transaction since it calls iput(). +static struct inode* +namex(char *path, int nameiparent, char *name) +{ + struct inode *ip, *next; + + if(*path == '/') + ip = iget(ROOTDEV, ROOTINO); + else + ip = idup(myproc()->cwd); + + while((path = skipelem(path, name)) != 0){ + ilock(ip); + if(ip->type != T_DIR){ + iunlockput(ip); + return 0; + } + if(nameiparent && *path == '\0'){ + // Stop one level early. + iunlock(ip); + return ip; + } + if((next = dirlookup(ip, name, 0)) == 0){ + iunlockput(ip); + return 0; + } + iunlockput(ip); + ip = next; + } + if(nameiparent){ + iput(ip); + return 0; + } + return ip; +} + +struct inode* +namei(char *path) +{ + char name[DIRSIZ]; + return namex(path, 0, name); +} + +struct inode* +nameiparent(char *path, char *name) +{ + return namex(path, 1, name); +} diff --git a/1/xv6-riscv-riscv/kernel/fs.h b/1/xv6-riscv-riscv/kernel/fs.h new file mode 100644 index 0000000000000000000000000000000000000000..139dcc9c37a0c1d64e479a3ba26dc9a0cc6273d3 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/fs.h @@ -0,0 +1,60 @@ +// On-disk file system format. +// Both the kernel and user programs use this header file. + + +#define ROOTINO 1 // root i-number +#define BSIZE 1024 // block size + +// Disk layout: +// [ boot block | super block | log | inode blocks | +// free bit map | data blocks] +// +// mkfs computes the super block and builds an initial file system. The +// super block describes the disk layout: +struct superblock { + uint magic; // Must be FSMAGIC + uint size; // Size of file system image (blocks) + uint nblocks; // Number of data blocks + uint ninodes; // Number of inodes. + uint nlog; // Number of log blocks + uint logstart; // Block number of first log block + uint inodestart; // Block number of first inode block + uint bmapstart; // Block number of first free map block +}; + +#define FSMAGIC 0x10203040 + +#define NDIRECT 12 +#define NINDIRECT (BSIZE / sizeof(uint)) +#define MAXFILE (NDIRECT + NINDIRECT) + +// On-disk inode structure +struct dinode { + short type; // File type + short major; // Major device number (T_DEVICE only) + short minor; // Minor device number (T_DEVICE only) + short nlink; // Number of links to inode in file system + uint size; // Size of file (bytes) + uint addrs[NDIRECT+1]; // Data block addresses +}; + +// Inodes per block. +#define IPB (BSIZE / sizeof(struct dinode)) + +// Block containing inode i +#define IBLOCK(i, sb) ((i) / IPB + sb.inodestart) + +// Bitmap bits per block +#define BPB (BSIZE*8) + +// Block of free map containing bit for block b +#define BBLOCK(b, sb) ((b)/BPB + sb.bmapstart) + +// Directory is a file containing a sequence of dirent structures. +#define DIRSIZ 14 + +struct dirent { + ushort inum; + char name[DIRSIZ]; +}; + diff --git a/1/xv6-riscv-riscv/kernel/kalloc.c b/1/xv6-riscv-riscv/kernel/kalloc.c new file mode 100644 index 0000000000000000000000000000000000000000..0699e7eed4842dcd893ef0b74f1650a0fcaf0d38 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/kalloc.c @@ -0,0 +1,82 @@ +// Physical memory allocator, for user processes, +// kernel stacks, page-table pages, +// and pipe buffers. Allocates whole 4096-byte pages. + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "spinlock.h" +#include "riscv.h" +#include "defs.h" + +void freerange(void *pa_start, void *pa_end); + +extern char end[]; // first address after kernel. + // defined by kernel.ld. + +struct run { + struct run *next; +}; + +struct { + struct spinlock lock; + struct run *freelist; +} kmem; + +void +kinit() +{ + initlock(&kmem.lock, "kmem"); + freerange(end, (void*)PHYSTOP); +} + +void +freerange(void *pa_start, void *pa_end) +{ + char *p; + p = (char*)PGROUNDUP((uint64)pa_start); + for(; p + PGSIZE <= (char*)pa_end; p += PGSIZE) + kfree(p); +} + +// Free the page of physical memory pointed at by pa, +// which normally should have been returned by a +// call to kalloc(). (The exception is when +// initializing the allocator; see kinit above.) +void +kfree(void *pa) +{ + struct run *r; + + if(((uint64)pa % PGSIZE) != 0 || (char*)pa < end || (uint64)pa >= PHYSTOP) + panic("kfree"); + + // Fill with junk to catch dangling refs. + memset(pa, 1, PGSIZE); + + r = (struct run*)pa; + + acquire(&kmem.lock); + r->next = kmem.freelist; + kmem.freelist = r; + release(&kmem.lock); +} + +// Allocate one 4096-byte page of physical memory. +// Returns a pointer that the kernel can use. +// Returns 0 if the memory cannot be allocated. +void * +kalloc(void) +{ + struct run *r; + + acquire(&kmem.lock); + r = kmem.freelist; + if(r) + kmem.freelist = r->next; + release(&kmem.lock); + + if(r) + memset((char*)r, 5, PGSIZE); // fill with junk + return (void*)r; +} diff --git a/1/xv6-riscv-riscv/kernel/kernel.ld b/1/xv6-riscv-riscv/kernel/kernel.ld new file mode 100644 index 0000000000000000000000000000000000000000..ee04f22bc97679d4c94aa840eeef3a02c06475e0 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/kernel.ld @@ -0,0 +1,44 @@ +OUTPUT_ARCH( "riscv" ) +ENTRY( _entry ) + +SECTIONS +{ + /* + * ensure that entry.S / _entry is at 0x80000000, + * where qemu's -kernel jumps. + */ + . = 0x80000000; + + .text : { + *(.text .text.*) + . = ALIGN(0x1000); + _trampoline = .; + *(trampsec) + . = ALIGN(0x1000); + ASSERT(. - _trampoline == 0x1000, "error: trampoline larger than one page"); + PROVIDE(etext = .); + } + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */ + . = ALIGN(16); + *(.rodata .rodata.*) + } + + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) /* do not need to distinguish this from .data */ + . = ALIGN(16); + *(.data .data.*) + } + + .bss : { + . = ALIGN(16); + *(.sbss .sbss.*) /* do not need to distinguish this from .bss */ + . = ALIGN(16); + *(.bss .bss.*) + } + + PROVIDE(end = .); +} diff --git a/1/xv6-riscv-riscv/kernel/kernelvec.S b/1/xv6-riscv-riscv/kernel/kernelvec.S new file mode 100644 index 0000000000000000000000000000000000000000..199f25590e4946c1f4017d93332117d903cae502 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/kernelvec.S @@ -0,0 +1,64 @@ + # + # interrupts and exceptions while in supervisor + # mode come here. + # + # the current stack is a kernel stack. + # push registers, call kerneltrap(). + # when kerneltrap() returns, restore registers, return. + # +.globl kerneltrap +.globl kernelvec +.align 4 +kernelvec: + # make room to save registers. + addi sp, sp, -256 + + # save caller-saved registers. + sd ra, 0(sp) + sd sp, 8(sp) + sd gp, 16(sp) + sd tp, 24(sp) + sd t0, 32(sp) + sd t1, 40(sp) + sd t2, 48(sp) + sd a0, 72(sp) + sd a1, 80(sp) + sd a2, 88(sp) + sd a3, 96(sp) + sd a4, 104(sp) + sd a5, 112(sp) + sd a6, 120(sp) + sd a7, 128(sp) + sd t3, 216(sp) + sd t4, 224(sp) + sd t5, 232(sp) + sd t6, 240(sp) + + # call the C trap handler in trap.c + call kerneltrap + + # restore registers. + ld ra, 0(sp) + ld sp, 8(sp) + ld gp, 16(sp) + # not tp (contains hartid), in case we moved CPUs + ld t0, 32(sp) + ld t1, 40(sp) + ld t2, 48(sp) + ld a0, 72(sp) + ld a1, 80(sp) + ld a2, 88(sp) + ld a3, 96(sp) + ld a4, 104(sp) + ld a5, 112(sp) + ld a6, 120(sp) + ld a7, 128(sp) + ld t3, 216(sp) + ld t4, 224(sp) + ld t5, 232(sp) + ld t6, 240(sp) + + addi sp, sp, 256 + + # return to whatever we were doing in the kernel. + sret diff --git a/1/xv6-riscv-riscv/kernel/log.c b/1/xv6-riscv-riscv/kernel/log.c new file mode 100644 index 0000000000000000000000000000000000000000..5b58306f8feeb85fc7d4f67dd5a539265bc29166 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/log.c @@ -0,0 +1,236 @@ +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "buf.h" + +// Simple logging that allows concurrent FS system calls. +// +// A log transaction contains the updates of multiple FS system +// calls. The logging system only commits when there are +// no FS system calls active. Thus there is never +// any reasoning required about whether a commit might +// write an uncommitted system call's updates to disk. +// +// A system call should call begin_op()/end_op() to mark +// its start and end. Usually begin_op() just increments +// the count of in-progress FS system calls and returns. +// But if it thinks the log is close to running out, it +// sleeps until the last outstanding end_op() commits. +// +// The log is a physical re-do log containing disk blocks. +// The on-disk log format: +// header block, containing block #s for block A, B, C, ... +// block A +// block B +// block C +// ... +// Log appends are synchronous. + +// Contents of the header block, used for both the on-disk header block +// and to keep track in memory of logged block# before commit. +struct logheader { + int n; + int block[LOGSIZE]; +}; + +struct log { + struct spinlock lock; + int start; + int size; + int outstanding; // how many FS sys calls are executing. + int committing; // in commit(), please wait. + int dev; + struct logheader lh; +}; +struct log log; + +static void recover_from_log(void); +static void commit(); + +void +initlog(int dev, struct superblock *sb) +{ + if (sizeof(struct logheader) >= BSIZE) + panic("initlog: too big logheader"); + + initlock(&log.lock, "log"); + log.start = sb->logstart; + log.size = sb->nlog; + log.dev = dev; + recover_from_log(); +} + +// Copy committed blocks from log to their home location +static void +install_trans(int recovering) +{ + int tail; + + for (tail = 0; tail < log.lh.n; tail++) { + struct buf *lbuf = bread(log.dev, log.start+tail+1); // read log block + struct buf *dbuf = bread(log.dev, log.lh.block[tail]); // read dst + memmove(dbuf->data, lbuf->data, BSIZE); // copy block to dst + bwrite(dbuf); // write dst to disk + if(recovering == 0) + bunpin(dbuf); + brelse(lbuf); + brelse(dbuf); + } +} + +// Read the log header from disk into the in-memory log header +static void +read_head(void) +{ + struct buf *buf = bread(log.dev, log.start); + struct logheader *lh = (struct logheader *) (buf->data); + int i; + log.lh.n = lh->n; + for (i = 0; i < log.lh.n; i++) { + log.lh.block[i] = lh->block[i]; + } + brelse(buf); +} + +// Write in-memory log header to disk. +// This is the true point at which the +// current transaction commits. +static void +write_head(void) +{ + struct buf *buf = bread(log.dev, log.start); + struct logheader *hb = (struct logheader *) (buf->data); + int i; + hb->n = log.lh.n; + for (i = 0; i < log.lh.n; i++) { + hb->block[i] = log.lh.block[i]; + } + bwrite(buf); + brelse(buf); +} + +static void +recover_from_log(void) +{ + read_head(); + install_trans(1); // if committed, copy from log to disk + log.lh.n = 0; + write_head(); // clear the log +} + +// called at the start of each FS system call. +void +begin_op(void) +{ + acquire(&log.lock); + while(1){ + if(log.committing){ + sleep(&log, &log.lock); + } else if(log.lh.n + (log.outstanding+1)*MAXOPBLOCKS > LOGSIZE){ + // this op might exhaust log space; wait for commit. + sleep(&log, &log.lock); + } else { + log.outstanding += 1; + release(&log.lock); + break; + } + } +} + +// called at the end of each FS system call. +// commits if this was the last outstanding operation. +void +end_op(void) +{ + int do_commit = 0; + + acquire(&log.lock); + log.outstanding -= 1; + if(log.committing) + panic("log.committing"); + if(log.outstanding == 0){ + do_commit = 1; + log.committing = 1; + } else { + // begin_op() may be waiting for log space, + // and decrementing log.outstanding has decreased + // the amount of reserved space. + wakeup(&log); + } + release(&log.lock); + + if(do_commit){ + // call commit w/o holding locks, since not allowed + // to sleep with locks. + commit(); + acquire(&log.lock); + log.committing = 0; + wakeup(&log); + release(&log.lock); + } +} + +// Copy modified blocks from cache to log. +static void +write_log(void) +{ + int tail; + + for (tail = 0; tail < log.lh.n; tail++) { + struct buf *to = bread(log.dev, log.start+tail+1); // log block + struct buf *from = bread(log.dev, log.lh.block[tail]); // cache block + memmove(to->data, from->data, BSIZE); + bwrite(to); // write the log + brelse(from); + brelse(to); + } +} + +static void +commit() +{ + if (log.lh.n > 0) { + write_log(); // Write modified blocks from cache to log + write_head(); // Write header to disk -- the real commit + install_trans(0); // Now install writes to home locations + log.lh.n = 0; + write_head(); // Erase the transaction from the log + } +} + +// Caller has modified b->data and is done with the buffer. +// Record the block number and pin in the cache by increasing refcnt. +// commit()/write_log() will do the disk write. +// +// log_write() replaces bwrite(); a typical use is: +// bp = bread(...) +// modify bp->data[] +// log_write(bp) +// brelse(bp) +void +log_write(struct buf *b) +{ + int i; + + acquire(&log.lock); + if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1) + panic("too big a transaction"); + if (log.outstanding < 1) + panic("log_write outside of trans"); + + for (i = 0; i < log.lh.n; i++) { + if (log.lh.block[i] == b->blockno) // log absorption + break; + } + log.lh.block[i] = b->blockno; + if (i == log.lh.n) { // Add new block to log? + bpin(b); + log.lh.n++; + } + release(&log.lock); +} + diff --git a/1/xv6-riscv-riscv/kernel/main.c b/1/xv6-riscv-riscv/kernel/main.c new file mode 100644 index 0000000000000000000000000000000000000000..f0d3171d4ecef2e09aa48a4a1cfe21a5801b6f97 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/main.c @@ -0,0 +1,45 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "defs.h" + +volatile static int started = 0; + +// start() jumps here in supervisor mode on all CPUs. +void +main() +{ + if(cpuid() == 0){ + consoleinit(); + printfinit(); + printf("\n"); + printf("xv6 kernel is booting\n"); + printf("\n"); + kinit(); // physical page allocator + kvminit(); // create kernel page table + kvminithart(); // turn on paging + procinit(); // process table + trapinit(); // trap vectors + trapinithart(); // install kernel trap vector + plicinit(); // set up interrupt controller + plicinithart(); // ask PLIC for device interrupts + binit(); // buffer cache + iinit(); // inode table + fileinit(); // file table + virtio_disk_init(); // emulated hard disk + userinit(); // first user process + __sync_synchronize(); + started = 1; + } else { + while(started == 0) + ; + __sync_synchronize(); + printf("hart %d starting\n", cpuid()); + kvminithart(); // turn on paging + trapinithart(); // install kernel trap vector + plicinithart(); // ask PLIC for device interrupts + } + + scheduler(); +} diff --git a/1/xv6-riscv-riscv/kernel/memlayout.h b/1/xv6-riscv-riscv/kernel/memlayout.h new file mode 100644 index 0000000000000000000000000000000000000000..3ab2ace56cee4c2a7e8bfc6a1c9120e617655176 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/memlayout.h @@ -0,0 +1,59 @@ +// Physical memory layout + +// qemu -machine virt is set up like this, +// based on qemu's hw/riscv/virt.c: +// +// 00001000 -- boot ROM, provided by qemu +// 02000000 -- CLINT +// 0C000000 -- PLIC +// 10000000 -- uart0 +// 10001000 -- virtio disk +// 80000000 -- boot ROM jumps here in machine mode +// -kernel loads the kernel here +// unused RAM after 80000000. + +// the kernel uses physical memory thus: +// 80000000 -- entry.S, then kernel text and data +// end -- start of kernel page allocation area +// PHYSTOP -- end RAM used by the kernel + +// qemu puts UART registers here in physical memory. +#define UART0 0x10000000L +#define UART0_IRQ 10 + +// virtio mmio interface +#define VIRTIO0 0x10001000 +#define VIRTIO0_IRQ 1 + +// qemu puts platform-level interrupt controller (PLIC) here. +#define PLIC 0x0c000000L +#define PLIC_PRIORITY (PLIC + 0x0) +#define PLIC_PENDING (PLIC + 0x1000) +#define PLIC_SENABLE(hart) (PLIC + 0x2080 + (hart)*0x100) +#define PLIC_SPRIORITY(hart) (PLIC + 0x201000 + (hart)*0x2000) +#define PLIC_SCLAIM(hart) (PLIC + 0x201004 + (hart)*0x2000) + +// the kernel expects there to be RAM +// for use by the kernel and user pages +// from physical address 0x80000000 to PHYSTOP. +#define KERNBASE 0x80000000L +#define PHYSTOP (KERNBASE + 128*1024*1024) + +// map the trampoline page to the highest address, +// in both user and kernel space. +#define TRAMPOLINE (MAXVA - PGSIZE) + +// map kernel stacks beneath the trampoline, +// each surrounded by invalid guard pages. +#define KSTACK(p) (TRAMPOLINE - ((p)+1)* 2*PGSIZE) + +// User memory layout. +// Address zero first: +// text +// original data and bss +// fixed-size stack +// expandable heap +// ... +// TRAPFRAME (p->trapframe, used by the trampoline) +// TRAMPOLINE (the same page as in the kernel) +#define TRAPFRAME (TRAMPOLINE - PGSIZE) diff --git a/1/xv6-riscv-riscv/kernel/param.h b/1/xv6-riscv-riscv/kernel/param.h new file mode 100644 index 0000000000000000000000000000000000000000..80ec6d34cce1076ddc6419952c732afb84f0ccd8 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/param.h @@ -0,0 +1,15 @@ +#define NPROC 64 // maximum number of processes +#define NCPU 8 // maximum number of CPUs +#define NOFILE 16 // open files per process +#define NFILE 100 // open files per system +#define NINODE 50 // maximum number of active i-nodes +#define NDEV 10 // maximum major device number +#define ROOTDEV 1 // device number of file system root disk +#define MAXARG 32 // max exec arguments +#define MAXOPBLOCKS 10 // max # of blocks any FS op writes +#define LOGSIZE (MAXOPBLOCKS*3) // max data blocks in on-disk log +#define NBUF (MAXOPBLOCKS*3) // size of disk block cache +#define FSSIZE 2000 // size of file system in blocks +#define MAXPATH 128 // maximum file path name +#define USERSTACK 1 // user stack pages + diff --git a/1/xv6-riscv-riscv/kernel/pipe.c b/1/xv6-riscv-riscv/kernel/pipe.c new file mode 100644 index 0000000000000000000000000000000000000000..f6b501a18414c29c1aaec79d092d55a36b76ac98 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/pipe.c @@ -0,0 +1,130 @@ +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "spinlock.h" +#include "proc.h" +#include "fs.h" +#include "sleeplock.h" +#include "file.h" + +#define PIPESIZE 512 + +struct pipe { + struct spinlock lock; + char data[PIPESIZE]; + uint nread; // number of bytes read + uint nwrite; // number of bytes written + int readopen; // read fd is still open + int writeopen; // write fd is still open +}; + +int +pipealloc(struct file **f0, struct file **f1) +{ + struct pipe *pi; + + pi = 0; + *f0 = *f1 = 0; + if((*f0 = filealloc()) == 0 || (*f1 = filealloc()) == 0) + goto bad; + if((pi = (struct pipe*)kalloc()) == 0) + goto bad; + pi->readopen = 1; + pi->writeopen = 1; + pi->nwrite = 0; + pi->nread = 0; + initlock(&pi->lock, "pipe"); + (*f0)->type = FD_PIPE; + (*f0)->readable = 1; + (*f0)->writable = 0; + (*f0)->pipe = pi; + (*f1)->type = FD_PIPE; + (*f1)->readable = 0; + (*f1)->writable = 1; + (*f1)->pipe = pi; + return 0; + + bad: + if(pi) + kfree((char*)pi); + if(*f0) + fileclose(*f0); + if(*f1) + fileclose(*f1); + return -1; +} + +void +pipeclose(struct pipe *pi, int writable) +{ + acquire(&pi->lock); + if(writable){ + pi->writeopen = 0; + wakeup(&pi->nread); + } else { + pi->readopen = 0; + wakeup(&pi->nwrite); + } + if(pi->readopen == 0 && pi->writeopen == 0){ + release(&pi->lock); + kfree((char*)pi); + } else + release(&pi->lock); +} + +int +pipewrite(struct pipe *pi, uint64 addr, int n) +{ + int i = 0; + struct proc *pr = myproc(); + + acquire(&pi->lock); + while(i < n){ + if(pi->readopen == 0 || killed(pr)){ + release(&pi->lock); + return -1; + } + if(pi->nwrite == pi->nread + PIPESIZE){ //DOC: pipewrite-full + wakeup(&pi->nread); + sleep(&pi->nwrite, &pi->lock); + } else { + char ch; + if(copyin(pr->pagetable, &ch, addr + i, 1) == -1) + break; + pi->data[pi->nwrite++ % PIPESIZE] = ch; + i++; + } + } + wakeup(&pi->nread); + release(&pi->lock); + + return i; +} + +int +piperead(struct pipe *pi, uint64 addr, int n) +{ + int i; + struct proc *pr = myproc(); + char ch; + + acquire(&pi->lock); + while(pi->nread == pi->nwrite && pi->writeopen){ //DOC: pipe-empty + if(killed(pr)){ + release(&pi->lock); + return -1; + } + sleep(&pi->nread, &pi->lock); //DOC: piperead-sleep + } + for(i = 0; i < n; i++){ //DOC: piperead-copy + if(pi->nread == pi->nwrite) + break; + ch = pi->data[pi->nread++ % PIPESIZE]; + if(copyout(pr->pagetable, addr + i, &ch, 1) == -1) + break; + } + wakeup(&pi->nwrite); //DOC: piperead-wakeup + release(&pi->lock); + return i; +} diff --git a/1/xv6-riscv-riscv/kernel/plic.c b/1/xv6-riscv-riscv/kernel/plic.c new file mode 100644 index 0000000000000000000000000000000000000000..4175db9145091ee9e704c16c8b22b1a976102a20 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/plic.c @@ -0,0 +1,47 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "defs.h" + +// +// the riscv Platform Level Interrupt Controller (PLIC). +// + +void +plicinit(void) +{ + // set desired IRQ priorities non-zero (otherwise disabled). + *(uint32*)(PLIC + UART0_IRQ*4) = 1; + *(uint32*)(PLIC + VIRTIO0_IRQ*4) = 1; +} + +void +plicinithart(void) +{ + int hart = cpuid(); + + // set enable bits for this hart's S-mode + // for the uart and virtio disk. + *(uint32*)PLIC_SENABLE(hart) = (1 << UART0_IRQ) | (1 << VIRTIO0_IRQ); + + // set this hart's S-mode priority threshold to 0. + *(uint32*)PLIC_SPRIORITY(hart) = 0; +} + +// ask the PLIC what interrupt we should serve. +int +plic_claim(void) +{ + int hart = cpuid(); + int irq = *(uint32*)PLIC_SCLAIM(hart); + return irq; +} + +// tell the PLIC we've served this IRQ. +void +plic_complete(int irq) +{ + int hart = cpuid(); + *(uint32*)PLIC_SCLAIM(hart) = irq; +} diff --git a/1/xv6-riscv-riscv/kernel/printf.c b/1/xv6-riscv-riscv/kernel/printf.c new file mode 100644 index 0000000000000000000000000000000000000000..d20534c150123b346bdc54a3f6f486a142cc121b --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/printf.c @@ -0,0 +1,178 @@ +// +// formatted console output -- printf, panic. +// + +#include <stdarg.h> + +#include "types.h" +#include "param.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "file.h" +#include "memlayout.h" +#include "riscv.h" +#include "defs.h" +#include "proc.h" + +volatile int panicked = 0; + +// lock to avoid interleaving concurrent printf's. +static struct { + struct spinlock lock; + int locking; +} pr; + +static char digits[] = "0123456789abcdef"; + +static void +printint(long long xx, int base, int sign) +{ + char buf[16]; + int i; + unsigned long long x; + + if(sign && (sign = (xx < 0))) + x = -xx; + else + x = xx; + + i = 0; + do { + buf[i++] = digits[x % base]; + } while((x /= base) != 0); + + if(sign) + buf[i++] = '-'; + + while(--i >= 0) + consputc(buf[i]); +} + +static void +printptr(uint64 x) +{ + int i; + consputc('0'); + consputc('x'); + for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4) + consputc(digits[x >> (sizeof(uint64) * 8 - 4)]); +} + +// Print to the console. +int +printf(char *fmt, ...) +{ + va_list ap; + int i, cx, c0, c1, c2, locking; + char *s; + + locking = pr.locking; + if(locking) + acquire(&pr.lock); + + va_start(ap, fmt); + for(i = 0; (cx = fmt[i] & 0xff) != 0; i++){ + if(cx != '%'){ + consputc(cx); + continue; + } + i++; + c0 = fmt[i+0] & 0xff; + c1 = c2 = 0; + if(c0) c1 = fmt[i+1] & 0xff; + if(c1) c2 = fmt[i+2] & 0xff; + if(c0 == 'd'){ + printint(va_arg(ap, int), 10, 1); + } else if(c0 == 'l' && c1 == 'd'){ + printint(va_arg(ap, uint64), 10, 1); + i += 1; + } else if(c0 == 'l' && c1 == 'l' && c2 == 'd'){ + printint(va_arg(ap, uint64), 10, 1); + i += 2; + } else if(c0 == 'u'){ + printint(va_arg(ap, int), 10, 0); + } else if(c0 == 'l' && c1 == 'u'){ + printint(va_arg(ap, uint64), 10, 0); + i += 1; + } else if(c0 == 'l' && c1 == 'l' && c2 == 'u'){ + printint(va_arg(ap, uint64), 10, 0); + i += 2; + } else if(c0 == 'x'){ + printint(va_arg(ap, int), 16, 0); + } else if(c0 == 'l' && c1 == 'x'){ + printint(va_arg(ap, uint64), 16, 0); + i += 1; + } else if(c0 == 'l' && c1 == 'l' && c2 == 'x'){ + printint(va_arg(ap, uint64), 16, 0); + i += 2; + } else if(c0 == 'p'){ + printptr(va_arg(ap, uint64)); + } else if(c0 == 's'){ + if((s = va_arg(ap, char*)) == 0) + s = "(null)"; + for(; *s; s++) + consputc(*s); + } else if(c0 == '%'){ + consputc('%'); + } else if(c0 == 0){ + break; + } else { + // Print unknown % sequence to draw attention. + consputc('%'); + consputc(c0); + } + +#if 0 + switch(c){ + case 'd': + printint(va_arg(ap, int), 10, 1); + break; + case 'x': + printint(va_arg(ap, int), 16, 1); + break; + case 'p': + printptr(va_arg(ap, uint64)); + break; + case 's': + if((s = va_arg(ap, char*)) == 0) + s = "(null)"; + for(; *s; s++) + consputc(*s); + break; + case '%': + consputc('%'); + break; + default: + // Print unknown % sequence to draw attention. + consputc('%'); + consputc(c); + break; + } +#endif + } + va_end(ap); + + if(locking) + release(&pr.lock); + + return 0; +} + +void +panic(char *s) +{ + pr.locking = 0; + printf("panic: "); + printf("%s\n", s); + panicked = 1; // freeze uart output from other CPUs + for(;;) + ; +} + +void +printfinit(void) +{ + initlock(&pr.lock, "pr"); + pr.locking = 1; +} diff --git a/1/xv6-riscv-riscv/kernel/proc.c b/1/xv6-riscv-riscv/kernel/proc.c new file mode 100644 index 0000000000000000000000000000000000000000..130d9ce3719f1c5bffa4159db98fadf7cab98521 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/proc.c @@ -0,0 +1,695 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" + +struct cpu cpus[NCPU]; + +struct proc proc[NPROC]; + +struct proc *initproc; + +int nextpid = 1; +struct spinlock pid_lock; + +extern void forkret(void); +static void freeproc(struct proc *p); + +extern char trampoline[]; // trampoline.S + +// helps ensure that wakeups of wait()ing +// parents are not lost. helps obey the +// memory model when using p->parent. +// must be acquired before any p->lock. +struct spinlock wait_lock; + +// Allocate a page for each process's kernel stack. +// Map it high in memory, followed by an invalid +// guard page. +void +proc_mapstacks(pagetable_t kpgtbl) +{ + struct proc *p; + + for(p = proc; p < &proc[NPROC]; p++) { + char *pa = kalloc(); + if(pa == 0) + panic("kalloc"); + uint64 va = KSTACK((int) (p - proc)); + kvmmap(kpgtbl, va, (uint64)pa, PGSIZE, PTE_R | PTE_W); + } +} + +// initialize the proc table. +void +procinit(void) +{ + struct proc *p; + + initlock(&pid_lock, "nextpid"); + initlock(&wait_lock, "wait_lock"); + for(p = proc; p < &proc[NPROC]; p++) { + initlock(&p->lock, "proc"); + p->state = UNUSED; + p->kstack = KSTACK((int) (p - proc)); + } +} + +// Must be called with interrupts disabled, +// to prevent race with process being moved +// to a different CPU. +int +cpuid() +{ + int id = r_tp(); + return id; +} + +// Return this CPU's cpu struct. +// Interrupts must be disabled. +struct cpu* +mycpu(void) +{ + int id = cpuid(); + struct cpu *c = &cpus[id]; + return c; +} + +// Return the current struct proc *, or zero if none. +struct proc* +myproc(void) +{ + push_off(); + struct cpu *c = mycpu(); + struct proc *p = c->proc; + pop_off(); + return p; +} + +int +allocpid() +{ + int pid; + + acquire(&pid_lock); + pid = nextpid; + nextpid = nextpid + 1; + release(&pid_lock); + + return pid; +} + +// Look in the process table for an UNUSED proc. +// If found, initialize state required to run in the kernel, +// and return with p->lock held. +// If there are no free procs, or a memory allocation fails, return 0. +static struct proc* +allocproc(void) +{ + struct proc *p; + + for(p = proc; p < &proc[NPROC]; p++) { + acquire(&p->lock); + if(p->state == UNUSED) { + goto found; + } else { + release(&p->lock); + } + } + return 0; + +found: + p->pid = allocpid(); + p->state = USED; + + // Allocate a trapframe page. + if((p->trapframe = (struct trapframe *)kalloc()) == 0){ + freeproc(p); + release(&p->lock); + return 0; + } + + // An empty user page table. + p->pagetable = proc_pagetable(p); + if(p->pagetable == 0){ + freeproc(p); + release(&p->lock); + return 0; + } + + // Set up new context to start executing at forkret, + // which returns to user space. + memset(&p->context, 0, sizeof(p->context)); + p->context.ra = (uint64)forkret; + p->context.sp = p->kstack + PGSIZE; + + return p; +} + +// free a proc structure and the data hanging from it, +// including user pages. +// p->lock must be held. +static void +freeproc(struct proc *p) +{ + if(p->trapframe) + kfree((void*)p->trapframe); + p->trapframe = 0; + if(p->pagetable) + proc_freepagetable(p->pagetable, p->sz); + p->pagetable = 0; + p->sz = 0; + p->pid = 0; + p->parent = 0; + p->name[0] = 0; + p->chan = 0; + p->killed = 0; + p->xstate = 0; + p->state = UNUSED; +} + +// Create a user page table for a given process, with no user memory, +// but with trampoline and trapframe pages. +pagetable_t +proc_pagetable(struct proc *p) +{ + pagetable_t pagetable; + + // An empty page table. + pagetable = uvmcreate(); + if(pagetable == 0) + return 0; + + // map the trampoline code (for system call return) + // at the highest user virtual address. + // only the supervisor uses it, on the way + // to/from user space, so not PTE_U. + if(mappages(pagetable, TRAMPOLINE, PGSIZE, + (uint64)trampoline, PTE_R | PTE_X) < 0){ + uvmfree(pagetable, 0); + return 0; + } + + // map the trapframe page just below the trampoline page, for + // trampoline.S. + if(mappages(pagetable, TRAPFRAME, PGSIZE, + (uint64)(p->trapframe), PTE_R | PTE_W) < 0){ + uvmunmap(pagetable, TRAMPOLINE, 1, 0); + uvmfree(pagetable, 0); + return 0; + } + + return pagetable; +} + +// Free a process's page table, and free the +// physical memory it refers to. +void +proc_freepagetable(pagetable_t pagetable, uint64 sz) +{ + uvmunmap(pagetable, TRAMPOLINE, 1, 0); + uvmunmap(pagetable, TRAPFRAME, 1, 0); + uvmfree(pagetable, sz); +} + +// a user program that calls exec("/init") +// assembled from ../user/initcode.S +// od -t xC ../user/initcode +uchar initcode[] = { + 0x17, 0x05, 0x00, 0x00, 0x13, 0x05, 0x45, 0x02, + 0x97, 0x05, 0x00, 0x00, 0x93, 0x85, 0x35, 0x02, + 0x93, 0x08, 0x70, 0x00, 0x73, 0x00, 0x00, 0x00, + 0x93, 0x08, 0x20, 0x00, 0x73, 0x00, 0x00, 0x00, + 0xef, 0xf0, 0x9f, 0xff, 0x2f, 0x69, 0x6e, 0x69, + 0x74, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +// Set up first user process. +void +userinit(void) +{ + struct proc *p; + + p = allocproc(); + initproc = p; + + // allocate one user page and copy initcode's instructions + // and data into it. + uvmfirst(p->pagetable, initcode, sizeof(initcode)); + p->sz = PGSIZE; + + // prepare for the very first "return" from kernel to user. + p->trapframe->epc = 0; // user program counter + p->trapframe->sp = PGSIZE; // user stack pointer + + safestrcpy(p->name, "initcode", sizeof(p->name)); + p->cwd = namei("/"); + + p->state = RUNNABLE; + + release(&p->lock); +} + +// Grow or shrink user memory by n bytes. +// Return 0 on success, -1 on failure. +int +growproc(int n) +{ + uint64 sz; + struct proc *p = myproc(); + + sz = p->sz; + if(n > 0){ + if((sz = uvmalloc(p->pagetable, sz, sz + n, PTE_W)) == 0) { + return -1; + } + } else if(n < 0){ + sz = uvmdealloc(p->pagetable, sz, sz + n); + } + p->sz = sz; + return 0; +} + +// Create a new process, copying the parent. +// Sets up child kernel stack to return as if from fork() system call. +int +fork(void) +{ + int i, pid; + struct proc *np; + struct proc *p = myproc(); + + // Allocate process. + if((np = allocproc()) == 0){ + return -1; + } + + // Copy user memory from parent to child. + if(uvmcopy(p->pagetable, np->pagetable, p->sz) < 0){ + freeproc(np); + release(&np->lock); + return -1; + } + np->sz = p->sz; + + // copy saved user registers. + *(np->trapframe) = *(p->trapframe); + + // Cause fork to return 0 in the child. + np->trapframe->a0 = 0; + + // increment reference counts on open file descriptors. + for(i = 0; i < NOFILE; i++) + if(p->ofile[i]) + np->ofile[i] = filedup(p->ofile[i]); + np->cwd = idup(p->cwd); + + safestrcpy(np->name, p->name, sizeof(p->name)); + + pid = np->pid; + + release(&np->lock); + + acquire(&wait_lock); + np->parent = p; + release(&wait_lock); + + acquire(&np->lock); + np->state = RUNNABLE; + release(&np->lock); + + return pid; +} + +// Pass p's abandoned children to init. +// Caller must hold wait_lock. +void +reparent(struct proc *p) +{ + struct proc *pp; + + for(pp = proc; pp < &proc[NPROC]; pp++){ + if(pp->parent == p){ + pp->parent = initproc; + wakeup(initproc); + } + } +} + +// Exit the current process. Does not return. +// An exited process remains in the zombie state +// until its parent calls wait(). +void +exit(int status) +{ + struct proc *p = myproc(); + + if(p == initproc) + panic("init exiting"); + + // Close all open files. + for(int fd = 0; fd < NOFILE; fd++){ + if(p->ofile[fd]){ + struct file *f = p->ofile[fd]; + fileclose(f); + p->ofile[fd] = 0; + } + } + + begin_op(); + iput(p->cwd); + end_op(); + p->cwd = 0; + + acquire(&wait_lock); + + // Give any children to init. + reparent(p); + + // Parent might be sleeping in wait(). + wakeup(p->parent); + + acquire(&p->lock); + + p->xstate = status; + p->state = ZOMBIE; + + release(&wait_lock); + + // Jump into the scheduler, never to return. + sched(); + panic("zombie exit"); +} + +// Wait for a child process to exit and return its pid. +// Return -1 if this process has no children. +int +wait(uint64 addr) +{ + struct proc *pp; + int havekids, pid; + struct proc *p = myproc(); + + acquire(&wait_lock); + + for(;;){ + // Scan through table looking for exited children. + havekids = 0; + for(pp = proc; pp < &proc[NPROC]; pp++){ + if(pp->parent == p){ + // make sure the child isn't still in exit() or swtch(). + acquire(&pp->lock); + + havekids = 1; + if(pp->state == ZOMBIE){ + // Found one. + pid = pp->pid; + if(addr != 0 && copyout(p->pagetable, addr, (char *)&pp->xstate, + sizeof(pp->xstate)) < 0) { + release(&pp->lock); + release(&wait_lock); + return -1; + } + freeproc(pp); + release(&pp->lock); + release(&wait_lock); + return pid; + } + release(&pp->lock); + } + } + + // No point waiting if we don't have any children. + if(!havekids || killed(p)){ + release(&wait_lock); + return -1; + } + + // Wait for a child to exit. + sleep(p, &wait_lock); //DOC: wait-sleep + } +} + +// Per-CPU process scheduler. +// Each CPU calls scheduler() after setting itself up. +// Scheduler never returns. It loops, doing: +// - choose a process to run. +// - swtch to start running that process. +// - eventually that process transfers control +// via swtch back to the scheduler. +void +scheduler(void) +{ + struct proc *p; + struct cpu *c = mycpu(); + + c->proc = 0; + for(;;){ + // The most recent process to run may have had interrupts + // turned off; enable them to avoid a deadlock if all + // processes are waiting. + intr_on(); + + int found = 0; + for(p = proc; p < &proc[NPROC]; p++) { + acquire(&p->lock); + if(p->state == RUNNABLE) { + // Switch to chosen process. It is the process's job + // to release its lock and then reacquire it + // before jumping back to us. + p->state = RUNNING; + c->proc = p; + swtch(&c->context, &p->context); + + // Process is done running for now. + // It should have changed its p->state before coming back. + c->proc = 0; + found = 1; + } + release(&p->lock); + } + if(found == 0) { + // nothing to run; stop running on this core until an interrupt. + intr_on(); + asm volatile("wfi"); + } + } +} + +// Switch to scheduler. Must hold only p->lock +// and have changed proc->state. Saves and restores +// intena because intena is a property of this +// kernel thread, not this CPU. It should +// be proc->intena and proc->noff, but that would +// break in the few places where a lock is held but +// there's no process. +void +sched(void) +{ + int intena; + struct proc *p = myproc(); + + if(!holding(&p->lock)) + panic("sched p->lock"); + if(mycpu()->noff != 1) + panic("sched locks"); + if(p->state == RUNNING) + panic("sched running"); + if(intr_get()) + panic("sched interruptible"); + + intena = mycpu()->intena; + swtch(&p->context, &mycpu()->context); + mycpu()->intena = intena; +} + +// Give up the CPU for one scheduling round. +void +yield(void) +{ + struct proc *p = myproc(); + acquire(&p->lock); + p->state = RUNNABLE; + sched(); + release(&p->lock); +} + +// A fork child's very first scheduling by scheduler() +// will swtch to forkret. +void +forkret(void) +{ + static int first = 1; + + // Still holding p->lock from scheduler. + release(&myproc()->lock); + + if (first) { + // File system initialization must be run in the context of a + // regular process (e.g., because it calls sleep), and thus cannot + // be run from main(). + fsinit(ROOTDEV); + + first = 0; + // ensure other cores see first=0. + __sync_synchronize(); + } + + usertrapret(); +} + +// Atomically release lock and sleep on chan. +// Reacquires lock when awakened. +void +sleep(void *chan, struct spinlock *lk) +{ + struct proc *p = myproc(); + + // Must acquire p->lock in order to + // change p->state and then call sched. + // Once we hold p->lock, we can be + // guaranteed that we won't miss any wakeup + // (wakeup locks p->lock), + // so it's okay to release lk. + + acquire(&p->lock); //DOC: sleeplock1 + release(lk); + + // Go to sleep. + p->chan = chan; + p->state = SLEEPING; + + sched(); + + // Tidy up. + p->chan = 0; + + // Reacquire original lock. + release(&p->lock); + acquire(lk); +} + +// Wake up all processes sleeping on chan. +// Must be called without any p->lock. +void +wakeup(void *chan) +{ + struct proc *p; + + for(p = proc; p < &proc[NPROC]; p++) { + if(p != myproc()){ + acquire(&p->lock); + if(p->state == SLEEPING && p->chan == chan) { + p->state = RUNNABLE; + } + release(&p->lock); + } + } +} + +// Kill the process with the given pid. +// The victim won't exit until it tries to return +// to user space (see usertrap() in trap.c). +int +kill(int pid) +{ + struct proc *p; + + for(p = proc; p < &proc[NPROC]; p++){ + acquire(&p->lock); + if(p->pid == pid){ + p->killed = 1; + if(p->state == SLEEPING){ + // Wake process from sleep(). + p->state = RUNNABLE; + } + release(&p->lock); + return 0; + } + release(&p->lock); + } + return -1; +} + +void +setkilled(struct proc *p) +{ + acquire(&p->lock); + p->killed = 1; + release(&p->lock); +} + +int +killed(struct proc *p) +{ + int k; + + acquire(&p->lock); + k = p->killed; + release(&p->lock); + return k; +} + +// Copy to either a user address, or kernel address, +// depending on usr_dst. +// Returns 0 on success, -1 on error. +int +either_copyout(int user_dst, uint64 dst, void *src, uint64 len) +{ + struct proc *p = myproc(); + if(user_dst){ + return copyout(p->pagetable, dst, src, len); + } else { + memmove((char *)dst, src, len); + return 0; + } +} + +// Copy from either a user address, or kernel address, +// depending on usr_src. +// Returns 0 on success, -1 on error. +int +either_copyin(void *dst, int user_src, uint64 src, uint64 len) +{ + struct proc *p = myproc(); + if(user_src){ + return copyin(p->pagetable, dst, src, len); + } else { + memmove(dst, (char*)src, len); + return 0; + } +} + +// Print a process listing to console. For debugging. +// Runs when user types ^P on console. +// No lock to avoid wedging a stuck machine further. +void +procdump(void) +{ + static char *states[] = { + [UNUSED] "unused", + [USED] "used", + [SLEEPING] "sleep ", + [RUNNABLE] "runble", + [RUNNING] "run ", + [ZOMBIE] "zombie" + }; + struct proc *p; + char *state; + + printf("\n"); + for(p = proc; p < &proc[NPROC]; p++){ + if(p->state == UNUSED) + continue; + if(p->state >= 0 && p->state < NELEM(states) && states[p->state]) + state = states[p->state]; + else + state = "???"; + printf("%d %s %s", p->pid, state, p->name); + printf("\n"); + } +} diff --git a/1/xv6-riscv-riscv/kernel/proc.h b/1/xv6-riscv-riscv/kernel/proc.h new file mode 100644 index 0000000000000000000000000000000000000000..d021857a3430c1b4bfb7792ad616ec986ef3874b --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/proc.h @@ -0,0 +1,107 @@ +// Saved registers for kernel context switches. +struct context { + uint64 ra; + uint64 sp; + + // callee-saved + uint64 s0; + uint64 s1; + uint64 s2; + uint64 s3; + uint64 s4; + uint64 s5; + uint64 s6; + uint64 s7; + uint64 s8; + uint64 s9; + uint64 s10; + uint64 s11; +}; + +// Per-CPU state. +struct cpu { + struct proc *proc; // The process running on this cpu, or null. + struct context context; // swtch() here to enter scheduler(). + int noff; // Depth of push_off() nesting. + int intena; // Were interrupts enabled before push_off()? +}; + +extern struct cpu cpus[NCPU]; + +// per-process data for the trap handling code in trampoline.S. +// sits in a page by itself just under the trampoline page in the +// user page table. not specially mapped in the kernel page table. +// uservec in trampoline.S saves user registers in the trapframe, +// then initializes registers from the trapframe's +// kernel_sp, kernel_hartid, kernel_satp, and jumps to kernel_trap. +// usertrapret() and userret in trampoline.S set up +// the trapframe's kernel_*, restore user registers from the +// trapframe, switch to the user page table, and enter user space. +// the trapframe includes callee-saved user registers like s0-s11 because the +// return-to-user path via usertrapret() doesn't return through +// the entire kernel call stack. +struct trapframe { + /* 0 */ uint64 kernel_satp; // kernel page table + /* 8 */ uint64 kernel_sp; // top of process's kernel stack + /* 16 */ uint64 kernel_trap; // usertrap() + /* 24 */ uint64 epc; // saved user program counter + /* 32 */ uint64 kernel_hartid; // saved kernel tp + /* 40 */ uint64 ra; + /* 48 */ uint64 sp; + /* 56 */ uint64 gp; + /* 64 */ uint64 tp; + /* 72 */ uint64 t0; + /* 80 */ uint64 t1; + /* 88 */ uint64 t2; + /* 96 */ uint64 s0; + /* 104 */ uint64 s1; + /* 112 */ uint64 a0; + /* 120 */ uint64 a1; + /* 128 */ uint64 a2; + /* 136 */ uint64 a3; + /* 144 */ uint64 a4; + /* 152 */ uint64 a5; + /* 160 */ uint64 a6; + /* 168 */ uint64 a7; + /* 176 */ uint64 s2; + /* 184 */ uint64 s3; + /* 192 */ uint64 s4; + /* 200 */ uint64 s5; + /* 208 */ uint64 s6; + /* 216 */ uint64 s7; + /* 224 */ uint64 s8; + /* 232 */ uint64 s9; + /* 240 */ uint64 s10; + /* 248 */ uint64 s11; + /* 256 */ uint64 t3; + /* 264 */ uint64 t4; + /* 272 */ uint64 t5; + /* 280 */ uint64 t6; +}; + +enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE }; + +// Per-process state +struct proc { + struct spinlock lock; + + // p->lock must be held when using these: + enum procstate state; // Process state + void *chan; // If non-zero, sleeping on chan + int killed; // If non-zero, have been killed + int xstate; // Exit status to be returned to parent's wait + int pid; // Process ID + + // wait_lock must be held when using this: + struct proc *parent; // Parent process + + // these are private to the process, so p->lock need not be held. + uint64 kstack; // Virtual address of kernel stack + uint64 sz; // Size of process memory (bytes) + pagetable_t pagetable; // User page table + struct trapframe *trapframe; // data page for trampoline.S + struct context context; // swtch() here to run process + struct file *ofile[NOFILE]; // Open files + struct inode *cwd; // Current directory + char name[16]; // Process name (debugging) +}; diff --git a/1/xv6-riscv-riscv/kernel/riscv.h b/1/xv6-riscv-riscv/kernel/riscv.h new file mode 100644 index 0000000000000000000000000000000000000000..f7aaa8a2b8386e14b2783883113d6a687b04d8b1 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/riscv.h @@ -0,0 +1,382 @@ +#ifndef __ASSEMBLER__ + +// which hart (core) is this? +static inline uint64 +r_mhartid() +{ + uint64 x; + asm volatile("csrr %0, mhartid" : "=r" (x) ); + return x; +} + +// Machine Status Register, mstatus + +#define MSTATUS_MPP_MASK (3L << 11) // previous mode. +#define MSTATUS_MPP_M (3L << 11) +#define MSTATUS_MPP_S (1L << 11) +#define MSTATUS_MPP_U (0L << 11) +#define MSTATUS_MIE (1L << 3) // machine-mode interrupt enable. + +static inline uint64 +r_mstatus() +{ + uint64 x; + asm volatile("csrr %0, mstatus" : "=r" (x) ); + return x; +} + +static inline void +w_mstatus(uint64 x) +{ + asm volatile("csrw mstatus, %0" : : "r" (x)); +} + +// machine exception program counter, holds the +// instruction address to which a return from +// exception will go. +static inline void +w_mepc(uint64 x) +{ + asm volatile("csrw mepc, %0" : : "r" (x)); +} + +// Supervisor Status Register, sstatus + +#define SSTATUS_SPP (1L << 8) // Previous mode, 1=Supervisor, 0=User +#define SSTATUS_SPIE (1L << 5) // Supervisor Previous Interrupt Enable +#define SSTATUS_UPIE (1L << 4) // User Previous Interrupt Enable +#define SSTATUS_SIE (1L << 1) // Supervisor Interrupt Enable +#define SSTATUS_UIE (1L << 0) // User Interrupt Enable + +static inline uint64 +r_sstatus() +{ + uint64 x; + asm volatile("csrr %0, sstatus" : "=r" (x) ); + return x; +} + +static inline void +w_sstatus(uint64 x) +{ + asm volatile("csrw sstatus, %0" : : "r" (x)); +} + +// Supervisor Interrupt Pending +static inline uint64 +r_sip() +{ + uint64 x; + asm volatile("csrr %0, sip" : "=r" (x) ); + return x; +} + +static inline void +w_sip(uint64 x) +{ + asm volatile("csrw sip, %0" : : "r" (x)); +} + +// Supervisor Interrupt Enable +#define SIE_SEIE (1L << 9) // external +#define SIE_STIE (1L << 5) // timer +#define SIE_SSIE (1L << 1) // software +static inline uint64 +r_sie() +{ + uint64 x; + asm volatile("csrr %0, sie" : "=r" (x) ); + return x; +} + +static inline void +w_sie(uint64 x) +{ + asm volatile("csrw sie, %0" : : "r" (x)); +} + +// Machine-mode Interrupt Enable +#define MIE_STIE (1L << 5) // supervisor timer +static inline uint64 +r_mie() +{ + uint64 x; + asm volatile("csrr %0, mie" : "=r" (x) ); + return x; +} + +static inline void +w_mie(uint64 x) +{ + asm volatile("csrw mie, %0" : : "r" (x)); +} + +// supervisor exception program counter, holds the +// instruction address to which a return from +// exception will go. +static inline void +w_sepc(uint64 x) +{ + asm volatile("csrw sepc, %0" : : "r" (x)); +} + +static inline uint64 +r_sepc() +{ + uint64 x; + asm volatile("csrr %0, sepc" : "=r" (x) ); + return x; +} + +// Machine Exception Delegation +static inline uint64 +r_medeleg() +{ + uint64 x; + asm volatile("csrr %0, medeleg" : "=r" (x) ); + return x; +} + +static inline void +w_medeleg(uint64 x) +{ + asm volatile("csrw medeleg, %0" : : "r" (x)); +} + +// Machine Interrupt Delegation +static inline uint64 +r_mideleg() +{ + uint64 x; + asm volatile("csrr %0, mideleg" : "=r" (x) ); + return x; +} + +static inline void +w_mideleg(uint64 x) +{ + asm volatile("csrw mideleg, %0" : : "r" (x)); +} + +// Supervisor Trap-Vector Base Address +// low two bits are mode. +static inline void +w_stvec(uint64 x) +{ + asm volatile("csrw stvec, %0" : : "r" (x)); +} + +static inline uint64 +r_stvec() +{ + uint64 x; + asm volatile("csrr %0, stvec" : "=r" (x) ); + return x; +} + +// Supervisor Timer Comparison Register +static inline uint64 +r_stimecmp() +{ + uint64 x; + // asm volatile("csrr %0, stimecmp" : "=r" (x) ); + asm volatile("csrr %0, 0x14d" : "=r" (x) ); + return x; +} + +static inline void +w_stimecmp(uint64 x) +{ + // asm volatile("csrw stimecmp, %0" : : "r" (x)); + asm volatile("csrw 0x14d, %0" : : "r" (x)); +} + +// Machine Environment Configuration Register +static inline uint64 +r_menvcfg() +{ + uint64 x; + // asm volatile("csrr %0, menvcfg" : "=r" (x) ); + asm volatile("csrr %0, 0x30a" : "=r" (x) ); + return x; +} + +static inline void +w_menvcfg(uint64 x) +{ + // asm volatile("csrw menvcfg, %0" : : "r" (x)); + asm volatile("csrw 0x30a, %0" : : "r" (x)); +} + +// Physical Memory Protection +static inline void +w_pmpcfg0(uint64 x) +{ + asm volatile("csrw pmpcfg0, %0" : : "r" (x)); +} + +static inline void +w_pmpaddr0(uint64 x) +{ + asm volatile("csrw pmpaddr0, %0" : : "r" (x)); +} + +// use riscv's sv39 page table scheme. +#define SATP_SV39 (8L << 60) + +#define MAKE_SATP(pagetable) (SATP_SV39 | (((uint64)pagetable) >> 12)) + +// supervisor address translation and protection; +// holds the address of the page table. +static inline void +w_satp(uint64 x) +{ + asm volatile("csrw satp, %0" : : "r" (x)); +} + +static inline uint64 +r_satp() +{ + uint64 x; + asm volatile("csrr %0, satp" : "=r" (x) ); + return x; +} + +// Supervisor Trap Cause +static inline uint64 +r_scause() +{ + uint64 x; + asm volatile("csrr %0, scause" : "=r" (x) ); + return x; +} + +// Supervisor Trap Value +static inline uint64 +r_stval() +{ + uint64 x; + asm volatile("csrr %0, stval" : "=r" (x) ); + return x; +} + +// Machine-mode Counter-Enable +static inline void +w_mcounteren(uint64 x) +{ + asm volatile("csrw mcounteren, %0" : : "r" (x)); +} + +static inline uint64 +r_mcounteren() +{ + uint64 x; + asm volatile("csrr %0, mcounteren" : "=r" (x) ); + return x; +} + +// machine-mode cycle counter +static inline uint64 +r_time() +{ + uint64 x; + asm volatile("csrr %0, time" : "=r" (x) ); + return x; +} + +// enable device interrupts +static inline void +intr_on() +{ + w_sstatus(r_sstatus() | SSTATUS_SIE); +} + +// disable device interrupts +static inline void +intr_off() +{ + w_sstatus(r_sstatus() & ~SSTATUS_SIE); +} + +// are device interrupts enabled? +static inline int +intr_get() +{ + uint64 x = r_sstatus(); + return (x & SSTATUS_SIE) != 0; +} + +static inline uint64 +r_sp() +{ + uint64 x; + asm volatile("mv %0, sp" : "=r" (x) ); + return x; +} + +// read and write tp, the thread pointer, which xv6 uses to hold +// this core's hartid (core number), the index into cpus[]. +static inline uint64 +r_tp() +{ + uint64 x; + asm volatile("mv %0, tp" : "=r" (x) ); + return x; +} + +static inline void +w_tp(uint64 x) +{ + asm volatile("mv tp, %0" : : "r" (x)); +} + +static inline uint64 +r_ra() +{ + uint64 x; + asm volatile("mv %0, ra" : "=r" (x) ); + return x; +} + +// flush the TLB. +static inline void +sfence_vma() +{ + // the zero, zero means flush all TLB entries. + asm volatile("sfence.vma zero, zero"); +} + +typedef uint64 pte_t; +typedef uint64 *pagetable_t; // 512 PTEs + +#endif // __ASSEMBLER__ + +#define PGSIZE 4096 // bytes per page +#define PGSHIFT 12 // bits of offset within a page + +#define PGROUNDUP(sz) (((sz)+PGSIZE-1) & ~(PGSIZE-1)) +#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE-1)) + +#define PTE_V (1L << 0) // valid +#define PTE_R (1L << 1) +#define PTE_W (1L << 2) +#define PTE_X (1L << 3) +#define PTE_U (1L << 4) // user can access + +// shift a physical address to the right place for a PTE. +#define PA2PTE(pa) ((((uint64)pa) >> 12) << 10) + +#define PTE2PA(pte) (((pte) >> 10) << 12) + +#define PTE_FLAGS(pte) ((pte) & 0x3FF) + +// extract the three 9-bit page table indices from a virtual address. +#define PXMASK 0x1FF // 9 bits +#define PXSHIFT(level) (PGSHIFT+(9*(level))) +#define PX(level, va) ((((uint64) (va)) >> PXSHIFT(level)) & PXMASK) + +// one beyond the highest possible virtual address. +// MAXVA is actually one bit less than the max allowed by +// Sv39, to avoid having to sign-extend virtual addresses +// that have the high bit set. +#define MAXVA (1L << (9 + 9 + 9 + 12 - 1)) diff --git a/1/xv6-riscv-riscv/kernel/sleeplock.c b/1/xv6-riscv-riscv/kernel/sleeplock.c new file mode 100644 index 0000000000000000000000000000000000000000..81de58572b85c31b179d2668b6ec99e46c28962c --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/sleeplock.c @@ -0,0 +1,55 @@ +// Sleeping locks + +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "spinlock.h" +#include "proc.h" +#include "sleeplock.h" + +void +initsleeplock(struct sleeplock *lk, char *name) +{ + initlock(&lk->lk, "sleep lock"); + lk->name = name; + lk->locked = 0; + lk->pid = 0; +} + +void +acquiresleep(struct sleeplock *lk) +{ + acquire(&lk->lk); + while (lk->locked) { + sleep(lk, &lk->lk); + } + lk->locked = 1; + lk->pid = myproc()->pid; + release(&lk->lk); +} + +void +releasesleep(struct sleeplock *lk) +{ + acquire(&lk->lk); + lk->locked = 0; + lk->pid = 0; + wakeup(lk); + release(&lk->lk); +} + +int +holdingsleep(struct sleeplock *lk) +{ + int r; + + acquire(&lk->lk); + r = lk->locked && (lk->pid == myproc()->pid); + release(&lk->lk); + return r; +} + + + diff --git a/1/xv6-riscv-riscv/kernel/sleeplock.h b/1/xv6-riscv-riscv/kernel/sleeplock.h new file mode 100644 index 0000000000000000000000000000000000000000..110e6f3d7f0ea289b5907ea4072bd5663d5734b2 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/sleeplock.h @@ -0,0 +1,10 @@ +// Long-term locks for processes +struct sleeplock { + uint locked; // Is the lock held? + struct spinlock lk; // spinlock protecting this sleep lock + + // For debugging: + char *name; // Name of lock. + int pid; // Process holding lock +}; + diff --git a/1/xv6-riscv-riscv/kernel/spinlock.c b/1/xv6-riscv-riscv/kernel/spinlock.c new file mode 100644 index 0000000000000000000000000000000000000000..9840302f8c57b6271fa6c89622ff3122aca5c118 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/spinlock.c @@ -0,0 +1,110 @@ +// Mutual exclusion spin locks. + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "spinlock.h" +#include "riscv.h" +#include "proc.h" +#include "defs.h" + +void +initlock(struct spinlock *lk, char *name) +{ + lk->name = name; + lk->locked = 0; + lk->cpu = 0; +} + +// Acquire the lock. +// Loops (spins) until the lock is acquired. +void +acquire(struct spinlock *lk) +{ + push_off(); // disable interrupts to avoid deadlock. + if(holding(lk)) + panic("acquire"); + + // On RISC-V, sync_lock_test_and_set turns into an atomic swap: + // a5 = 1 + // s1 = &lk->locked + // amoswap.w.aq a5, a5, (s1) + while(__sync_lock_test_and_set(&lk->locked, 1) != 0) + ; + + // Tell the C compiler and the processor to not move loads or stores + // past this point, to ensure that the critical section's memory + // references happen strictly after the lock is acquired. + // On RISC-V, this emits a fence instruction. + __sync_synchronize(); + + // Record info about lock acquisition for holding() and debugging. + lk->cpu = mycpu(); +} + +// Release the lock. +void +release(struct spinlock *lk) +{ + if(!holding(lk)) + panic("release"); + + lk->cpu = 0; + + // Tell the C compiler and the CPU to not move loads or stores + // past this point, to ensure that all the stores in the critical + // section are visible to other CPUs before the lock is released, + // and that loads in the critical section occur strictly before + // the lock is released. + // On RISC-V, this emits a fence instruction. + __sync_synchronize(); + + // Release the lock, equivalent to lk->locked = 0. + // This code doesn't use a C assignment, since the C standard + // implies that an assignment might be implemented with + // multiple store instructions. + // On RISC-V, sync_lock_release turns into an atomic swap: + // s1 = &lk->locked + // amoswap.w zero, zero, (s1) + __sync_lock_release(&lk->locked); + + pop_off(); +} + +// Check whether this cpu is holding the lock. +// Interrupts must be off. +int +holding(struct spinlock *lk) +{ + int r; + r = (lk->locked && lk->cpu == mycpu()); + return r; +} + +// push_off/pop_off are like intr_off()/intr_on() except that they are matched: +// it takes two pop_off()s to undo two push_off()s. Also, if interrupts +// are initially off, then push_off, pop_off leaves them off. + +void +push_off(void) +{ + int old = intr_get(); + + intr_off(); + if(mycpu()->noff == 0) + mycpu()->intena = old; + mycpu()->noff += 1; +} + +void +pop_off(void) +{ + struct cpu *c = mycpu(); + if(intr_get()) + panic("pop_off - interruptible"); + if(c->noff < 1) + panic("pop_off"); + c->noff -= 1; + if(c->noff == 0 && c->intena) + intr_on(); +} diff --git a/1/xv6-riscv-riscv/kernel/spinlock.h b/1/xv6-riscv-riscv/kernel/spinlock.h new file mode 100644 index 0000000000000000000000000000000000000000..4392820d021114b6e78f01b2ac4c8fd80eccb07b --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/spinlock.h @@ -0,0 +1,9 @@ +// Mutual exclusion lock. +struct spinlock { + uint locked; // Is the lock held? + + // For debugging: + char *name; // Name of lock. + struct cpu *cpu; // The cpu holding the lock. +}; + diff --git a/1/xv6-riscv-riscv/kernel/start.c b/1/xv6-riscv-riscv/kernel/start.c new file mode 100644 index 0000000000000000000000000000000000000000..9ee35f1dfcf1308bd5984fdfaa819d45806f5220 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/start.c @@ -0,0 +1,66 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "defs.h" + +void main(); +void timerinit(); + +// entry.S needs one stack per CPU. +__attribute__ ((aligned (16))) char stack0[4096 * NCPU]; + +// entry.S jumps here in machine mode on stack0. +void +start() +{ + // set M Previous Privilege mode to Supervisor, for mret. + unsigned long x = r_mstatus(); + x &= ~MSTATUS_MPP_MASK; + x |= MSTATUS_MPP_S; + w_mstatus(x); + + // set M Exception Program Counter to main, for mret. + // requires gcc -mcmodel=medany + w_mepc((uint64)main); + + // disable paging for now. + w_satp(0); + + // delegate all interrupts and exceptions to supervisor mode. + w_medeleg(0xffff); + w_mideleg(0xffff); + w_sie(r_sie() | SIE_SEIE | SIE_STIE | SIE_SSIE); + + // configure Physical Memory Protection to give supervisor mode + // access to all of physical memory. + w_pmpaddr0(0x3fffffffffffffull); + w_pmpcfg0(0xf); + + // ask for clock interrupts. + timerinit(); + + // keep each CPU's hartid in its tp register, for cpuid(). + int id = r_mhartid(); + w_tp(id); + + // switch to supervisor mode and jump to main(). + asm volatile("mret"); +} + +// ask each hart to generate timer interrupts. +void +timerinit() +{ + // enable supervisor-mode timer interrupts. + w_mie(r_mie() | MIE_STIE); + + // enable the sstc extension (i.e. stimecmp). + w_menvcfg(r_menvcfg() | (1L << 63)); + + // allow supervisor to use stimecmp and time. + w_mcounteren(r_mcounteren() | 2); + + // ask for the very first timer interrupt. + w_stimecmp(r_time() + 1000000); +} diff --git a/1/xv6-riscv-riscv/kernel/stat.h b/1/xv6-riscv-riscv/kernel/stat.h new file mode 100644 index 0000000000000000000000000000000000000000..19543afdf206d9d165812bf32a5a9eea6633f3be --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/stat.h @@ -0,0 +1,11 @@ +#define T_DIR 1 // Directory +#define T_FILE 2 // File +#define T_DEVICE 3 // Device + +struct stat { + int dev; // File system's disk device + uint ino; // Inode number + short type; // Type of file + short nlink; // Number of links to file + uint64 size; // Size of file in bytes +}; diff --git a/1/xv6-riscv-riscv/kernel/string.c b/1/xv6-riscv-riscv/kernel/string.c new file mode 100644 index 0000000000000000000000000000000000000000..153536fc11c03cf1dbd968e7f335af9f4d027a09 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/string.c @@ -0,0 +1,107 @@ +#include "types.h" + +void* +memset(void *dst, int c, uint n) +{ + char *cdst = (char *) dst; + int i; + for(i = 0; i < n; i++){ + cdst[i] = c; + } + return dst; +} + +int +memcmp(const void *v1, const void *v2, uint n) +{ + const uchar *s1, *s2; + + s1 = v1; + s2 = v2; + while(n-- > 0){ + if(*s1 != *s2) + return *s1 - *s2; + s1++, s2++; + } + + return 0; +} + +void* +memmove(void *dst, const void *src, uint n) +{ + const char *s; + char *d; + + if(n == 0) + return dst; + + s = src; + d = dst; + if(s < d && s + n > d){ + s += n; + d += n; + while(n-- > 0) + *--d = *--s; + } else + while(n-- > 0) + *d++ = *s++; + + return dst; +} + +// memcpy exists to placate GCC. Use memmove. +void* +memcpy(void *dst, const void *src, uint n) +{ + return memmove(dst, src, n); +} + +int +strncmp(const char *p, const char *q, uint n) +{ + while(n > 0 && *p && *p == *q) + n--, p++, q++; + if(n == 0) + return 0; + return (uchar)*p - (uchar)*q; +} + +char* +strncpy(char *s, const char *t, int n) +{ + char *os; + + os = s; + while(n-- > 0 && (*s++ = *t++) != 0) + ; + while(n-- > 0) + *s++ = 0; + return os; +} + +// Like strncpy but guaranteed to NUL-terminate. +char* +safestrcpy(char *s, const char *t, int n) +{ + char *os; + + os = s; + if(n <= 0) + return os; + while(--n > 0 && (*s++ = *t++) != 0) + ; + *s = 0; + return os; +} + +int +strlen(const char *s) +{ + int n; + + for(n = 0; s[n]; n++) + ; + return n; +} + diff --git a/1/xv6-riscv-riscv/kernel/swtch.S b/1/xv6-riscv-riscv/kernel/swtch.S new file mode 100644 index 0000000000000000000000000000000000000000..17a86637cb1f94b66b1369bbcc45e4f8b72feb88 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/swtch.S @@ -0,0 +1,42 @@ +# Context switch +# +# void swtch(struct context *old, struct context *new); +# +# Save current registers in old. Load from new. + + +.globl swtch +swtch: + sd ra, 0(a0) + sd sp, 8(a0) + sd s0, 16(a0) + sd s1, 24(a0) + sd s2, 32(a0) + sd s3, 40(a0) + sd s4, 48(a0) + sd s5, 56(a0) + sd s6, 64(a0) + sd s7, 72(a0) + sd s8, 80(a0) + sd s9, 88(a0) + sd s10, 96(a0) + sd s11, 104(a0) + + ld ra, 0(a1) + ld sp, 8(a1) + ld s0, 16(a1) + ld s1, 24(a1) + ld s2, 32(a1) + ld s3, 40(a1) + ld s4, 48(a1) + ld s5, 56(a1) + ld s6, 64(a1) + ld s7, 72(a1) + ld s8, 80(a1) + ld s9, 88(a1) + ld s10, 96(a1) + ld s11, 104(a1) + + ret + + diff --git a/1/xv6-riscv-riscv/kernel/syscall.c b/1/xv6-riscv-riscv/kernel/syscall.c new file mode 100644 index 0000000000000000000000000000000000000000..ed654094cbe24566803fed436126b7773a0b0541 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/syscall.c @@ -0,0 +1,147 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "syscall.h" +#include "defs.h" + +// Fetch the uint64 at addr from the current process. +int +fetchaddr(uint64 addr, uint64 *ip) +{ + struct proc *p = myproc(); + if(addr >= p->sz || addr+sizeof(uint64) > p->sz) // both tests needed, in case of overflow + return -1; + if(copyin(p->pagetable, (char *)ip, addr, sizeof(*ip)) != 0) + return -1; + return 0; +} + +// Fetch the nul-terminated string at addr from the current process. +// Returns length of string, not including nul, or -1 for error. +int +fetchstr(uint64 addr, char *buf, int max) +{ + struct proc *p = myproc(); + if(copyinstr(p->pagetable, buf, addr, max) < 0) + return -1; + return strlen(buf); +} + +static uint64 +argraw(int n) +{ + struct proc *p = myproc(); + switch (n) { + case 0: + return p->trapframe->a0; + case 1: + return p->trapframe->a1; + case 2: + return p->trapframe->a2; + case 3: + return p->trapframe->a3; + case 4: + return p->trapframe->a4; + case 5: + return p->trapframe->a5; + } + panic("argraw"); + return -1; +} + +// Fetch the nth 32-bit system call argument. +void +argint(int n, int *ip) +{ + *ip = argraw(n); +} + +// Retrieve an argument as a pointer. +// Doesn't check for legality, since +// copyin/copyout will do that. +void +argaddr(int n, uint64 *ip) +{ + *ip = argraw(n); +} + +// Fetch the nth word-sized system call argument as a null-terminated string. +// Copies into buf, at most max. +// Returns string length if OK (including nul), -1 if error. +int +argstr(int n, char *buf, int max) +{ + uint64 addr; + argaddr(n, &addr); + return fetchstr(addr, buf, max); +} + +// Prototypes for the functions that handle system calls. +extern uint64 sys_fork(void); +extern uint64 sys_exit(void); +extern uint64 sys_wait(void); +extern uint64 sys_pipe(void); +extern uint64 sys_read(void); +extern uint64 sys_kill(void); +extern uint64 sys_exec(void); +extern uint64 sys_fstat(void); +extern uint64 sys_chdir(void); +extern uint64 sys_dup(void); +extern uint64 sys_getpid(void); +extern uint64 sys_sbrk(void); +extern uint64 sys_sleep(void); +extern uint64 sys_uptime(void); +extern uint64 sys_open(void); +extern uint64 sys_write(void); +extern uint64 sys_mknod(void); +extern uint64 sys_unlink(void); +extern uint64 sys_link(void); +extern uint64 sys_mkdir(void); +extern uint64 sys_close(void); + +// An array mapping syscall numbers from syscall.h +// to the function that handles the system call. +static uint64 (*syscalls[])(void) = { +[SYS_fork] sys_fork, +[SYS_exit] sys_exit, +[SYS_wait] sys_wait, +[SYS_pipe] sys_pipe, +[SYS_read] sys_read, +[SYS_kill] sys_kill, +[SYS_exec] sys_exec, +[SYS_fstat] sys_fstat, +[SYS_chdir] sys_chdir, +[SYS_dup] sys_dup, +[SYS_getpid] sys_getpid, +[SYS_sbrk] sys_sbrk, +[SYS_sleep] sys_sleep, +[SYS_uptime] sys_uptime, +[SYS_open] sys_open, +[SYS_write] sys_write, +[SYS_mknod] sys_mknod, +[SYS_unlink] sys_unlink, +[SYS_link] sys_link, +[SYS_mkdir] sys_mkdir, +[SYS_close] sys_close, +}; + +void +syscall(void) +{ + int num; + struct proc *p = myproc(); + + num = p->trapframe->a7; + if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { + // Use num to lookup the system call function for num, call it, + // and store its return value in p->trapframe->a0 + p->trapframe->a0 = syscalls[num](); + } else { + printf("%d %s: unknown sys call %d\n", + p->pid, p->name, num); + p->trapframe->a0 = -1; + } +} diff --git a/1/xv6-riscv-riscv/kernel/syscall.h b/1/xv6-riscv-riscv/kernel/syscall.h new file mode 100644 index 0000000000000000000000000000000000000000..bc5f35651c0e0e6d79e60a80b778962429832aeb --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/syscall.h @@ -0,0 +1,22 @@ +// System call numbers +#define SYS_fork 1 +#define SYS_exit 2 +#define SYS_wait 3 +#define SYS_pipe 4 +#define SYS_read 5 +#define SYS_kill 6 +#define SYS_exec 7 +#define SYS_fstat 8 +#define SYS_chdir 9 +#define SYS_dup 10 +#define SYS_getpid 11 +#define SYS_sbrk 12 +#define SYS_sleep 13 +#define SYS_uptime 14 +#define SYS_open 15 +#define SYS_write 16 +#define SYS_mknod 17 +#define SYS_unlink 18 +#define SYS_link 19 +#define SYS_mkdir 20 +#define SYS_close 21 diff --git a/1/xv6-riscv-riscv/kernel/sysfile.c b/1/xv6-riscv-riscv/kernel/sysfile.c new file mode 100644 index 0000000000000000000000000000000000000000..16b668cb3a87d9256387599aead5bb1a8aa59589 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/sysfile.c @@ -0,0 +1,505 @@ +// +// File-system system calls. +// Mostly argument checking, since we don't trust +// user code, and calls into file.c and fs.c. +// + +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "stat.h" +#include "spinlock.h" +#include "proc.h" +#include "fs.h" +#include "sleeplock.h" +#include "file.h" +#include "fcntl.h" + +// Fetch the nth word-sized system call argument as a file descriptor +// and return both the descriptor and the corresponding struct file. +static int +argfd(int n, int *pfd, struct file **pf) +{ + int fd; + struct file *f; + + argint(n, &fd); + if(fd < 0 || fd >= NOFILE || (f=myproc()->ofile[fd]) == 0) + return -1; + if(pfd) + *pfd = fd; + if(pf) + *pf = f; + return 0; +} + +// Allocate a file descriptor for the given file. +// Takes over file reference from caller on success. +static int +fdalloc(struct file *f) +{ + int fd; + struct proc *p = myproc(); + + for(fd = 0; fd < NOFILE; fd++){ + if(p->ofile[fd] == 0){ + p->ofile[fd] = f; + return fd; + } + } + return -1; +} + +uint64 +sys_dup(void) +{ + struct file *f; + int fd; + + if(argfd(0, 0, &f) < 0) + return -1; + if((fd=fdalloc(f)) < 0) + return -1; + filedup(f); + return fd; +} + +uint64 +sys_read(void) +{ + struct file *f; + int n; + uint64 p; + + argaddr(1, &p); + argint(2, &n); + if(argfd(0, 0, &f) < 0) + return -1; + return fileread(f, p, n); +} + +uint64 +sys_write(void) +{ + struct file *f; + int n; + uint64 p; + + argaddr(1, &p); + argint(2, &n); + if(argfd(0, 0, &f) < 0) + return -1; + + return filewrite(f, p, n); +} + +uint64 +sys_close(void) +{ + int fd; + struct file *f; + + if(argfd(0, &fd, &f) < 0) + return -1; + myproc()->ofile[fd] = 0; + fileclose(f); + return 0; +} + +uint64 +sys_fstat(void) +{ + struct file *f; + uint64 st; // user pointer to struct stat + + argaddr(1, &st); + if(argfd(0, 0, &f) < 0) + return -1; + return filestat(f, st); +} + +// Create the path new as a link to the same inode as old. +uint64 +sys_link(void) +{ + char name[DIRSIZ], new[MAXPATH], old[MAXPATH]; + struct inode *dp, *ip; + + if(argstr(0, old, MAXPATH) < 0 || argstr(1, new, MAXPATH) < 0) + return -1; + + begin_op(); + if((ip = namei(old)) == 0){ + end_op(); + return -1; + } + + ilock(ip); + if(ip->type == T_DIR){ + iunlockput(ip); + end_op(); + return -1; + } + + ip->nlink++; + iupdate(ip); + iunlock(ip); + + if((dp = nameiparent(new, name)) == 0) + goto bad; + ilock(dp); + if(dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0){ + iunlockput(dp); + goto bad; + } + iunlockput(dp); + iput(ip); + + end_op(); + + return 0; + +bad: + ilock(ip); + ip->nlink--; + iupdate(ip); + iunlockput(ip); + end_op(); + return -1; +} + +// Is the directory dp empty except for "." and ".." ? +static int +isdirempty(struct inode *dp) +{ + int off; + struct dirent de; + + for(off=2*sizeof(de); off<dp->size; off+=sizeof(de)){ + if(readi(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) + panic("isdirempty: readi"); + if(de.inum != 0) + return 0; + } + return 1; +} + +uint64 +sys_unlink(void) +{ + struct inode *ip, *dp; + struct dirent de; + char name[DIRSIZ], path[MAXPATH]; + uint off; + + if(argstr(0, path, MAXPATH) < 0) + return -1; + + begin_op(); + if((dp = nameiparent(path, name)) == 0){ + end_op(); + return -1; + } + + ilock(dp); + + // Cannot unlink "." or "..". + if(namecmp(name, ".") == 0 || namecmp(name, "..") == 0) + goto bad; + + if((ip = dirlookup(dp, name, &off)) == 0) + goto bad; + ilock(ip); + + if(ip->nlink < 1) + panic("unlink: nlink < 1"); + if(ip->type == T_DIR && !isdirempty(ip)){ + iunlockput(ip); + goto bad; + } + + memset(&de, 0, sizeof(de)); + if(writei(dp, 0, (uint64)&de, off, sizeof(de)) != sizeof(de)) + panic("unlink: writei"); + if(ip->type == T_DIR){ + dp->nlink--; + iupdate(dp); + } + iunlockput(dp); + + ip->nlink--; + iupdate(ip); + iunlockput(ip); + + end_op(); + + return 0; + +bad: + iunlockput(dp); + end_op(); + return -1; +} + +static struct inode* +create(char *path, short type, short major, short minor) +{ + struct inode *ip, *dp; + char name[DIRSIZ]; + + if((dp = nameiparent(path, name)) == 0) + return 0; + + ilock(dp); + + if((ip = dirlookup(dp, name, 0)) != 0){ + iunlockput(dp); + ilock(ip); + if(type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE)) + return ip; + iunlockput(ip); + return 0; + } + + if((ip = ialloc(dp->dev, type)) == 0){ + iunlockput(dp); + return 0; + } + + ilock(ip); + ip->major = major; + ip->minor = minor; + ip->nlink = 1; + iupdate(ip); + + if(type == T_DIR){ // Create . and .. entries. + // No ip->nlink++ for ".": avoid cyclic ref count. + if(dirlink(ip, ".", ip->inum) < 0 || dirlink(ip, "..", dp->inum) < 0) + goto fail; + } + + if(dirlink(dp, name, ip->inum) < 0) + goto fail; + + if(type == T_DIR){ + // now that success is guaranteed: + dp->nlink++; // for ".." + iupdate(dp); + } + + iunlockput(dp); + + return ip; + + fail: + // something went wrong. de-allocate ip. + ip->nlink = 0; + iupdate(ip); + iunlockput(ip); + iunlockput(dp); + return 0; +} + +uint64 +sys_open(void) +{ + char path[MAXPATH]; + int fd, omode; + struct file *f; + struct inode *ip; + int n; + + argint(1, &omode); + if((n = argstr(0, path, MAXPATH)) < 0) + return -1; + + begin_op(); + + if(omode & O_CREATE){ + ip = create(path, T_FILE, 0, 0); + if(ip == 0){ + end_op(); + return -1; + } + } else { + if((ip = namei(path)) == 0){ + end_op(); + return -1; + } + ilock(ip); + if(ip->type == T_DIR && omode != O_RDONLY){ + iunlockput(ip); + end_op(); + return -1; + } + } + + if(ip->type == T_DEVICE && (ip->major < 0 || ip->major >= NDEV)){ + iunlockput(ip); + end_op(); + return -1; + } + + if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){ + if(f) + fileclose(f); + iunlockput(ip); + end_op(); + return -1; + } + + if(ip->type == T_DEVICE){ + f->type = FD_DEVICE; + f->major = ip->major; + } else { + f->type = FD_INODE; + f->off = 0; + } + f->ip = ip; + f->readable = !(omode & O_WRONLY); + f->writable = (omode & O_WRONLY) || (omode & O_RDWR); + + if((omode & O_TRUNC) && ip->type == T_FILE){ + itrunc(ip); + } + + iunlock(ip); + end_op(); + + return fd; +} + +uint64 +sys_mkdir(void) +{ + char path[MAXPATH]; + struct inode *ip; + + begin_op(); + if(argstr(0, path, MAXPATH) < 0 || (ip = create(path, T_DIR, 0, 0)) == 0){ + end_op(); + return -1; + } + iunlockput(ip); + end_op(); + return 0; +} + +uint64 +sys_mknod(void) +{ + struct inode *ip; + char path[MAXPATH]; + int major, minor; + + begin_op(); + argint(1, &major); + argint(2, &minor); + if((argstr(0, path, MAXPATH)) < 0 || + (ip = create(path, T_DEVICE, major, minor)) == 0){ + end_op(); + return -1; + } + iunlockput(ip); + end_op(); + return 0; +} + +uint64 +sys_chdir(void) +{ + char path[MAXPATH]; + struct inode *ip; + struct proc *p = myproc(); + + begin_op(); + if(argstr(0, path, MAXPATH) < 0 || (ip = namei(path)) == 0){ + end_op(); + return -1; + } + ilock(ip); + if(ip->type != T_DIR){ + iunlockput(ip); + end_op(); + return -1; + } + iunlock(ip); + iput(p->cwd); + end_op(); + p->cwd = ip; + return 0; +} + +uint64 +sys_exec(void) +{ + char path[MAXPATH], *argv[MAXARG]; + int i; + uint64 uargv, uarg; + + argaddr(1, &uargv); + if(argstr(0, path, MAXPATH) < 0) { + return -1; + } + memset(argv, 0, sizeof(argv)); + for(i=0;; i++){ + if(i >= NELEM(argv)){ + goto bad; + } + if(fetchaddr(uargv+sizeof(uint64)*i, (uint64*)&uarg) < 0){ + goto bad; + } + if(uarg == 0){ + argv[i] = 0; + break; + } + argv[i] = kalloc(); + if(argv[i] == 0) + goto bad; + if(fetchstr(uarg, argv[i], PGSIZE) < 0) + goto bad; + } + + int ret = exec(path, argv); + + for(i = 0; i < NELEM(argv) && argv[i] != 0; i++) + kfree(argv[i]); + + return ret; + + bad: + for(i = 0; i < NELEM(argv) && argv[i] != 0; i++) + kfree(argv[i]); + return -1; +} + +uint64 +sys_pipe(void) +{ + uint64 fdarray; // user pointer to array of two integers + struct file *rf, *wf; + int fd0, fd1; + struct proc *p = myproc(); + + argaddr(0, &fdarray); + if(pipealloc(&rf, &wf) < 0) + return -1; + fd0 = -1; + if((fd0 = fdalloc(rf)) < 0 || (fd1 = fdalloc(wf)) < 0){ + if(fd0 >= 0) + p->ofile[fd0] = 0; + fileclose(rf); + fileclose(wf); + return -1; + } + if(copyout(p->pagetable, fdarray, (char*)&fd0, sizeof(fd0)) < 0 || + copyout(p->pagetable, fdarray+sizeof(fd0), (char *)&fd1, sizeof(fd1)) < 0){ + p->ofile[fd0] = 0; + p->ofile[fd1] = 0; + fileclose(rf); + fileclose(wf); + return -1; + } + return 0; +} diff --git a/1/xv6-riscv-riscv/kernel/sysproc.c b/1/xv6-riscv-riscv/kernel/sysproc.c new file mode 100644 index 0000000000000000000000000000000000000000..3b4d5bd86338fc90086bce8b56d8f57e84df4d20 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/sysproc.c @@ -0,0 +1,93 @@ +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "spinlock.h" +#include "proc.h" + +uint64 +sys_exit(void) +{ + int n; + argint(0, &n); + exit(n); + return 0; // not reached +} + +uint64 +sys_getpid(void) +{ + return myproc()->pid; +} + +uint64 +sys_fork(void) +{ + return fork(); +} + +uint64 +sys_wait(void) +{ + uint64 p; + argaddr(0, &p); + return wait(p); +} + +uint64 +sys_sbrk(void) +{ + uint64 addr; + int n; + + argint(0, &n); + addr = myproc()->sz; + if(growproc(n) < 0) + return -1; + return addr; +} + +uint64 +sys_sleep(void) +{ + int n; + uint ticks0; + + argint(0, &n); + if(n < 0) + n = 0; + acquire(&tickslock); + ticks0 = ticks; + while(ticks - ticks0 < n){ + if(killed(myproc())){ + release(&tickslock); + return -1; + } + sleep(&ticks, &tickslock); + } + release(&tickslock); + return 0; +} + +uint64 +sys_kill(void) +{ + int pid; + + argint(0, &pid); + return kill(pid); +} + +// return how many clock tick interrupts have occurred +// since start. +uint64 +sys_uptime(void) +{ + uint xticks; + + acquire(&tickslock); + xticks = ticks; + release(&tickslock); + return xticks; +} diff --git a/1/xv6-riscv-riscv/kernel/trampoline.S b/1/xv6-riscv-riscv/kernel/trampoline.S new file mode 100644 index 0000000000000000000000000000000000000000..693f8a113906ee74cbc2eb85654d5b9e15968b63 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/trampoline.S @@ -0,0 +1,151 @@ + # + # low-level code to handle traps from user space into + # the kernel, and returns from kernel to user. + # + # the kernel maps the page holding this code + # at the same virtual address (TRAMPOLINE) + # in user and kernel space so that it continues + # to work when it switches page tables. + # kernel.ld causes this code to start at + # a page boundary. + # + +#include "riscv.h" +#include "memlayout.h" + +.section trampsec +.globl trampoline +.globl usertrap +trampoline: +.align 4 +.globl uservec +uservec: + # + # trap.c sets stvec to point here, so + # traps from user space start here, + # in supervisor mode, but with a + # user page table. + # + + # save user a0 in sscratch so + # a0 can be used to get at TRAPFRAME. + csrw sscratch, a0 + + # each process has a separate p->trapframe memory area, + # but it's mapped to the same virtual address + # (TRAPFRAME) in every process's user page table. + li a0, TRAPFRAME + + # save the user registers in TRAPFRAME + sd ra, 40(a0) + sd sp, 48(a0) + sd gp, 56(a0) + sd tp, 64(a0) + sd t0, 72(a0) + sd t1, 80(a0) + sd t2, 88(a0) + sd s0, 96(a0) + sd s1, 104(a0) + sd a1, 120(a0) + sd a2, 128(a0) + sd a3, 136(a0) + sd a4, 144(a0) + sd a5, 152(a0) + sd a6, 160(a0) + sd a7, 168(a0) + sd s2, 176(a0) + sd s3, 184(a0) + sd s4, 192(a0) + sd s5, 200(a0) + sd s6, 208(a0) + sd s7, 216(a0) + sd s8, 224(a0) + sd s9, 232(a0) + sd s10, 240(a0) + sd s11, 248(a0) + sd t3, 256(a0) + sd t4, 264(a0) + sd t5, 272(a0) + sd t6, 280(a0) + + # save the user a0 in p->trapframe->a0 + csrr t0, sscratch + sd t0, 112(a0) + + # initialize kernel stack pointer, from p->trapframe->kernel_sp + ld sp, 8(a0) + + # make tp hold the current hartid, from p->trapframe->kernel_hartid + ld tp, 32(a0) + + # load the address of usertrap(), from p->trapframe->kernel_trap + ld t0, 16(a0) + + # fetch the kernel page table address, from p->trapframe->kernel_satp. + ld t1, 0(a0) + + # wait for any previous memory operations to complete, so that + # they use the user page table. + sfence.vma zero, zero + + # install the kernel page table. + csrw satp, t1 + + # flush now-stale user entries from the TLB. + sfence.vma zero, zero + + # jump to usertrap(), which does not return + jr t0 + +.globl userret +userret: + # userret(pagetable) + # called by usertrapret() in trap.c to + # switch from kernel to user. + # a0: user page table, for satp. + + # switch to the user page table. + sfence.vma zero, zero + csrw satp, a0 + sfence.vma zero, zero + + li a0, TRAPFRAME + + # restore all but a0 from TRAPFRAME + ld ra, 40(a0) + ld sp, 48(a0) + ld gp, 56(a0) + ld tp, 64(a0) + ld t0, 72(a0) + ld t1, 80(a0) + ld t2, 88(a0) + ld s0, 96(a0) + ld s1, 104(a0) + ld a1, 120(a0) + ld a2, 128(a0) + ld a3, 136(a0) + ld a4, 144(a0) + ld a5, 152(a0) + ld a6, 160(a0) + ld a7, 168(a0) + ld s2, 176(a0) + ld s3, 184(a0) + ld s4, 192(a0) + ld s5, 200(a0) + ld s6, 208(a0) + ld s7, 216(a0) + ld s8, 224(a0) + ld s9, 232(a0) + ld s10, 240(a0) + ld s11, 248(a0) + ld t3, 256(a0) + ld t4, 264(a0) + ld t5, 272(a0) + ld t6, 280(a0) + + # restore user a0 + ld a0, 112(a0) + + # return to user mode and user pc. + # usertrapret() set up sstatus and sepc. + sret diff --git a/1/xv6-riscv-riscv/kernel/trap.c b/1/xv6-riscv-riscv/kernel/trap.c new file mode 100644 index 0000000000000000000000000000000000000000..d454a7d8ba5f0674b559811f86e92f2b73204beb --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/trap.c @@ -0,0 +1,218 @@ +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" + +struct spinlock tickslock; +uint ticks; + +extern char trampoline[], uservec[], userret[]; + +// in kernelvec.S, calls kerneltrap(). +void kernelvec(); + +extern int devintr(); + +void +trapinit(void) +{ + initlock(&tickslock, "time"); +} + +// set up to take exceptions and traps while in the kernel. +void +trapinithart(void) +{ + w_stvec((uint64)kernelvec); +} + +// +// handle an interrupt, exception, or system call from user space. +// called from trampoline.S +// +void +usertrap(void) +{ + int which_dev = 0; + + if((r_sstatus() & SSTATUS_SPP) != 0) + panic("usertrap: not from user mode"); + + // send interrupts and exceptions to kerneltrap(), + // since we're now in the kernel. + w_stvec((uint64)kernelvec); + + struct proc *p = myproc(); + + // save user program counter. + p->trapframe->epc = r_sepc(); + + if(r_scause() == 8){ + // system call + + if(killed(p)) + exit(-1); + + // sepc points to the ecall instruction, + // but we want to return to the next instruction. + p->trapframe->epc += 4; + + // an interrupt will change sepc, scause, and sstatus, + // so enable only now that we're done with those registers. + intr_on(); + + syscall(); + } else if((which_dev = devintr()) != 0){ + // ok + } else { + printf("usertrap(): unexpected scause 0x%lx pid=%d\n", r_scause(), p->pid); + printf(" sepc=0x%lx stval=0x%lx\n", r_sepc(), r_stval()); + setkilled(p); + } + + if(killed(p)) + exit(-1); + + // give up the CPU if this is a timer interrupt. + if(which_dev == 2) + yield(); + + usertrapret(); +} + +// +// return to user space +// +void +usertrapret(void) +{ + struct proc *p = myproc(); + + // we're about to switch the destination of traps from + // kerneltrap() to usertrap(), so turn off interrupts until + // we're back in user space, where usertrap() is correct. + intr_off(); + + // send syscalls, interrupts, and exceptions to uservec in trampoline.S + uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline); + w_stvec(trampoline_uservec); + + // set up trapframe values that uservec will need when + // the process next traps into the kernel. + p->trapframe->kernel_satp = r_satp(); // kernel page table + p->trapframe->kernel_sp = p->kstack + PGSIZE; // process's kernel stack + p->trapframe->kernel_trap = (uint64)usertrap; + p->trapframe->kernel_hartid = r_tp(); // hartid for cpuid() + + // set up the registers that trampoline.S's sret will use + // to get to user space. + + // set S Previous Privilege mode to User. + unsigned long x = r_sstatus(); + x &= ~SSTATUS_SPP; // clear SPP to 0 for user mode + x |= SSTATUS_SPIE; // enable interrupts in user mode + w_sstatus(x); + + // set S Exception Program Counter to the saved user pc. + w_sepc(p->trapframe->epc); + + // tell trampoline.S the user page table to switch to. + uint64 satp = MAKE_SATP(p->pagetable); + + // jump to userret in trampoline.S at the top of memory, which + // switches to the user page table, restores user registers, + // and switches to user mode with sret. + uint64 trampoline_userret = TRAMPOLINE + (userret - trampoline); + ((void (*)(uint64))trampoline_userret)(satp); +} + +// interrupts and exceptions from kernel code go here via kernelvec, +// on whatever the current kernel stack is. +void +kerneltrap() +{ + int which_dev = 0; + uint64 sepc = r_sepc(); + uint64 sstatus = r_sstatus(); + uint64 scause = r_scause(); + + if((sstatus & SSTATUS_SPP) == 0) + panic("kerneltrap: not from supervisor mode"); + if(intr_get() != 0) + panic("kerneltrap: interrupts enabled"); + + if((which_dev = devintr()) == 0){ + // interrupt or trap from an unknown source + printf("scause=0x%lx sepc=0x%lx stval=0x%lx\n", scause, r_sepc(), r_stval()); + panic("kerneltrap"); + } + + // give up the CPU if this is a timer interrupt. + if(which_dev == 2 && myproc() != 0) + yield(); + + // the yield() may have caused some traps to occur, + // so restore trap registers for use by kernelvec.S's sepc instruction. + w_sepc(sepc); + w_sstatus(sstatus); +} + +void +clockintr() +{ + if(cpuid() == 0){ + acquire(&tickslock); + ticks++; + wakeup(&ticks); + release(&tickslock); + } + + // ask for the next timer interrupt. this also clears + // the interrupt request. 1000000 is about a tenth + // of a second. + w_stimecmp(r_time() + 1000000); +} + +// check if it's an external interrupt or software interrupt, +// and handle it. +// returns 2 if timer interrupt, +// 1 if other device, +// 0 if not recognized. +int +devintr() +{ + uint64 scause = r_scause(); + + if(scause == 0x8000000000000009L){ + // this is a supervisor external interrupt, via PLIC. + + // irq indicates which device interrupted. + int irq = plic_claim(); + + if(irq == UART0_IRQ){ + uartintr(); + } else if(irq == VIRTIO0_IRQ){ + virtio_disk_intr(); + } else if(irq){ + printf("unexpected interrupt irq=%d\n", irq); + } + + // the PLIC allows each device to raise at most one + // interrupt at a time; tell the PLIC the device is + // now allowed to interrupt again. + if(irq) + plic_complete(irq); + + return 1; + } else if(scause == 0x8000000000000005L){ + // timer interrupt. + clockintr(); + return 2; + } else { + return 0; + } +} + diff --git a/1/xv6-riscv-riscv/kernel/types.h b/1/xv6-riscv-riscv/kernel/types.h new file mode 100644 index 0000000000000000000000000000000000000000..ee731649e26036528f55d39c0a7e8ae53f6ceb41 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/types.h @@ -0,0 +1,10 @@ +typedef unsigned int uint; +typedef unsigned short ushort; +typedef unsigned char uchar; + +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef unsigned int uint32; +typedef unsigned long uint64; + +typedef uint64 pde_t; diff --git a/1/xv6-riscv-riscv/kernel/uart.c b/1/xv6-riscv-riscv/kernel/uart.c new file mode 100644 index 0000000000000000000000000000000000000000..83846ad9244cee15f59f8506c4450ca94dc96a75 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/uart.c @@ -0,0 +1,191 @@ +// +// low-level driver routines for 16550a UART. +// + +#include "types.h" +#include "param.h" +#include "memlayout.h" +#include "riscv.h" +#include "spinlock.h" +#include "proc.h" +#include "defs.h" + +// the UART control registers are memory-mapped +// at address UART0. this macro returns the +// address of one of the registers. +#define Reg(reg) ((volatile unsigned char *)(UART0 + (reg))) + +// the UART control registers. +// some have different meanings for +// read vs write. +// see http://byterunner.com/16550.html +#define RHR 0 // receive holding register (for input bytes) +#define THR 0 // transmit holding register (for output bytes) +#define IER 1 // interrupt enable register +#define IER_RX_ENABLE (1<<0) +#define IER_TX_ENABLE (1<<1) +#define FCR 2 // FIFO control register +#define FCR_FIFO_ENABLE (1<<0) +#define FCR_FIFO_CLEAR (3<<1) // clear the content of the two FIFOs +#define ISR 2 // interrupt status register +#define LCR 3 // line control register +#define LCR_EIGHT_BITS (3<<0) +#define LCR_BAUD_LATCH (1<<7) // special mode to set baud rate +#define LSR 5 // line status register +#define LSR_RX_READY (1<<0) // input is waiting to be read from RHR +#define LSR_TX_IDLE (1<<5) // THR can accept another character to send + +#define ReadReg(reg) (*(Reg(reg))) +#define WriteReg(reg, v) (*(Reg(reg)) = (v)) + +// the transmit output buffer. +struct spinlock uart_tx_lock; +#define UART_TX_BUF_SIZE 32 +char uart_tx_buf[UART_TX_BUF_SIZE]; +uint64 uart_tx_w; // write next to uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] +uint64 uart_tx_r; // read next from uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE] + +extern volatile int panicked; // from printf.c + +void uartstart(); + +void +uartinit(void) +{ + // disable interrupts. + WriteReg(IER, 0x00); + + // special mode to set baud rate. + WriteReg(LCR, LCR_BAUD_LATCH); + + // LSB for baud rate of 38.4K. + WriteReg(0, 0x03); + + // MSB for baud rate of 38.4K. + WriteReg(1, 0x00); + + // leave set-baud mode, + // and set word length to 8 bits, no parity. + WriteReg(LCR, LCR_EIGHT_BITS); + + // reset and enable FIFOs. + WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR); + + // enable transmit and receive interrupts. + WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE); + + initlock(&uart_tx_lock, "uart"); +} + +// add a character to the output buffer and tell the +// UART to start sending if it isn't already. +// blocks if the output buffer is full. +// because it may block, it can't be called +// from interrupts; it's only suitable for use +// by write(). +void +uartputc(int c) +{ + acquire(&uart_tx_lock); + + if(panicked){ + for(;;) + ; + } + while(uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){ + // buffer is full. + // wait for uartstart() to open up space in the buffer. + sleep(&uart_tx_r, &uart_tx_lock); + } + uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c; + uart_tx_w += 1; + uartstart(); + release(&uart_tx_lock); +} + + +// alternate version of uartputc() that doesn't +// use interrupts, for use by kernel printf() and +// to echo characters. it spins waiting for the uart's +// output register to be empty. +void +uartputc_sync(int c) +{ + push_off(); + + if(panicked){ + for(;;) + ; + } + + // wait for Transmit Holding Empty to be set in LSR. + while((ReadReg(LSR) & LSR_TX_IDLE) == 0) + ; + WriteReg(THR, c); + + pop_off(); +} + +// if the UART is idle, and a character is waiting +// in the transmit buffer, send it. +// caller must hold uart_tx_lock. +// called from both the top- and bottom-half. +void +uartstart() +{ + while(1){ + if(uart_tx_w == uart_tx_r){ + // transmit buffer is empty. + ReadReg(ISR); + return; + } + + if((ReadReg(LSR) & LSR_TX_IDLE) == 0){ + // the UART transmit holding register is full, + // so we cannot give it another byte. + // it will interrupt when it's ready for a new byte. + return; + } + + int c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE]; + uart_tx_r += 1; + + // maybe uartputc() is waiting for space in the buffer. + wakeup(&uart_tx_r); + + WriteReg(THR, c); + } +} + +// read one input character from the UART. +// return -1 if none is waiting. +int +uartgetc(void) +{ + if(ReadReg(LSR) & 0x01){ + // input data is ready. + return ReadReg(RHR); + } else { + return -1; + } +} + +// handle a uart interrupt, raised because input has +// arrived, or the uart is ready for more output, or +// both. called from devintr(). +void +uartintr(void) +{ + // read and process incoming characters. + while(1){ + int c = uartgetc(); + if(c == -1) + break; + consoleintr(c); + } + + // send buffered characters. + acquire(&uart_tx_lock); + uartstart(); + release(&uart_tx_lock); +} diff --git a/1/xv6-riscv-riscv/kernel/virtio.h b/1/xv6-riscv-riscv/kernel/virtio.h new file mode 100644 index 0000000000000000000000000000000000000000..96272b46d796c8f17de324135c4f890257994ecf --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/virtio.h @@ -0,0 +1,96 @@ +// +// virtio device definitions. +// for both the mmio interface, and virtio descriptors. +// only tested with qemu. +// +// the virtio spec: +// https://docs.oasis-open.org/virtio/virtio/v1.1/virtio-v1.1.pdf +// + +// virtio mmio control registers, mapped starting at 0x10001000. +// from qemu virtio_mmio.h +#define VIRTIO_MMIO_MAGIC_VALUE 0x000 // 0x74726976 +#define VIRTIO_MMIO_VERSION 0x004 // version; should be 2 +#define VIRTIO_MMIO_DEVICE_ID 0x008 // device type; 1 is net, 2 is disk +#define VIRTIO_MMIO_VENDOR_ID 0x00c // 0x554d4551 +#define VIRTIO_MMIO_DEVICE_FEATURES 0x010 +#define VIRTIO_MMIO_DRIVER_FEATURES 0x020 +#define VIRTIO_MMIO_QUEUE_SEL 0x030 // select queue, write-only +#define VIRTIO_MMIO_QUEUE_NUM_MAX 0x034 // max size of current queue, read-only +#define VIRTIO_MMIO_QUEUE_NUM 0x038 // size of current queue, write-only +#define VIRTIO_MMIO_QUEUE_READY 0x044 // ready bit +#define VIRTIO_MMIO_QUEUE_NOTIFY 0x050 // write-only +#define VIRTIO_MMIO_INTERRUPT_STATUS 0x060 // read-only +#define VIRTIO_MMIO_INTERRUPT_ACK 0x064 // write-only +#define VIRTIO_MMIO_STATUS 0x070 // read/write +#define VIRTIO_MMIO_QUEUE_DESC_LOW 0x080 // physical address for descriptor table, write-only +#define VIRTIO_MMIO_QUEUE_DESC_HIGH 0x084 +#define VIRTIO_MMIO_DRIVER_DESC_LOW 0x090 // physical address for available ring, write-only +#define VIRTIO_MMIO_DRIVER_DESC_HIGH 0x094 +#define VIRTIO_MMIO_DEVICE_DESC_LOW 0x0a0 // physical address for used ring, write-only +#define VIRTIO_MMIO_DEVICE_DESC_HIGH 0x0a4 + +// status register bits, from qemu virtio_config.h +#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1 +#define VIRTIO_CONFIG_S_DRIVER 2 +#define VIRTIO_CONFIG_S_DRIVER_OK 4 +#define VIRTIO_CONFIG_S_FEATURES_OK 8 + +// device feature bits +#define VIRTIO_BLK_F_RO 5 /* Disk is read-only */ +#define VIRTIO_BLK_F_SCSI 7 /* Supports scsi command passthru */ +#define VIRTIO_BLK_F_CONFIG_WCE 11 /* Writeback mode available in config */ +#define VIRTIO_BLK_F_MQ 12 /* support more than one vq */ +#define VIRTIO_F_ANY_LAYOUT 27 +#define VIRTIO_RING_F_INDIRECT_DESC 28 +#define VIRTIO_RING_F_EVENT_IDX 29 + +// this many virtio descriptors. +// must be a power of two. +#define NUM 8 + +// a single descriptor, from the spec. +struct virtq_desc { + uint64 addr; + uint32 len; + uint16 flags; + uint16 next; +}; +#define VRING_DESC_F_NEXT 1 // chained with another descriptor +#define VRING_DESC_F_WRITE 2 // device writes (vs read) + +// the (entire) avail ring, from the spec. +struct virtq_avail { + uint16 flags; // always zero + uint16 idx; // driver will write ring[idx] next + uint16 ring[NUM]; // descriptor numbers of chain heads + uint16 unused; +}; + +// one entry in the "used" ring, with which the +// device tells the driver about completed requests. +struct virtq_used_elem { + uint32 id; // index of start of completed descriptor chain + uint32 len; +}; + +struct virtq_used { + uint16 flags; // always zero + uint16 idx; // device increments when it adds a ring[] entry + struct virtq_used_elem ring[NUM]; +}; + +// these are specific to virtio block devices, e.g. disks, +// described in Section 5.2 of the spec. + +#define VIRTIO_BLK_T_IN 0 // read the disk +#define VIRTIO_BLK_T_OUT 1 // write the disk + +// the format of the first descriptor in a disk request. +// to be followed by two more descriptors containing +// the block, and a one-byte status. +struct virtio_blk_req { + uint32 type; // VIRTIO_BLK_T_IN or ..._OUT + uint32 reserved; + uint64 sector; +}; diff --git a/1/xv6-riscv-riscv/kernel/virtio_disk.c b/1/xv6-riscv-riscv/kernel/virtio_disk.c new file mode 100644 index 0000000000000000000000000000000000000000..ae6c164f0859d8796cb46cf97c867b11b9891f91 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/virtio_disk.c @@ -0,0 +1,327 @@ +// +// driver for qemu's virtio disk device. +// uses qemu's mmio interface to virtio. +// +// qemu ... -drive file=fs.img,if=none,format=raw,id=x0 -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 +// + +#include "types.h" +#include "riscv.h" +#include "defs.h" +#include "param.h" +#include "memlayout.h" +#include "spinlock.h" +#include "sleeplock.h" +#include "fs.h" +#include "buf.h" +#include "virtio.h" + +// the address of virtio mmio register r. +#define R(r) ((volatile uint32 *)(VIRTIO0 + (r))) + +static struct disk { + // a set (not a ring) of DMA descriptors, with which the + // driver tells the device where to read and write individual + // disk operations. there are NUM descriptors. + // most commands consist of a "chain" (a linked list) of a couple of + // these descriptors. + struct virtq_desc *desc; + + // a ring in which the driver writes descriptor numbers + // that the driver would like the device to process. it only + // includes the head descriptor of each chain. the ring has + // NUM elements. + struct virtq_avail *avail; + + // a ring in which the device writes descriptor numbers that + // the device has finished processing (just the head of each chain). + // there are NUM used ring entries. + struct virtq_used *used; + + // our own book-keeping. + char free[NUM]; // is a descriptor free? + uint16 used_idx; // we've looked this far in used[2..NUM]. + + // track info about in-flight operations, + // for use when completion interrupt arrives. + // indexed by first descriptor index of chain. + struct { + struct buf *b; + char status; + } info[NUM]; + + // disk command headers. + // one-for-one with descriptors, for convenience. + struct virtio_blk_req ops[NUM]; + + struct spinlock vdisk_lock; + +} disk; + +void +virtio_disk_init(void) +{ + uint32 status = 0; + + initlock(&disk.vdisk_lock, "virtio_disk"); + + if(*R(VIRTIO_MMIO_MAGIC_VALUE) != 0x74726976 || + *R(VIRTIO_MMIO_VERSION) != 2 || + *R(VIRTIO_MMIO_DEVICE_ID) != 2 || + *R(VIRTIO_MMIO_VENDOR_ID) != 0x554d4551){ + panic("could not find virtio disk"); + } + + // reset device + *R(VIRTIO_MMIO_STATUS) = status; + + // set ACKNOWLEDGE status bit + status |= VIRTIO_CONFIG_S_ACKNOWLEDGE; + *R(VIRTIO_MMIO_STATUS) = status; + + // set DRIVER status bit + status |= VIRTIO_CONFIG_S_DRIVER; + *R(VIRTIO_MMIO_STATUS) = status; + + // negotiate features + uint64 features = *R(VIRTIO_MMIO_DEVICE_FEATURES); + features &= ~(1 << VIRTIO_BLK_F_RO); + features &= ~(1 << VIRTIO_BLK_F_SCSI); + features &= ~(1 << VIRTIO_BLK_F_CONFIG_WCE); + features &= ~(1 << VIRTIO_BLK_F_MQ); + features &= ~(1 << VIRTIO_F_ANY_LAYOUT); + features &= ~(1 << VIRTIO_RING_F_EVENT_IDX); + features &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC); + *R(VIRTIO_MMIO_DRIVER_FEATURES) = features; + + // tell device that feature negotiation is complete. + status |= VIRTIO_CONFIG_S_FEATURES_OK; + *R(VIRTIO_MMIO_STATUS) = status; + + // re-read status to ensure FEATURES_OK is set. + status = *R(VIRTIO_MMIO_STATUS); + if(!(status & VIRTIO_CONFIG_S_FEATURES_OK)) + panic("virtio disk FEATURES_OK unset"); + + // initialize queue 0. + *R(VIRTIO_MMIO_QUEUE_SEL) = 0; + + // ensure queue 0 is not in use. + if(*R(VIRTIO_MMIO_QUEUE_READY)) + panic("virtio disk should not be ready"); + + // check maximum queue size. + uint32 max = *R(VIRTIO_MMIO_QUEUE_NUM_MAX); + if(max == 0) + panic("virtio disk has no queue 0"); + if(max < NUM) + panic("virtio disk max queue too short"); + + // allocate and zero queue memory. + disk.desc = kalloc(); + disk.avail = kalloc(); + disk.used = kalloc(); + if(!disk.desc || !disk.avail || !disk.used) + panic("virtio disk kalloc"); + memset(disk.desc, 0, PGSIZE); + memset(disk.avail, 0, PGSIZE); + memset(disk.used, 0, PGSIZE); + + // set queue size. + *R(VIRTIO_MMIO_QUEUE_NUM) = NUM; + + // write physical addresses. + *R(VIRTIO_MMIO_QUEUE_DESC_LOW) = (uint64)disk.desc; + *R(VIRTIO_MMIO_QUEUE_DESC_HIGH) = (uint64)disk.desc >> 32; + *R(VIRTIO_MMIO_DRIVER_DESC_LOW) = (uint64)disk.avail; + *R(VIRTIO_MMIO_DRIVER_DESC_HIGH) = (uint64)disk.avail >> 32; + *R(VIRTIO_MMIO_DEVICE_DESC_LOW) = (uint64)disk.used; + *R(VIRTIO_MMIO_DEVICE_DESC_HIGH) = (uint64)disk.used >> 32; + + // queue is ready. + *R(VIRTIO_MMIO_QUEUE_READY) = 0x1; + + // all NUM descriptors start out unused. + for(int i = 0; i < NUM; i++) + disk.free[i] = 1; + + // tell device we're completely ready. + status |= VIRTIO_CONFIG_S_DRIVER_OK; + *R(VIRTIO_MMIO_STATUS) = status; + + // plic.c and trap.c arrange for interrupts from VIRTIO0_IRQ. +} + +// find a free descriptor, mark it non-free, return its index. +static int +alloc_desc() +{ + for(int i = 0; i < NUM; i++){ + if(disk.free[i]){ + disk.free[i] = 0; + return i; + } + } + return -1; +} + +// mark a descriptor as free. +static void +free_desc(int i) +{ + if(i >= NUM) + panic("free_desc 1"); + if(disk.free[i]) + panic("free_desc 2"); + disk.desc[i].addr = 0; + disk.desc[i].len = 0; + disk.desc[i].flags = 0; + disk.desc[i].next = 0; + disk.free[i] = 1; + wakeup(&disk.free[0]); +} + +// free a chain of descriptors. +static void +free_chain(int i) +{ + while(1){ + int flag = disk.desc[i].flags; + int nxt = disk.desc[i].next; + free_desc(i); + if(flag & VRING_DESC_F_NEXT) + i = nxt; + else + break; + } +} + +// allocate three descriptors (they need not be contiguous). +// disk transfers always use three descriptors. +static int +alloc3_desc(int *idx) +{ + for(int i = 0; i < 3; i++){ + idx[i] = alloc_desc(); + if(idx[i] < 0){ + for(int j = 0; j < i; j++) + free_desc(idx[j]); + return -1; + } + } + return 0; +} + +void +virtio_disk_rw(struct buf *b, int write) +{ + uint64 sector = b->blockno * (BSIZE / 512); + + acquire(&disk.vdisk_lock); + + // the spec's Section 5.2 says that legacy block operations use + // three descriptors: one for type/reserved/sector, one for the + // data, one for a 1-byte status result. + + // allocate the three descriptors. + int idx[3]; + while(1){ + if(alloc3_desc(idx) == 0) { + break; + } + sleep(&disk.free[0], &disk.vdisk_lock); + } + + // format the three descriptors. + // qemu's virtio-blk.c reads them. + + struct virtio_blk_req *buf0 = &disk.ops[idx[0]]; + + if(write) + buf0->type = VIRTIO_BLK_T_OUT; // write the disk + else + buf0->type = VIRTIO_BLK_T_IN; // read the disk + buf0->reserved = 0; + buf0->sector = sector; + + disk.desc[idx[0]].addr = (uint64) buf0; + disk.desc[idx[0]].len = sizeof(struct virtio_blk_req); + disk.desc[idx[0]].flags = VRING_DESC_F_NEXT; + disk.desc[idx[0]].next = idx[1]; + + disk.desc[idx[1]].addr = (uint64) b->data; + disk.desc[idx[1]].len = BSIZE; + if(write) + disk.desc[idx[1]].flags = 0; // device reads b->data + else + disk.desc[idx[1]].flags = VRING_DESC_F_WRITE; // device writes b->data + disk.desc[idx[1]].flags |= VRING_DESC_F_NEXT; + disk.desc[idx[1]].next = idx[2]; + + disk.info[idx[0]].status = 0xff; // device writes 0 on success + disk.desc[idx[2]].addr = (uint64) &disk.info[idx[0]].status; + disk.desc[idx[2]].len = 1; + disk.desc[idx[2]].flags = VRING_DESC_F_WRITE; // device writes the status + disk.desc[idx[2]].next = 0; + + // record struct buf for virtio_disk_intr(). + b->disk = 1; + disk.info[idx[0]].b = b; + + // tell the device the first index in our chain of descriptors. + disk.avail->ring[disk.avail->idx % NUM] = idx[0]; + + __sync_synchronize(); + + // tell the device another avail ring entry is available. + disk.avail->idx += 1; // not % NUM ... + + __sync_synchronize(); + + *R(VIRTIO_MMIO_QUEUE_NOTIFY) = 0; // value is queue number + + // Wait for virtio_disk_intr() to say request has finished. + while(b->disk == 1) { + sleep(b, &disk.vdisk_lock); + } + + disk.info[idx[0]].b = 0; + free_chain(idx[0]); + + release(&disk.vdisk_lock); +} + +void +virtio_disk_intr() +{ + acquire(&disk.vdisk_lock); + + // the device won't raise another interrupt until we tell it + // we've seen this interrupt, which the following line does. + // this may race with the device writing new entries to + // the "used" ring, in which case we may process the new + // completion entries in this interrupt, and have nothing to do + // in the next interrupt, which is harmless. + *R(VIRTIO_MMIO_INTERRUPT_ACK) = *R(VIRTIO_MMIO_INTERRUPT_STATUS) & 0x3; + + __sync_synchronize(); + + // the device increments disk.used->idx when it + // adds an entry to the used ring. + + while(disk.used_idx != disk.used->idx){ + __sync_synchronize(); + int id = disk.used->ring[disk.used_idx % NUM].id; + + if(disk.info[id].status != 0) + panic("virtio_disk_intr status"); + + struct buf *b = disk.info[id].b; + b->disk = 0; // disk is done with buf + wakeup(b); + + disk.used_idx += 1; + } + + release(&disk.vdisk_lock); +} diff --git a/1/xv6-riscv-riscv/kernel/vm.c b/1/xv6-riscv-riscv/kernel/vm.c new file mode 100644 index 0000000000000000000000000000000000000000..62421a24f32ecd69bee06db5da9fdc76cd58def6 --- /dev/null +++ b/1/xv6-riscv-riscv/kernel/vm.c @@ -0,0 +1,451 @@ +#include "param.h" +#include "types.h" +#include "memlayout.h" +#include "elf.h" +#include "riscv.h" +#include "defs.h" +#include "fs.h" + +/* + * the kernel's page table. + */ +pagetable_t kernel_pagetable; + +extern char etext[]; // kernel.ld sets this to end of kernel code. + +extern char trampoline[]; // trampoline.S + +// Make a direct-map page table for the kernel. +pagetable_t +kvmmake(void) +{ + pagetable_t kpgtbl; + + kpgtbl = (pagetable_t) kalloc(); + memset(kpgtbl, 0, PGSIZE); + + // uart registers + kvmmap(kpgtbl, UART0, UART0, PGSIZE, PTE_R | PTE_W); + + // virtio mmio disk interface + kvmmap(kpgtbl, VIRTIO0, VIRTIO0, PGSIZE, PTE_R | PTE_W); + + // PLIC + kvmmap(kpgtbl, PLIC, PLIC, 0x4000000, PTE_R | PTE_W); + + // map kernel text executable and read-only. + kvmmap(kpgtbl, KERNBASE, KERNBASE, (uint64)etext-KERNBASE, PTE_R | PTE_X); + + // map kernel data and the physical RAM we'll make use of. + kvmmap(kpgtbl, (uint64)etext, (uint64)etext, PHYSTOP-(uint64)etext, PTE_R | PTE_W); + + // map the trampoline for trap entry/exit to + // the highest virtual address in the kernel. + kvmmap(kpgtbl, TRAMPOLINE, (uint64)trampoline, PGSIZE, PTE_R | PTE_X); + + // allocate and map a kernel stack for each process. + proc_mapstacks(kpgtbl); + + return kpgtbl; +} + +// Initialize the one kernel_pagetable +void +kvminit(void) +{ + kernel_pagetable = kvmmake(); +} + +// Switch h/w page table register to the kernel's page table, +// and enable paging. +void +kvminithart() +{ + // wait for any previous writes to the page table memory to finish. + sfence_vma(); + + w_satp(MAKE_SATP(kernel_pagetable)); + + // flush stale entries from the TLB. + sfence_vma(); +} + +// Return the address of the PTE in page table pagetable +// that corresponds to virtual address va. If alloc!=0, +// create any required page-table pages. +// +// The risc-v Sv39 scheme has three levels of page-table +// pages. A page-table page contains 512 64-bit PTEs. +// A 64-bit virtual address is split into five fields: +// 39..63 -- must be zero. +// 30..38 -- 9 bits of level-2 index. +// 21..29 -- 9 bits of level-1 index. +// 12..20 -- 9 bits of level-0 index. +// 0..11 -- 12 bits of byte offset within the page. +pte_t * +walk(pagetable_t pagetable, uint64 va, int alloc) +{ + if(va >= MAXVA) + panic("walk"); + + for(int level = 2; level > 0; level--) { + pte_t *pte = &pagetable[PX(level, va)]; + if(*pte & PTE_V) { + pagetable = (pagetable_t)PTE2PA(*pte); + } else { + if(!alloc || (pagetable = (pde_t*)kalloc()) == 0) + return 0; + memset(pagetable, 0, PGSIZE); + *pte = PA2PTE(pagetable) | PTE_V; + } + } + return &pagetable[PX(0, va)]; +} + +// Look up a virtual address, return the physical address, +// or 0 if not mapped. +// Can only be used to look up user pages. +uint64 +walkaddr(pagetable_t pagetable, uint64 va) +{ + pte_t *pte; + uint64 pa; + + if(va >= MAXVA) + return 0; + + pte = walk(pagetable, va, 0); + if(pte == 0) + return 0; + if((*pte & PTE_V) == 0) + return 0; + if((*pte & PTE_U) == 0) + return 0; + pa = PTE2PA(*pte); + return pa; +} + +// add a mapping to the kernel page table. +// only used when booting. +// does not flush TLB or enable paging. +void +kvmmap(pagetable_t kpgtbl, uint64 va, uint64 pa, uint64 sz, int perm) +{ + if(mappages(kpgtbl, va, sz, pa, perm) != 0) + panic("kvmmap"); +} + +// Create PTEs for virtual addresses starting at va that refer to +// physical addresses starting at pa. +// va and size MUST be page-aligned. +// Returns 0 on success, -1 if walk() couldn't +// allocate a needed page-table page. +int +mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm) +{ + uint64 a, last; + pte_t *pte; + + if((va % PGSIZE) != 0) + panic("mappages: va not aligned"); + + if((size % PGSIZE) != 0) + panic("mappages: size not aligned"); + + if(size == 0) + panic("mappages: size"); + + a = va; + last = va + size - PGSIZE; + for(;;){ + if((pte = walk(pagetable, a, 1)) == 0) + return -1; + if(*pte & PTE_V) + panic("mappages: remap"); + *pte = PA2PTE(pa) | perm | PTE_V; + if(a == last) + break; + a += PGSIZE; + pa += PGSIZE; + } + return 0; +} + +// Remove npages of mappings starting from va. va must be +// page-aligned. The mappings must exist. +// Optionally free the physical memory. +void +uvmunmap(pagetable_t pagetable, uint64 va, uint64 npages, int do_free) +{ + uint64 a; + pte_t *pte; + + if((va % PGSIZE) != 0) + panic("uvmunmap: not aligned"); + + for(a = va; a < va + npages*PGSIZE; a += PGSIZE){ + if((pte = walk(pagetable, a, 0)) == 0) + panic("uvmunmap: walk"); + if((*pte & PTE_V) == 0) + panic("uvmunmap: not mapped"); + if(PTE_FLAGS(*pte) == PTE_V) + panic("uvmunmap: not a leaf"); + if(do_free){ + uint64 pa = PTE2PA(*pte); + kfree((void*)pa); + } + *pte = 0; + } +} + +// create an empty user page table. +// returns 0 if out of memory. +pagetable_t +uvmcreate() +{ + pagetable_t pagetable; + pagetable = (pagetable_t) kalloc(); + if(pagetable == 0) + return 0; + memset(pagetable, 0, PGSIZE); + return pagetable; +} + +// Load the user initcode into address 0 of pagetable, +// for the very first process. +// sz must be less than a page. +void +uvmfirst(pagetable_t pagetable, uchar *src, uint sz) +{ + char *mem; + + if(sz >= PGSIZE) + panic("uvmfirst: more than a page"); + mem = kalloc(); + memset(mem, 0, PGSIZE); + mappages(pagetable, 0, PGSIZE, (uint64)mem, PTE_W|PTE_R|PTE_X|PTE_U); + memmove(mem, src, sz); +} + +// Allocate PTEs and physical memory to grow process from oldsz to +// newsz, which need not be page aligned. Returns new size or 0 on error. +uint64 +uvmalloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz, int xperm) +{ + char *mem; + uint64 a; + + if(newsz < oldsz) + return oldsz; + + oldsz = PGROUNDUP(oldsz); + for(a = oldsz; a < newsz; a += PGSIZE){ + mem = kalloc(); + if(mem == 0){ + uvmdealloc(pagetable, a, oldsz); + return 0; + } + memset(mem, 0, PGSIZE); + if(mappages(pagetable, a, PGSIZE, (uint64)mem, PTE_R|PTE_U|xperm) != 0){ + kfree(mem); + uvmdealloc(pagetable, a, oldsz); + return 0; + } + } + return newsz; +} + +// Deallocate user pages to bring the process size from oldsz to +// newsz. oldsz and newsz need not be page-aligned, nor does newsz +// need to be less than oldsz. oldsz can be larger than the actual +// process size. Returns the new process size. +uint64 +uvmdealloc(pagetable_t pagetable, uint64 oldsz, uint64 newsz) +{ + if(newsz >= oldsz) + return oldsz; + + if(PGROUNDUP(newsz) < PGROUNDUP(oldsz)){ + int npages = (PGROUNDUP(oldsz) - PGROUNDUP(newsz)) / PGSIZE; + uvmunmap(pagetable, PGROUNDUP(newsz), npages, 1); + } + + return newsz; +} + +// Recursively free page-table pages. +// All leaf mappings must already have been removed. +void +freewalk(pagetable_t pagetable) +{ + // there are 2^9 = 512 PTEs in a page table. + for(int i = 0; i < 512; i++){ + pte_t pte = pagetable[i]; + if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){ + // this PTE points to a lower-level page table. + uint64 child = PTE2PA(pte); + freewalk((pagetable_t)child); + pagetable[i] = 0; + } else if(pte & PTE_V){ + panic("freewalk: leaf"); + } + } + kfree((void*)pagetable); +} + +// Free user memory pages, +// then free page-table pages. +void +uvmfree(pagetable_t pagetable, uint64 sz) +{ + if(sz > 0) + uvmunmap(pagetable, 0, PGROUNDUP(sz)/PGSIZE, 1); + freewalk(pagetable); +} + +// Given a parent process's page table, copy +// its memory into a child's page table. +// Copies both the page table and the +// physical memory. +// returns 0 on success, -1 on failure. +// frees any allocated pages on failure. +int +uvmcopy(pagetable_t old, pagetable_t new, uint64 sz) +{ + pte_t *pte; + uint64 pa, i; + uint flags; + char *mem; + + for(i = 0; i < sz; i += PGSIZE){ + if((pte = walk(old, i, 0)) == 0) + panic("uvmcopy: pte should exist"); + if((*pte & PTE_V) == 0) + panic("uvmcopy: page not present"); + pa = PTE2PA(*pte); + flags = PTE_FLAGS(*pte); + if((mem = kalloc()) == 0) + goto err; + memmove(mem, (char*)pa, PGSIZE); + if(mappages(new, i, PGSIZE, (uint64)mem, flags) != 0){ + kfree(mem); + goto err; + } + } + return 0; + + err: + uvmunmap(new, 0, i / PGSIZE, 1); + return -1; +} + +// mark a PTE invalid for user access. +// used by exec for the user stack guard page. +void +uvmclear(pagetable_t pagetable, uint64 va) +{ + pte_t *pte; + + pte = walk(pagetable, va, 0); + if(pte == 0) + panic("uvmclear"); + *pte &= ~PTE_U; +} + +// Copy from kernel to user. +// Copy len bytes from src to virtual address dstva in a given page table. +// Return 0 on success, -1 on error. +int +copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len) +{ + uint64 n, va0, pa0; + pte_t *pte; + + while(len > 0){ + va0 = PGROUNDDOWN(dstva); + if(va0 >= MAXVA) + return -1; + pte = walk(pagetable, va0, 0); + if(pte == 0 || (*pte & PTE_V) == 0 || (*pte & PTE_U) == 0 || + (*pte & PTE_W) == 0) + return -1; + pa0 = PTE2PA(*pte); + n = PGSIZE - (dstva - va0); + if(n > len) + n = len; + memmove((void *)(pa0 + (dstva - va0)), src, n); + + len -= n; + src += n; + dstva = va0 + PGSIZE; + } + return 0; +} + +// Copy from user to kernel. +// Copy len bytes to dst from virtual address srcva in a given page table. +// Return 0 on success, -1 on error. +int +copyin(pagetable_t pagetable, char *dst, uint64 srcva, uint64 len) +{ + uint64 n, va0, pa0; + + while(len > 0){ + va0 = PGROUNDDOWN(srcva); + pa0 = walkaddr(pagetable, va0); + if(pa0 == 0) + return -1; + n = PGSIZE - (srcva - va0); + if(n > len) + n = len; + memmove(dst, (void *)(pa0 + (srcva - va0)), n); + + len -= n; + dst += n; + srcva = va0 + PGSIZE; + } + return 0; +} + +// Copy a null-terminated string from user to kernel. +// Copy bytes to dst from virtual address srcva in a given page table, +// until a '\0', or max. +// Return 0 on success, -1 on error. +int +copyinstr(pagetable_t pagetable, char *dst, uint64 srcva, uint64 max) +{ + uint64 n, va0, pa0; + int got_null = 0; + + while(got_null == 0 && max > 0){ + va0 = PGROUNDDOWN(srcva); + pa0 = walkaddr(pagetable, va0); + if(pa0 == 0) + return -1; + n = PGSIZE - (srcva - va0); + if(n > max) + n = max; + + char *p = (char *) (pa0 + (srcva - va0)); + while(n > 0){ + if(*p == '\0'){ + *dst = '\0'; + got_null = 1; + break; + } else { + *dst = *p; + } + --n; + --max; + p++; + dst++; + } + + srcva = va0 + PGSIZE; + } + if(got_null){ + return 0; + } else { + return -1; + } +} diff --git a/1/xv6-riscv-riscv/user/cat.c b/1/xv6-riscv-riscv/user/cat.c new file mode 100644 index 0000000000000000000000000000000000000000..6d873a9705a6a8df529b42d4d50dbbe964e459ff --- /dev/null +++ b/1/xv6-riscv-riscv/user/cat.c @@ -0,0 +1,43 @@ +#include "kernel/types.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +char buf[512]; + +void +cat(int fd) +{ + int n; + + while((n = read(fd, buf, sizeof(buf))) > 0) { + if (write(1, buf, n) != n) { + fprintf(2, "cat: write error\n"); + exit(1); + } + } + if(n < 0){ + fprintf(2, "cat: read error\n"); + exit(1); + } +} + +int +main(int argc, char *argv[]) +{ + int fd, i; + + if(argc <= 1){ + cat(0); + exit(0); + } + + for(i = 1; i < argc; i++){ + if((fd = open(argv[i], O_RDONLY)) < 0){ + fprintf(2, "cat: cannot open %s\n", argv[i]); + exit(1); + } + cat(fd); + close(fd); + } + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/echo.c b/1/xv6-riscv-riscv/user/echo.c new file mode 100644 index 0000000000000000000000000000000000000000..3f19cd7f1d70082950f741aa7eeb073fdcae1859 --- /dev/null +++ b/1/xv6-riscv-riscv/user/echo.c @@ -0,0 +1,19 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ + int i; + + for(i = 1; i < argc; i++){ + write(1, argv[i], strlen(argv[i])); + if(i + 1 < argc){ + write(1, " ", 1); + } else { + write(1, "\n", 1); + } + } + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/forktest.c b/1/xv6-riscv-riscv/user/forktest.c new file mode 100644 index 0000000000000000000000000000000000000000..384e75f785d1a3730e3e61136508feabb6d2bfb1 --- /dev/null +++ b/1/xv6-riscv-riscv/user/forktest.c @@ -0,0 +1,56 @@ +// Test that fork fails gracefully. +// Tiny executable so that the limit can be filling the proc table. + +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +#define N 1000 + +void +print(const char *s) +{ + write(1, s, strlen(s)); +} + +void +forktest(void) +{ + int n, pid; + + print("fork test\n"); + + for(n=0; n<N; n++){ + pid = fork(); + if(pid < 0) + break; + if(pid == 0) + exit(0); + } + + if(n == N){ + print("fork claimed to work N times!\n"); + exit(1); + } + + for(; n > 0; n--){ + if(wait(0) < 0){ + print("wait stopped early\n"); + exit(1); + } + } + + if(wait(0) != -1){ + print("wait got too many\n"); + exit(1); + } + + print("fork test OK\n"); +} + +int +main(void) +{ + forktest(); + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/grep.c b/1/xv6-riscv-riscv/user/grep.c new file mode 100644 index 0000000000000000000000000000000000000000..6c33766adc9da5b8e623ff5ec439272c7466faac --- /dev/null +++ b/1/xv6-riscv-riscv/user/grep.c @@ -0,0 +1,107 @@ +// Simple grep. Only supports ^ . * $ operators. + +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +char buf[1024]; +int match(char*, char*); + +void +grep(char *pattern, int fd) +{ + int n, m; + char *p, *q; + + m = 0; + while((n = read(fd, buf+m, sizeof(buf)-m-1)) > 0){ + m += n; + buf[m] = '\0'; + p = buf; + while((q = strchr(p, '\n')) != 0){ + *q = 0; + if(match(pattern, p)){ + *q = '\n'; + write(1, p, q+1 - p); + } + p = q+1; + } + if(m > 0){ + m -= p - buf; + memmove(buf, p, m); + } + } +} + +int +main(int argc, char *argv[]) +{ + int fd, i; + char *pattern; + + if(argc <= 1){ + fprintf(2, "usage: grep pattern [file ...]\n"); + exit(1); + } + pattern = argv[1]; + + if(argc <= 2){ + grep(pattern, 0); + exit(0); + } + + for(i = 2; i < argc; i++){ + if((fd = open(argv[i], O_RDONLY)) < 0){ + printf("grep: cannot open %s\n", argv[i]); + exit(1); + } + grep(pattern, fd); + close(fd); + } + exit(0); +} + +// Regexp matcher from Kernighan & Pike, +// The Practice of Programming, Chapter 9, or +// https://www.cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html + +int matchhere(char*, char*); +int matchstar(int, char*, char*); + +int +match(char *re, char *text) +{ + if(re[0] == '^') + return matchhere(re+1, text); + do{ // must look at empty string + if(matchhere(re, text)) + return 1; + }while(*text++ != '\0'); + return 0; +} + +// matchhere: search for re at beginning of text +int matchhere(char *re, char *text) +{ + if(re[0] == '\0') + return 1; + if(re[1] == '*') + return matchstar(re[0], re+2, text); + if(re[0] == '$' && re[1] == '\0') + return *text == '\0'; + if(*text!='\0' && (re[0]=='.' || re[0]==*text)) + return matchhere(re+1, text+1); + return 0; +} + +// matchstar: search for c*re at beginning of text +int matchstar(int c, char *re, char *text) +{ + do{ // a * matches zero or more instances + if(matchhere(re, text)) + return 1; + }while(*text!='\0' && (*text++==c || c=='.')); + return 0; +} + diff --git a/1/xv6-riscv-riscv/user/grind.c b/1/xv6-riscv-riscv/user/grind.c new file mode 100644 index 0000000000000000000000000000000000000000..431ed19952ca4852a8367a6e01840adbf3c85d9f --- /dev/null +++ b/1/xv6-riscv-riscv/user/grind.c @@ -0,0 +1,351 @@ +// +// run random system calls in parallel forever. +// + +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" +#include "kernel/fs.h" +#include "kernel/fcntl.h" +#include "kernel/syscall.h" +#include "kernel/memlayout.h" +#include "kernel/riscv.h" + +// from FreeBSD. +int +do_rand(unsigned long *ctx) +{ +/* + * Compute x = (7^5 * x) mod (2^31 - 1) + * without overflowing 31 bits: + * (2^31 - 1) = 127773 * (7^5) + 2836 + * From "Random number generators: good ones are hard to find", + * Park and Miller, Communications of the ACM, vol. 31, no. 10, + * October 1988, p. 1195. + */ + long hi, lo, x; + + /* Transform to [1, 0x7ffffffe] range. */ + x = (*ctx % 0x7ffffffe) + 1; + hi = x / 127773; + lo = x % 127773; + x = 16807 * lo - 2836 * hi; + if (x < 0) + x += 0x7fffffff; + /* Transform to [0, 0x7ffffffd] range. */ + x--; + *ctx = x; + return (x); +} + +unsigned long rand_next = 1; + +int +rand(void) +{ + return (do_rand(&rand_next)); +} + +void +go(int which_child) +{ + int fd = -1; + static char buf[999]; + char *break0 = sbrk(0); + uint64 iters = 0; + + mkdir("grindir"); + if(chdir("grindir") != 0){ + printf("grind: chdir grindir failed\n"); + exit(1); + } + chdir("/"); + + while(1){ + iters++; + if((iters % 500) == 0) + write(1, which_child?"B":"A", 1); + int what = rand() % 23; + if(what == 1){ + close(open("grindir/../a", O_CREATE|O_RDWR)); + } else if(what == 2){ + close(open("grindir/../grindir/../b", O_CREATE|O_RDWR)); + } else if(what == 3){ + unlink("grindir/../a"); + } else if(what == 4){ + if(chdir("grindir") != 0){ + printf("grind: chdir grindir failed\n"); + exit(1); + } + unlink("../b"); + chdir("/"); + } else if(what == 5){ + close(fd); + fd = open("/grindir/../a", O_CREATE|O_RDWR); + } else if(what == 6){ + close(fd); + fd = open("/./grindir/./../b", O_CREATE|O_RDWR); + } else if(what == 7){ + write(fd, buf, sizeof(buf)); + } else if(what == 8){ + read(fd, buf, sizeof(buf)); + } else if(what == 9){ + mkdir("grindir/../a"); + close(open("a/../a/./a", O_CREATE|O_RDWR)); + unlink("a/a"); + } else if(what == 10){ + mkdir("/../b"); + close(open("grindir/../b/b", O_CREATE|O_RDWR)); + unlink("b/b"); + } else if(what == 11){ + unlink("b"); + link("../grindir/./../a", "../b"); + } else if(what == 12){ + unlink("../grindir/../a"); + link(".././b", "/grindir/../a"); + } else if(what == 13){ + int pid = fork(); + if(pid == 0){ + exit(0); + } else if(pid < 0){ + printf("grind: fork failed\n"); + exit(1); + } + wait(0); + } else if(what == 14){ + int pid = fork(); + if(pid == 0){ + fork(); + fork(); + exit(0); + } else if(pid < 0){ + printf("grind: fork failed\n"); + exit(1); + } + wait(0); + } else if(what == 15){ + sbrk(6011); + } else if(what == 16){ + if(sbrk(0) > break0) + sbrk(-(sbrk(0) - break0)); + } else if(what == 17){ + int pid = fork(); + if(pid == 0){ + close(open("a", O_CREATE|O_RDWR)); + exit(0); + } else if(pid < 0){ + printf("grind: fork failed\n"); + exit(1); + } + if(chdir("../grindir/..") != 0){ + printf("grind: chdir failed\n"); + exit(1); + } + kill(pid); + wait(0); + } else if(what == 18){ + int pid = fork(); + if(pid == 0){ + kill(getpid()); + exit(0); + } else if(pid < 0){ + printf("grind: fork failed\n"); + exit(1); + } + wait(0); + } else if(what == 19){ + int fds[2]; + if(pipe(fds) < 0){ + printf("grind: pipe failed\n"); + exit(1); + } + int pid = fork(); + if(pid == 0){ + fork(); + fork(); + if(write(fds[1], "x", 1) != 1) + printf("grind: pipe write failed\n"); + char c; + if(read(fds[0], &c, 1) != 1) + printf("grind: pipe read failed\n"); + exit(0); + } else if(pid < 0){ + printf("grind: fork failed\n"); + exit(1); + } + close(fds[0]); + close(fds[1]); + wait(0); + } else if(what == 20){ + int pid = fork(); + if(pid == 0){ + unlink("a"); + mkdir("a"); + chdir("a"); + unlink("../a"); + fd = open("x", O_CREATE|O_RDWR); + unlink("x"); + exit(0); + } else if(pid < 0){ + printf("grind: fork failed\n"); + exit(1); + } + wait(0); + } else if(what == 21){ + unlink("c"); + // should always succeed. check that there are free i-nodes, + // file descriptors, blocks. + int fd1 = open("c", O_CREATE|O_RDWR); + if(fd1 < 0){ + printf("grind: create c failed\n"); + exit(1); + } + if(write(fd1, "x", 1) != 1){ + printf("grind: write c failed\n"); + exit(1); + } + struct stat st; + if(fstat(fd1, &st) != 0){ + printf("grind: fstat failed\n"); + exit(1); + } + if(st.size != 1){ + printf("grind: fstat reports wrong size %d\n", (int)st.size); + exit(1); + } + if(st.ino > 200){ + printf("grind: fstat reports crazy i-number %d\n", st.ino); + exit(1); + } + close(fd1); + unlink("c"); + } else if(what == 22){ + // echo hi | cat + int aa[2], bb[2]; + if(pipe(aa) < 0){ + fprintf(2, "grind: pipe failed\n"); + exit(1); + } + if(pipe(bb) < 0){ + fprintf(2, "grind: pipe failed\n"); + exit(1); + } + int pid1 = fork(); + if(pid1 == 0){ + close(bb[0]); + close(bb[1]); + close(aa[0]); + close(1); + if(dup(aa[1]) != 1){ + fprintf(2, "grind: dup failed\n"); + exit(1); + } + close(aa[1]); + char *args[3] = { "echo", "hi", 0 }; + exec("grindir/../echo", args); + fprintf(2, "grind: echo: not found\n"); + exit(2); + } else if(pid1 < 0){ + fprintf(2, "grind: fork failed\n"); + exit(3); + } + int pid2 = fork(); + if(pid2 == 0){ + close(aa[1]); + close(bb[0]); + close(0); + if(dup(aa[0]) != 0){ + fprintf(2, "grind: dup failed\n"); + exit(4); + } + close(aa[0]); + close(1); + if(dup(bb[1]) != 1){ + fprintf(2, "grind: dup failed\n"); + exit(5); + } + close(bb[1]); + char *args[2] = { "cat", 0 }; + exec("/cat", args); + fprintf(2, "grind: cat: not found\n"); + exit(6); + } else if(pid2 < 0){ + fprintf(2, "grind: fork failed\n"); + exit(7); + } + close(aa[0]); + close(aa[1]); + close(bb[1]); + char buf[4] = { 0, 0, 0, 0 }; + read(bb[0], buf+0, 1); + read(bb[0], buf+1, 1); + read(bb[0], buf+2, 1); + close(bb[0]); + int st1, st2; + wait(&st1); + wait(&st2); + if(st1 != 0 || st2 != 0 || strcmp(buf, "hi\n") != 0){ + printf("grind: exec pipeline failed %d %d \"%s\"\n", st1, st2, buf); + exit(1); + } + } + } +} + +void +iter() +{ + unlink("a"); + unlink("b"); + + int pid1 = fork(); + if(pid1 < 0){ + printf("grind: fork failed\n"); + exit(1); + } + if(pid1 == 0){ + rand_next ^= 31; + go(0); + exit(0); + } + + int pid2 = fork(); + if(pid2 < 0){ + printf("grind: fork failed\n"); + exit(1); + } + if(pid2 == 0){ + rand_next ^= 7177; + go(1); + exit(0); + } + + int st1 = -1; + wait(&st1); + if(st1 != 0){ + kill(pid1); + kill(pid2); + } + int st2 = -1; + wait(&st2); + + exit(0); +} + +int +main() +{ + while(1){ + int pid = fork(); + if(pid == 0){ + iter(); + exit(0); + } + if(pid > 0){ + wait(0); + } + sleep(20); + rand_next += 1; + } +} diff --git a/1/xv6-riscv-riscv/user/init.c b/1/xv6-riscv-riscv/user/init.c new file mode 100644 index 0000000000000000000000000000000000000000..e0a5689524c083dce90d0281c773121e8d3c4a6b --- /dev/null +++ b/1/xv6-riscv-riscv/user/init.c @@ -0,0 +1,54 @@ +// init: The initial user-level program + +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/spinlock.h" +#include "kernel/sleeplock.h" +#include "kernel/fs.h" +#include "kernel/file.h" +#include "user/user.h" +#include "kernel/fcntl.h" + +char *argv[] = { "sh", 0 }; + +int +main(void) +{ + int pid, wpid; + + if(open("console", O_RDWR) < 0){ + mknod("console", CONSOLE, 0); + open("console", O_RDWR); + } + dup(0); // stdout + dup(0); // stderr + + for(;;){ + printf("init: starting sh\n"); + pid = fork(); + if(pid < 0){ + printf("init: fork failed\n"); + exit(1); + } + if(pid == 0){ + exec("sh", argv); + printf("init: exec sh failed\n"); + exit(1); + } + + for(;;){ + // this call to wait() returns if the shell exits, + // or if a parentless process exits. + wpid = wait((int *) 0); + if(wpid == pid){ + // the shell exited; restart it. + break; + } else if(wpid < 0){ + printf("init: wait returned an error\n"); + exit(1); + } else { + // it was a parentless process; do nothing. + } + } + } +} diff --git a/1/xv6-riscv-riscv/user/initcode.S b/1/xv6-riscv-riscv/user/initcode.S new file mode 100644 index 0000000000000000000000000000000000000000..ba44ff57b07528acdb50afcc7317e4d0a2dc5071 --- /dev/null +++ b/1/xv6-riscv-riscv/user/initcode.S @@ -0,0 +1,28 @@ +# Initial process that execs /init. +# This code runs in user space. + +#include "syscall.h" + +# exec(init, argv) +.globl start +start: + la a0, init + la a1, argv + li a7, SYS_exec + ecall + +# for(;;) exit(); +exit: + li a7, SYS_exit + ecall + jal exit + +# char init[] = "/init\0"; +init: + .string "/init\0" + +# char *argv[] = { init, 0 }; +.p2align 2 +argv: + .quad init + .quad 0 diff --git a/1/xv6-riscv-riscv/user/kill.c b/1/xv6-riscv-riscv/user/kill.c new file mode 100644 index 0000000000000000000000000000000000000000..1b0253b978e45f0145ff94d723c97ecaf998d129 --- /dev/null +++ b/1/xv6-riscv-riscv/user/kill.c @@ -0,0 +1,17 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char **argv) +{ + int i; + + if(argc < 2){ + fprintf(2, "usage: kill pid...\n"); + exit(1); + } + for(i=1; i<argc; i++) + kill(atoi(argv[i])); + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/ln.c b/1/xv6-riscv-riscv/user/ln.c new file mode 100644 index 0000000000000000000000000000000000000000..1894143708ef9e777ff4840f0a1ca8f9686162de --- /dev/null +++ b/1/xv6-riscv-riscv/user/ln.c @@ -0,0 +1,15 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ + if(argc != 3){ + fprintf(2, "Usage: ln old new\n"); + exit(1); + } + if(link(argv[1], argv[2]) < 0) + fprintf(2, "link %s %s: failed\n", argv[1], argv[2]); + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/ls.c b/1/xv6-riscv-riscv/user/ls.c new file mode 100644 index 0000000000000000000000000000000000000000..39ab074779ae36dbb937744e8d44168f317e7a0c --- /dev/null +++ b/1/xv6-riscv-riscv/user/ls.c @@ -0,0 +1,87 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" +#include "kernel/fs.h" +#include "kernel/fcntl.h" + +char* +fmtname(char *path) +{ + static char buf[DIRSIZ+1]; + char *p; + + // Find first character after last slash. + for(p=path+strlen(path); p >= path && *p != '/'; p--) + ; + p++; + + // Return blank-padded name. + if(strlen(p) >= DIRSIZ) + return p; + memmove(buf, p, strlen(p)); + memset(buf+strlen(p), ' ', DIRSIZ-strlen(p)); + return buf; +} + +void +ls(char *path) +{ + char buf[512], *p; + int fd; + struct dirent de; + struct stat st; + + if((fd = open(path, O_RDONLY)) < 0){ + fprintf(2, "ls: cannot open %s\n", path); + return; + } + + if(fstat(fd, &st) < 0){ + fprintf(2, "ls: cannot stat %s\n", path); + close(fd); + return; + } + + switch(st.type){ + case T_DEVICE: + case T_FILE: + printf("%s %d %d %d\n", fmtname(path), st.type, st.ino, (int) st.size); + break; + + case T_DIR: + if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){ + printf("ls: path too long\n"); + break; + } + strcpy(buf, path); + p = buf+strlen(buf); + *p++ = '/'; + while(read(fd, &de, sizeof(de)) == sizeof(de)){ + if(de.inum == 0) + continue; + memmove(p, de.name, DIRSIZ); + p[DIRSIZ] = 0; + if(stat(buf, &st) < 0){ + printf("ls: cannot stat %s\n", buf); + continue; + } + printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, (int) st.size); + } + break; + } + close(fd); +} + +int +main(int argc, char *argv[]) +{ + int i; + + if(argc < 2){ + ls("."); + exit(0); + } + for(i=1; i<argc; i++) + ls(argv[i]); + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/mkdir.c b/1/xv6-riscv-riscv/user/mkdir.c new file mode 100644 index 0000000000000000000000000000000000000000..c2b31c1378ad1ad4c597bd30833d431431a1937a --- /dev/null +++ b/1/xv6-riscv-riscv/user/mkdir.c @@ -0,0 +1,23 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ + int i; + + if(argc < 2){ + fprintf(2, "Usage: mkdir files...\n"); + exit(1); + } + + for(i = 1; i < argc; i++){ + if(mkdir(argv[i]) < 0){ + fprintf(2, "mkdir: %s failed to create\n", argv[i]); + break; + } + } + + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/printf.c b/1/xv6-riscv-riscv/user/printf.c new file mode 100644 index 0000000000000000000000000000000000000000..81787467fa7910a827f21abe7cae444ca2213cb4 --- /dev/null +++ b/1/xv6-riscv-riscv/user/printf.c @@ -0,0 +1,157 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +#include <stdarg.h> + +static char digits[] = "0123456789ABCDEF"; + +static void +putc(int fd, char c) +{ + write(fd, &c, 1); +} + +static void +printint(int fd, int xx, int base, int sgn) +{ + char buf[16]; + int i, neg; + uint x; + + neg = 0; + if(sgn && xx < 0){ + neg = 1; + x = -xx; + } else { + x = xx; + } + + i = 0; + do{ + buf[i++] = digits[x % base]; + }while((x /= base) != 0); + if(neg) + buf[i++] = '-'; + + while(--i >= 0) + putc(fd, buf[i]); +} + +static void +printptr(int fd, uint64 x) { + int i; + putc(fd, '0'); + putc(fd, 'x'); + for (i = 0; i < (sizeof(uint64) * 2); i++, x <<= 4) + putc(fd, digits[x >> (sizeof(uint64) * 8 - 4)]); +} + +// Print to the given fd. Only understands %d, %x, %p, %s. +void +vprintf(int fd, const char *fmt, va_list ap) +{ + char *s; + int c0, c1, c2, i, state; + + state = 0; + for(i = 0; fmt[i]; i++){ + c0 = fmt[i] & 0xff; + if(state == 0){ + if(c0 == '%'){ + state = '%'; + } else { + putc(fd, c0); + } + } else if(state == '%'){ + c1 = c2 = 0; + if(c0) c1 = fmt[i+1] & 0xff; + if(c1) c2 = fmt[i+2] & 0xff; + if(c0 == 'd'){ + printint(fd, va_arg(ap, int), 10, 1); + } else if(c0 == 'l' && c1 == 'd'){ + printint(fd, va_arg(ap, uint64), 10, 1); + i += 1; + } else if(c0 == 'l' && c1 == 'l' && c2 == 'd'){ + printint(fd, va_arg(ap, uint64), 10, 1); + i += 2; + } else if(c0 == 'u'){ + printint(fd, va_arg(ap, int), 10, 0); + } else if(c0 == 'l' && c1 == 'u'){ + printint(fd, va_arg(ap, uint64), 10, 0); + i += 1; + } else if(c0 == 'l' && c1 == 'l' && c2 == 'u'){ + printint(fd, va_arg(ap, uint64), 10, 0); + i += 2; + } else if(c0 == 'x'){ + printint(fd, va_arg(ap, int), 16, 0); + } else if(c0 == 'l' && c1 == 'x'){ + printint(fd, va_arg(ap, uint64), 16, 0); + i += 1; + } else if(c0 == 'l' && c1 == 'l' && c2 == 'x'){ + printint(fd, va_arg(ap, uint64), 16, 0); + i += 2; + } else if(c0 == 'p'){ + printptr(fd, va_arg(ap, uint64)); + } else if(c0 == 's'){ + if((s = va_arg(ap, char*)) == 0) + s = "(null)"; + for(; *s; s++) + putc(fd, *s); + } else if(c0 == '%'){ + putc(fd, '%'); + } else { + // Unknown % sequence. Print it to draw attention. + putc(fd, '%'); + putc(fd, c0); + } + +#if 0 + if(c == 'd'){ + printint(fd, va_arg(ap, int), 10, 1); + } else if(c == 'l') { + printint(fd, va_arg(ap, uint64), 10, 0); + } else if(c == 'x') { + printint(fd, va_arg(ap, int), 16, 0); + } else if(c == 'p') { + printptr(fd, va_arg(ap, uint64)); + } else if(c == 's'){ + s = va_arg(ap, char*); + if(s == 0) + s = "(null)"; + while(*s != 0){ + putc(fd, *s); + s++; + } + } else if(c == 'c'){ + putc(fd, va_arg(ap, uint)); + } else if(c == '%'){ + putc(fd, c); + } else { + // Unknown % sequence. Print it to draw attention. + putc(fd, '%'); + putc(fd, c); + } +#endif + state = 0; + } + } +} + +void +fprintf(int fd, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fd, fmt, ap); +} + +void +printf(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(1, fmt, ap); +} diff --git a/1/xv6-riscv-riscv/user/rm.c b/1/xv6-riscv-riscv/user/rm.c new file mode 100644 index 0000000000000000000000000000000000000000..26b8f1feeef4503687a2243f02972567027154a9 --- /dev/null +++ b/1/xv6-riscv-riscv/user/rm.c @@ -0,0 +1,23 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(int argc, char *argv[]) +{ + int i; + + if(argc < 2){ + fprintf(2, "Usage: rm files...\n"); + exit(1); + } + + for(i = 1; i < argc; i++){ + if(unlink(argv[i]) < 0){ + fprintf(2, "rm: %s failed to delete\n", argv[i]); + break; + } + } + + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/sh.c b/1/xv6-riscv-riscv/user/sh.c new file mode 100644 index 0000000000000000000000000000000000000000..836ebcbc7eba2f8133085b96c376a20d9e7ce565 --- /dev/null +++ b/1/xv6-riscv-riscv/user/sh.c @@ -0,0 +1,494 @@ +// Shell. + +#include "kernel/types.h" +#include "user/user.h" +#include "kernel/fcntl.h" + +// Parsed command representation +#define EXEC 1 +#define REDIR 2 +#define PIPE 3 +#define LIST 4 +#define BACK 5 + +#define MAXARGS 10 + +struct cmd { + int type; +}; + +struct execcmd { + int type; + char *argv[MAXARGS]; + char *eargv[MAXARGS]; +}; + +struct redircmd { + int type; + struct cmd *cmd; + char *file; + char *efile; + int mode; + int fd; +}; + +struct pipecmd { + int type; + struct cmd *left; + struct cmd *right; +}; + +struct listcmd { + int type; + struct cmd *left; + struct cmd *right; +}; + +struct backcmd { + int type; + struct cmd *cmd; +}; + +int fork1(void); // Fork but panics on failure. +void panic(char*); +struct cmd *parsecmd(char*); +void runcmd(struct cmd*) __attribute__((noreturn)); + +// Execute cmd. Never returns. +void +runcmd(struct cmd *cmd) +{ + int p[2]; + struct backcmd *bcmd; + struct execcmd *ecmd; + struct listcmd *lcmd; + struct pipecmd *pcmd; + struct redircmd *rcmd; + + if(cmd == 0) + exit(1); + + switch(cmd->type){ + default: + panic("runcmd"); + + case EXEC: + ecmd = (struct execcmd*)cmd; + if(ecmd->argv[0] == 0) + exit(1); + exec(ecmd->argv[0], ecmd->argv); + fprintf(2, "exec %s failed\n", ecmd->argv[0]); + break; + + case REDIR: + rcmd = (struct redircmd*)cmd; + close(rcmd->fd); + if(open(rcmd->file, rcmd->mode) < 0){ + fprintf(2, "open %s failed\n", rcmd->file); + exit(1); + } + runcmd(rcmd->cmd); + break; + + case LIST: + lcmd = (struct listcmd*)cmd; + if(fork1() == 0) + runcmd(lcmd->left); + wait(0); + runcmd(lcmd->right); + break; + + case PIPE: + pcmd = (struct pipecmd*)cmd; + if(pipe(p) < 0) + panic("pipe"); + if(fork1() == 0){ + close(1); + dup(p[1]); + close(p[0]); + close(p[1]); + runcmd(pcmd->left); + } + if(fork1() == 0){ + close(0); + dup(p[0]); + close(p[0]); + close(p[1]); + runcmd(pcmd->right); + } + close(p[0]); + close(p[1]); + wait(0); + wait(0); + break; + + case BACK: + bcmd = (struct backcmd*)cmd; + if(fork1() == 0) + runcmd(bcmd->cmd); + break; + } + exit(0); +} + +int +getcmd(char *buf, int nbuf) +{ + write(2, "$ ", 2); + memset(buf, 0, nbuf); + gets(buf, nbuf); + if(buf[0] == 0) // EOF + return -1; + return 0; +} + +int +main(void) +{ + static char buf[100]; + int fd; + + // Ensure that three file descriptors are open. + while((fd = open("console", O_RDWR)) >= 0){ + if(fd >= 3){ + close(fd); + break; + } + } + + // Read and run input commands. + while(getcmd(buf, sizeof(buf)) >= 0){ + if(buf[0] == 'c' && buf[1] == 'd' && buf[2] == ' '){ + // Chdir must be called by the parent, not the child. + buf[strlen(buf)-1] = 0; // chop \n + if(chdir(buf+3) < 0) + fprintf(2, "cannot cd %s\n", buf+3); + continue; + } + if(fork1() == 0) + runcmd(parsecmd(buf)); + wait(0); + } + exit(0); +} + +void +panic(char *s) +{ + fprintf(2, "%s\n", s); + exit(1); +} + +int +fork1(void) +{ + int pid; + + pid = fork(); + if(pid == -1) + panic("fork"); + return pid; +} + +//PAGEBREAK! +// Constructors + +struct cmd* +execcmd(void) +{ + struct execcmd *cmd; + + cmd = malloc(sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->type = EXEC; + return (struct cmd*)cmd; +} + +struct cmd* +redircmd(struct cmd *subcmd, char *file, char *efile, int mode, int fd) +{ + struct redircmd *cmd; + + cmd = malloc(sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->type = REDIR; + cmd->cmd = subcmd; + cmd->file = file; + cmd->efile = efile; + cmd->mode = mode; + cmd->fd = fd; + return (struct cmd*)cmd; +} + +struct cmd* +pipecmd(struct cmd *left, struct cmd *right) +{ + struct pipecmd *cmd; + + cmd = malloc(sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->type = PIPE; + cmd->left = left; + cmd->right = right; + return (struct cmd*)cmd; +} + +struct cmd* +listcmd(struct cmd *left, struct cmd *right) +{ + struct listcmd *cmd; + + cmd = malloc(sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->type = LIST; + cmd->left = left; + cmd->right = right; + return (struct cmd*)cmd; +} + +struct cmd* +backcmd(struct cmd *subcmd) +{ + struct backcmd *cmd; + + cmd = malloc(sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + cmd->type = BACK; + cmd->cmd = subcmd; + return (struct cmd*)cmd; +} +//PAGEBREAK! +// Parsing + +char whitespace[] = " \t\r\n\v"; +char symbols[] = "<|>&;()"; + +int +gettoken(char **ps, char *es, char **q, char **eq) +{ + char *s; + int ret; + + s = *ps; + while(s < es && strchr(whitespace, *s)) + s++; + if(q) + *q = s; + ret = *s; + switch(*s){ + case 0: + break; + case '|': + case '(': + case ')': + case ';': + case '&': + case '<': + s++; + break; + case '>': + s++; + if(*s == '>'){ + ret = '+'; + s++; + } + break; + default: + ret = 'a'; + while(s < es && !strchr(whitespace, *s) && !strchr(symbols, *s)) + s++; + break; + } + if(eq) + *eq = s; + + while(s < es && strchr(whitespace, *s)) + s++; + *ps = s; + return ret; +} + +int +peek(char **ps, char *es, char *toks) +{ + char *s; + + s = *ps; + while(s < es && strchr(whitespace, *s)) + s++; + *ps = s; + return *s && strchr(toks, *s); +} + +struct cmd *parseline(char**, char*); +struct cmd *parsepipe(char**, char*); +struct cmd *parseexec(char**, char*); +struct cmd *nulterminate(struct cmd*); + +struct cmd* +parsecmd(char *s) +{ + char *es; + struct cmd *cmd; + + es = s + strlen(s); + cmd = parseline(&s, es); + peek(&s, es, ""); + if(s != es){ + fprintf(2, "leftovers: %s\n", s); + panic("syntax"); + } + nulterminate(cmd); + return cmd; +} + +struct cmd* +parseline(char **ps, char *es) +{ + struct cmd *cmd; + + cmd = parsepipe(ps, es); + while(peek(ps, es, "&")){ + gettoken(ps, es, 0, 0); + cmd = backcmd(cmd); + } + if(peek(ps, es, ";")){ + gettoken(ps, es, 0, 0); + cmd = listcmd(cmd, parseline(ps, es)); + } + return cmd; +} + +struct cmd* +parsepipe(char **ps, char *es) +{ + struct cmd *cmd; + + cmd = parseexec(ps, es); + if(peek(ps, es, "|")){ + gettoken(ps, es, 0, 0); + cmd = pipecmd(cmd, parsepipe(ps, es)); + } + return cmd; +} + +struct cmd* +parseredirs(struct cmd *cmd, char **ps, char *es) +{ + int tok; + char *q, *eq; + + while(peek(ps, es, "<>")){ + tok = gettoken(ps, es, 0, 0); + if(gettoken(ps, es, &q, &eq) != 'a') + panic("missing file for redirection"); + switch(tok){ + case '<': + cmd = redircmd(cmd, q, eq, O_RDONLY, 0); + break; + case '>': + cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE|O_TRUNC, 1); + break; + case '+': // >> + cmd = redircmd(cmd, q, eq, O_WRONLY|O_CREATE, 1); + break; + } + } + return cmd; +} + +struct cmd* +parseblock(char **ps, char *es) +{ + struct cmd *cmd; + + if(!peek(ps, es, "(")) + panic("parseblock"); + gettoken(ps, es, 0, 0); + cmd = parseline(ps, es); + if(!peek(ps, es, ")")) + panic("syntax - missing )"); + gettoken(ps, es, 0, 0); + cmd = parseredirs(cmd, ps, es); + return cmd; +} + +struct cmd* +parseexec(char **ps, char *es) +{ + char *q, *eq; + int tok, argc; + struct execcmd *cmd; + struct cmd *ret; + + if(peek(ps, es, "(")) + return parseblock(ps, es); + + ret = execcmd(); + cmd = (struct execcmd*)ret; + + argc = 0; + ret = parseredirs(ret, ps, es); + while(!peek(ps, es, "|)&;")){ + if((tok=gettoken(ps, es, &q, &eq)) == 0) + break; + if(tok != 'a') + panic("syntax"); + cmd->argv[argc] = q; + cmd->eargv[argc] = eq; + argc++; + if(argc >= MAXARGS) + panic("too many args"); + ret = parseredirs(ret, ps, es); + } + cmd->argv[argc] = 0; + cmd->eargv[argc] = 0; + return ret; +} + +// NUL-terminate all the counted strings. +struct cmd* +nulterminate(struct cmd *cmd) +{ + int i; + struct backcmd *bcmd; + struct execcmd *ecmd; + struct listcmd *lcmd; + struct pipecmd *pcmd; + struct redircmd *rcmd; + + if(cmd == 0) + return 0; + + switch(cmd->type){ + case EXEC: + ecmd = (struct execcmd*)cmd; + for(i=0; ecmd->argv[i]; i++) + *ecmd->eargv[i] = 0; + break; + + case REDIR: + rcmd = (struct redircmd*)cmd; + nulterminate(rcmd->cmd); + *rcmd->efile = 0; + break; + + case PIPE: + pcmd = (struct pipecmd*)cmd; + nulterminate(pcmd->left); + nulterminate(pcmd->right); + break; + + case LIST: + lcmd = (struct listcmd*)cmd; + nulterminate(lcmd->left); + nulterminate(lcmd->right); + break; + + case BACK: + bcmd = (struct backcmd*)cmd; + nulterminate(bcmd->cmd); + break; + } + return cmd; +} diff --git a/1/xv6-riscv-riscv/user/stressfs.c b/1/xv6-riscv-riscv/user/stressfs.c new file mode 100644 index 0000000000000000000000000000000000000000..247a7a503599c4f1d9434d4b67abe1b665757050 --- /dev/null +++ b/1/xv6-riscv-riscv/user/stressfs.c @@ -0,0 +1,49 @@ +// Demonstrate that moving the "acquire" in iderw after the loop that +// appends to the idequeue results in a race. + +// For this to work, you should also add a spin within iderw's +// idequeue traversal loop. Adding the following demonstrated a panic +// after about 5 runs of stressfs in QEMU on a 2.1GHz CPU: +// for (i = 0; i < 40000; i++) +// asm volatile(""); + +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" +#include "kernel/fs.h" +#include "kernel/fcntl.h" + +int +main(int argc, char *argv[]) +{ + int fd, i; + char path[] = "stressfs0"; + char data[512]; + + printf("stressfs starting\n"); + memset(data, 'a', sizeof(data)); + + for(i = 0; i < 4; i++) + if(fork() > 0) + break; + + printf("write %d\n", i); + + path[8] += i; + fd = open(path, O_CREATE | O_RDWR); + for(i = 0; i < 20; i++) +// printf(fd, "%d\n", i); + write(fd, data, sizeof(data)); + close(fd); + + printf("read\n"); + + fd = open(path, O_RDONLY); + for (i = 0; i < 20; i++) + read(fd, data, sizeof(data)); + close(fd); + + wait(0); + + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/ulib.c b/1/xv6-riscv-riscv/user/ulib.c new file mode 100644 index 0000000000000000000000000000000000000000..00648466f35d018b609dc4b8c162c8a7a48d32fb --- /dev/null +++ b/1/xv6-riscv-riscv/user/ulib.c @@ -0,0 +1,147 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +// +// wrapper so that it's OK if main() does not call exit(). +// +void +start() +{ + extern int main(); + main(); + exit(0); +} + +char* +strcpy(char *s, const char *t) +{ + char *os; + + os = s; + while((*s++ = *t++) != 0) + ; + return os; +} + +int +strcmp(const char *p, const char *q) +{ + while(*p && *p == *q) + p++, q++; + return (uchar)*p - (uchar)*q; +} + +uint +strlen(const char *s) +{ + int n; + + for(n = 0; s[n]; n++) + ; + return n; +} + +void* +memset(void *dst, int c, uint n) +{ + char *cdst = (char *) dst; + int i; + for(i = 0; i < n; i++){ + cdst[i] = c; + } + return dst; +} + +char* +strchr(const char *s, char c) +{ + for(; *s; s++) + if(*s == c) + return (char*)s; + return 0; +} + +char* +gets(char *buf, int max) +{ + int i, cc; + char c; + + for(i=0; i+1 < max; ){ + cc = read(0, &c, 1); + if(cc < 1) + break; + buf[i++] = c; + if(c == '\n' || c == '\r') + break; + } + buf[i] = '\0'; + return buf; +} + +int +stat(const char *n, struct stat *st) +{ + int fd; + int r; + + fd = open(n, O_RDONLY); + if(fd < 0) + return -1; + r = fstat(fd, st); + close(fd); + return r; +} + +int +atoi(const char *s) +{ + int n; + + n = 0; + while('0' <= *s && *s <= '9') + n = n*10 + *s++ - '0'; + return n; +} + +void* +memmove(void *vdst, const void *vsrc, int n) +{ + char *dst; + const char *src; + + dst = vdst; + src = vsrc; + if (src > dst) { + while(n-- > 0) + *dst++ = *src++; + } else { + dst += n; + src += n; + while(n-- > 0) + *--dst = *--src; + } + return vdst; +} + +int +memcmp(const void *s1, const void *s2, uint n) +{ + const char *p1 = s1, *p2 = s2; + while (n-- > 0) { + if (*p1 != *p2) { + return *p1 - *p2; + } + p1++; + p2++; + } + return 0; +} + +void * +memcpy(void *dst, const void *src, uint n) +{ + return memmove(dst, src, n); +} diff --git a/1/xv6-riscv-riscv/user/umalloc.c b/1/xv6-riscv-riscv/user/umalloc.c new file mode 100644 index 0000000000000000000000000000000000000000..2092a323c3f3117bf2f1a05518e5967b0316f937 --- /dev/null +++ b/1/xv6-riscv-riscv/user/umalloc.c @@ -0,0 +1,90 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" +#include "kernel/param.h" + +// Memory allocator by Kernighan and Ritchie, +// The C programming Language, 2nd ed. Section 8.7. + +typedef long Align; + +union header { + struct { + union header *ptr; + uint size; + } s; + Align x; +}; + +typedef union header Header; + +static Header base; +static Header *freep; + +void +free(void *ap) +{ + Header *bp, *p; + + bp = (Header*)ap - 1; + for(p = freep; !(bp > p && bp < p->s.ptr); p = p->s.ptr) + if(p >= p->s.ptr && (bp > p || bp < p->s.ptr)) + break; + if(bp + bp->s.size == p->s.ptr){ + bp->s.size += p->s.ptr->s.size; + bp->s.ptr = p->s.ptr->s.ptr; + } else + bp->s.ptr = p->s.ptr; + if(p + p->s.size == bp){ + p->s.size += bp->s.size; + p->s.ptr = bp->s.ptr; + } else + p->s.ptr = bp; + freep = p; +} + +static Header* +morecore(uint nu) +{ + char *p; + Header *hp; + + if(nu < 4096) + nu = 4096; + p = sbrk(nu * sizeof(Header)); + if(p == (char*)-1) + return 0; + hp = (Header*)p; + hp->s.size = nu; + free((void*)(hp + 1)); + return freep; +} + +void* +malloc(uint nbytes) +{ + Header *p, *prevp; + uint nunits; + + nunits = (nbytes + sizeof(Header) - 1)/sizeof(Header) + 1; + if((prevp = freep) == 0){ + base.s.ptr = freep = prevp = &base; + base.s.size = 0; + } + for(p = prevp->s.ptr; ; prevp = p, p = p->s.ptr){ + if(p->s.size >= nunits){ + if(p->s.size == nunits) + prevp->s.ptr = p->s.ptr; + else { + p->s.size -= nunits; + p += p->s.size; + p->s.size = nunits; + } + freep = prevp; + return (void*)(p + 1); + } + if(p == freep) + if((p = morecore(nunits)) == 0) + return 0; + } +} diff --git a/1/xv6-riscv-riscv/user/user.h b/1/xv6-riscv-riscv/user/user.h new file mode 100644 index 0000000000000000000000000000000000000000..f16fe278281e40a79abfa4c734700e9fcf1f5dda --- /dev/null +++ b/1/xv6-riscv-riscv/user/user.h @@ -0,0 +1,43 @@ +struct stat; + +// system calls +int fork(void); +int exit(int) __attribute__((noreturn)); +int wait(int*); +int pipe(int*); +int write(int, const void*, int); +int read(int, void*, int); +int close(int); +int kill(int); +int exec(const char*, char**); +int open(const char*, int); +int mknod(const char*, short, short); +int unlink(const char*); +int fstat(int fd, struct stat*); +int link(const char*, const char*); +int mkdir(const char*); +int chdir(const char*); +int dup(int); +int getpid(void); +char* sbrk(int); +int sleep(int); +int uptime(void); + +// ulib.c +int stat(const char*, struct stat*); +char* strcpy(char*, const char*); +void *memmove(void*, const void*, int); +char* strchr(const char*, char c); +int strcmp(const char*, const char*); +void fprintf(int, const char*, ...) __attribute__ ((format (printf, 2, 3))); +void printf(const char*, ...) __attribute__ ((format (printf, 1, 2))); +char* gets(char*, int max); +uint strlen(const char*); +void* memset(void*, int, uint); +int atoi(const char*); +int memcmp(const void *, const void *, uint); +void *memcpy(void *, const void *, uint); + +// umalloc.c +void* malloc(uint); +void free(void*); diff --git a/1/xv6-riscv-riscv/user/user.ld b/1/xv6-riscv-riscv/user/user.ld new file mode 100644 index 0000000000000000000000000000000000000000..3da93e00d561e260fbe36584494ca8759dff698d --- /dev/null +++ b/1/xv6-riscv-riscv/user/user.ld @@ -0,0 +1,39 @@ +OUTPUT_ARCH( "riscv" ) + +SECTIONS +{ + . = 0x0; + + .text : { + *(.text .text.*) + } + + .rodata : { + . = ALIGN(16); + *(.srodata .srodata.*) /* do not need to distinguish this from .rodata */ + . = ALIGN(16); + *(.rodata .rodata.*) + } + + .eh_frame : { + *(.eh_frame) + *(.eh_frame.*) + } + + . = ALIGN(0x1000); + .data : { + . = ALIGN(16); + *(.sdata .sdata.*) /* do not need to distinguish this from .data */ + . = ALIGN(16); + *(.data .data.*) + } + + .bss : { + . = ALIGN(16); + *(.sbss .sbss.*) /* do not need to distinguish this from .bss */ + . = ALIGN(16); + *(.bss .bss.*) + } + + PROVIDE(end = .); +} diff --git a/1/xv6-riscv-riscv/user/usertests.c b/1/xv6-riscv-riscv/user/usertests.c new file mode 100644 index 0000000000000000000000000000000000000000..28b53f9a6d4c21ea1bcf489359d4ea94abc472d9 --- /dev/null +++ b/1/xv6-riscv-riscv/user/usertests.c @@ -0,0 +1,3118 @@ +#include "kernel/param.h" +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" +#include "kernel/fs.h" +#include "kernel/fcntl.h" +#include "kernel/syscall.h" +#include "kernel/memlayout.h" +#include "kernel/riscv.h" + +// +// Tests xv6 system calls. usertests without arguments runs them all +// and usertests <name> runs <name> test. The test runner creates for +// each test a process and based on the exit status of the process, +// the test runner reports "OK" or "FAILED". Some tests result in +// kernel printing usertrap messages, which can be ignored if test +// prints "OK". +// + +#define BUFSZ ((MAXOPBLOCKS+2)*BSIZE) + +char buf[BUFSZ]; + +// +// Section with tests that run fairly quickly. Use -q if you want to +// run just those. With -q usertests also runs the ones that take a +// fair of time. +// + +// what if you pass ridiculous pointers to system calls +// that read user memory with copyin? +void +copyin(char *s) +{ + uint64 addrs[] = { 0x80000000LL, 0x3fffffe000, 0x3ffffff000, 0x4000000000, + 0xffffffffffffffff }; + + for(int ai = 0; ai < sizeof(addrs)/sizeof(addrs[0]); ai++){ + uint64 addr = addrs[ai]; + + int fd = open("copyin1", O_CREATE|O_WRONLY); + if(fd < 0){ + printf("open(copyin1) failed\n"); + exit(1); + } + int n = write(fd, (void*)addr, 8192); + if(n >= 0){ + printf("write(fd, %p, 8192) returned %d, not -1\n", (void*)addr, n); + exit(1); + } + close(fd); + unlink("copyin1"); + + n = write(1, (char*)addr, 8192); + if(n > 0){ + printf("write(1, %p, 8192) returned %d, not -1 or 0\n", (void*)addr, n); + exit(1); + } + + int fds[2]; + if(pipe(fds) < 0){ + printf("pipe() failed\n"); + exit(1); + } + n = write(fds[1], (char*)addr, 8192); + if(n > 0){ + printf("write(pipe, %p, 8192) returned %d, not -1 or 0\n", (void*)addr, n); + exit(1); + } + close(fds[0]); + close(fds[1]); + } +} + +// what if you pass ridiculous pointers to system calls +// that write user memory with copyout? +void +copyout(char *s) +{ + uint64 addrs[] = { 0LL, 0x80000000LL, 0x3fffffe000, 0x3ffffff000, 0x4000000000, + 0xffffffffffffffff }; + + for(int ai = 0; ai < sizeof(addrs)/sizeof(addrs[0]); ai++){ + uint64 addr = addrs[ai]; + + int fd = open("README", 0); + if(fd < 0){ + printf("open(README) failed\n"); + exit(1); + } + int n = read(fd, (void*)addr, 8192); + if(n > 0){ + printf("read(fd, %p, 8192) returned %d, not -1 or 0\n", (void*)addr, n); + exit(1); + } + close(fd); + + int fds[2]; + if(pipe(fds) < 0){ + printf("pipe() failed\n"); + exit(1); + } + n = write(fds[1], "x", 1); + if(n != 1){ + printf("pipe write failed\n"); + exit(1); + } + n = read(fds[0], (void*)addr, 8192); + if(n > 0){ + printf("read(pipe, %p, 8192) returned %d, not -1 or 0\n", (void*)addr, n); + exit(1); + } + close(fds[0]); + close(fds[1]); + } +} + +// what if you pass ridiculous string pointers to system calls? +void +copyinstr1(char *s) +{ + uint64 addrs[] = { 0x80000000LL, 0x3fffffe000, 0x3ffffff000, 0x4000000000, + 0xffffffffffffffff }; + + for(int ai = 0; ai < sizeof(addrs)/sizeof(addrs[0]); ai++){ + uint64 addr = addrs[ai]; + + int fd = open((char *)addr, O_CREATE|O_WRONLY); + if(fd >= 0){ + printf("open(%p) returned %d, not -1\n", (void*)addr, fd); + exit(1); + } + } +} + +// what if a string system call argument is exactly the size +// of the kernel buffer it is copied into, so that the null +// would fall just beyond the end of the kernel buffer? +void +copyinstr2(char *s) +{ + char b[MAXPATH+1]; + + for(int i = 0; i < MAXPATH; i++) + b[i] = 'x'; + b[MAXPATH] = '\0'; + + int ret = unlink(b); + if(ret != -1){ + printf("unlink(%s) returned %d, not -1\n", b, ret); + exit(1); + } + + int fd = open(b, O_CREATE | O_WRONLY); + if(fd != -1){ + printf("open(%s) returned %d, not -1\n", b, fd); + exit(1); + } + + ret = link(b, b); + if(ret != -1){ + printf("link(%s, %s) returned %d, not -1\n", b, b, ret); + exit(1); + } + + char *args[] = { "xx", 0 }; + ret = exec(b, args); + if(ret != -1){ + printf("exec(%s) returned %d, not -1\n", b, fd); + exit(1); + } + + int pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(1); + } + if(pid == 0){ + static char big[PGSIZE+1]; + for(int i = 0; i < PGSIZE; i++) + big[i] = 'x'; + big[PGSIZE] = '\0'; + char *args2[] = { big, big, big, 0 }; + ret = exec("echo", args2); + if(ret != -1){ + printf("exec(echo, BIG) returned %d, not -1\n", fd); + exit(1); + } + exit(747); // OK + } + + int st = 0; + wait(&st); + if(st != 747){ + printf("exec(echo, BIG) succeeded, should have failed\n"); + exit(1); + } +} + +// what if a string argument crosses over the end of last user page? +void +copyinstr3(char *s) +{ + sbrk(8192); + uint64 top = (uint64) sbrk(0); + if((top % PGSIZE) != 0){ + sbrk(PGSIZE - (top % PGSIZE)); + } + top = (uint64) sbrk(0); + if(top % PGSIZE){ + printf("oops\n"); + exit(1); + } + + char *b = (char *) (top - 1); + *b = 'x'; + + int ret = unlink(b); + if(ret != -1){ + printf("unlink(%s) returned %d, not -1\n", b, ret); + exit(1); + } + + int fd = open(b, O_CREATE | O_WRONLY); + if(fd != -1){ + printf("open(%s) returned %d, not -1\n", b, fd); + exit(1); + } + + ret = link(b, b); + if(ret != -1){ + printf("link(%s, %s) returned %d, not -1\n", b, b, ret); + exit(1); + } + + char *args[] = { "xx", 0 }; + ret = exec(b, args); + if(ret != -1){ + printf("exec(%s) returned %d, not -1\n", b, fd); + exit(1); + } +} + +// See if the kernel refuses to read/write user memory that the +// application doesn't have anymore, because it returned it. +void +rwsbrk() +{ + int fd, n; + + uint64 a = (uint64) sbrk(8192); + + if(a == 0xffffffffffffffffLL) { + printf("sbrk(rwsbrk) failed\n"); + exit(1); + } + + if ((uint64) sbrk(-8192) == 0xffffffffffffffffLL) { + printf("sbrk(rwsbrk) shrink failed\n"); + exit(1); + } + + fd = open("rwsbrk", O_CREATE|O_WRONLY); + if(fd < 0){ + printf("open(rwsbrk) failed\n"); + exit(1); + } + n = write(fd, (void*)(a+4096), 1024); + if(n >= 0){ + printf("write(fd, %p, 1024) returned %d, not -1\n", (void*)a+4096, n); + exit(1); + } + close(fd); + unlink("rwsbrk"); + + fd = open("README", O_RDONLY); + if(fd < 0){ + printf("open(rwsbrk) failed\n"); + exit(1); + } + n = read(fd, (void*)(a+4096), 10); + if(n >= 0){ + printf("read(fd, %p, 10) returned %d, not -1\n", (void*)a+4096, n); + exit(1); + } + close(fd); + + exit(0); +} + +// test O_TRUNC. +void +truncate1(char *s) +{ + char buf[32]; + + unlink("truncfile"); + int fd1 = open("truncfile", O_CREATE|O_WRONLY|O_TRUNC); + write(fd1, "abcd", 4); + close(fd1); + + int fd2 = open("truncfile", O_RDONLY); + int n = read(fd2, buf, sizeof(buf)); + if(n != 4){ + printf("%s: read %d bytes, wanted 4\n", s, n); + exit(1); + } + + fd1 = open("truncfile", O_WRONLY|O_TRUNC); + + int fd3 = open("truncfile", O_RDONLY); + n = read(fd3, buf, sizeof(buf)); + if(n != 0){ + printf("aaa fd3=%d\n", fd3); + printf("%s: read %d bytes, wanted 0\n", s, n); + exit(1); + } + + n = read(fd2, buf, sizeof(buf)); + if(n != 0){ + printf("bbb fd2=%d\n", fd2); + printf("%s: read %d bytes, wanted 0\n", s, n); + exit(1); + } + + write(fd1, "abcdef", 6); + + n = read(fd3, buf, sizeof(buf)); + if(n != 6){ + printf("%s: read %d bytes, wanted 6\n", s, n); + exit(1); + } + + n = read(fd2, buf, sizeof(buf)); + if(n != 2){ + printf("%s: read %d bytes, wanted 2\n", s, n); + exit(1); + } + + unlink("truncfile"); + + close(fd1); + close(fd2); + close(fd3); +} + +// write to an open FD whose file has just been truncated. +// this causes a write at an offset beyond the end of the file. +// such writes fail on xv6 (unlike POSIX) but at least +// they don't crash. +void +truncate2(char *s) +{ + unlink("truncfile"); + + int fd1 = open("truncfile", O_CREATE|O_TRUNC|O_WRONLY); + write(fd1, "abcd", 4); + + int fd2 = open("truncfile", O_TRUNC|O_WRONLY); + + int n = write(fd1, "x", 1); + if(n != -1){ + printf("%s: write returned %d, expected -1\n", s, n); + exit(1); + } + + unlink("truncfile"); + close(fd1); + close(fd2); +} + +void +truncate3(char *s) +{ + int pid, xstatus; + + close(open("truncfile", O_CREATE|O_TRUNC|O_WRONLY)); + + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + + if(pid == 0){ + for(int i = 0; i < 100; i++){ + char buf[32]; + int fd = open("truncfile", O_WRONLY); + if(fd < 0){ + printf("%s: open failed\n", s); + exit(1); + } + int n = write(fd, "1234567890", 10); + if(n != 10){ + printf("%s: write got %d, expected 10\n", s, n); + exit(1); + } + close(fd); + fd = open("truncfile", O_RDONLY); + read(fd, buf, sizeof(buf)); + close(fd); + } + exit(0); + } + + for(int i = 0; i < 150; i++){ + int fd = open("truncfile", O_CREATE|O_WRONLY|O_TRUNC); + if(fd < 0){ + printf("%s: open failed\n", s); + exit(1); + } + int n = write(fd, "xxx", 3); + if(n != 3){ + printf("%s: write got %d, expected 3\n", s, n); + exit(1); + } + close(fd); + } + + wait(&xstatus); + unlink("truncfile"); + exit(xstatus); +} + + +// does chdir() call iput(p->cwd) in a transaction? +void +iputtest(char *s) +{ + if(mkdir("iputdir") < 0){ + printf("%s: mkdir failed\n", s); + exit(1); + } + if(chdir("iputdir") < 0){ + printf("%s: chdir iputdir failed\n", s); + exit(1); + } + if(unlink("../iputdir") < 0){ + printf("%s: unlink ../iputdir failed\n", s); + exit(1); + } + if(chdir("/") < 0){ + printf("%s: chdir / failed\n", s); + exit(1); + } +} + +// does exit() call iput(p->cwd) in a transaction? +void +exitiputtest(char *s) +{ + int pid, xstatus; + + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid == 0){ + if(mkdir("iputdir") < 0){ + printf("%s: mkdir failed\n", s); + exit(1); + } + if(chdir("iputdir") < 0){ + printf("%s: child chdir failed\n", s); + exit(1); + } + if(unlink("../iputdir") < 0){ + printf("%s: unlink ../iputdir failed\n", s); + exit(1); + } + exit(0); + } + wait(&xstatus); + exit(xstatus); +} + +// does the error path in open() for attempt to write a +// directory call iput() in a transaction? +// needs a hacked kernel that pauses just after the namei() +// call in sys_open(): +// if((ip = namei(path)) == 0) +// return -1; +// { +// int i; +// for(i = 0; i < 10000; i++) +// yield(); +// } +void +openiputtest(char *s) +{ + int pid, xstatus; + + if(mkdir("oidir") < 0){ + printf("%s: mkdir oidir failed\n", s); + exit(1); + } + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid == 0){ + int fd = open("oidir", O_RDWR); + if(fd >= 0){ + printf("%s: open directory for write succeeded\n", s); + exit(1); + } + exit(0); + } + sleep(1); + if(unlink("oidir") != 0){ + printf("%s: unlink failed\n", s); + exit(1); + } + wait(&xstatus); + exit(xstatus); +} + +// simple file system tests + +void +opentest(char *s) +{ + int fd; + + fd = open("echo", 0); + if(fd < 0){ + printf("%s: open echo failed!\n", s); + exit(1); + } + close(fd); + fd = open("doesnotexist", 0); + if(fd >= 0){ + printf("%s: open doesnotexist succeeded!\n", s); + exit(1); + } +} + +void +writetest(char *s) +{ + int fd; + int i; + enum { N=100, SZ=10 }; + + fd = open("small", O_CREATE|O_RDWR); + if(fd < 0){ + printf("%s: error: creat small failed!\n", s); + exit(1); + } + for(i = 0; i < N; i++){ + if(write(fd, "aaaaaaaaaa", SZ) != SZ){ + printf("%s: error: write aa %d new file failed\n", s, i); + exit(1); + } + if(write(fd, "bbbbbbbbbb", SZ) != SZ){ + printf("%s: error: write bb %d new file failed\n", s, i); + exit(1); + } + } + close(fd); + fd = open("small", O_RDONLY); + if(fd < 0){ + printf("%s: error: open small failed!\n", s); + exit(1); + } + i = read(fd, buf, N*SZ*2); + if(i != N*SZ*2){ + printf("%s: read failed\n", s); + exit(1); + } + close(fd); + + if(unlink("small") < 0){ + printf("%s: unlink small failed\n", s); + exit(1); + } +} + +void +writebig(char *s) +{ + int i, fd, n; + + fd = open("big", O_CREATE|O_RDWR); + if(fd < 0){ + printf("%s: error: creat big failed!\n", s); + exit(1); + } + + for(i = 0; i < MAXFILE; i++){ + ((int*)buf)[0] = i; + if(write(fd, buf, BSIZE) != BSIZE){ + printf("%s: error: write big file failed i=%d\n", s, i); + exit(1); + } + } + + close(fd); + + fd = open("big", O_RDONLY); + if(fd < 0){ + printf("%s: error: open big failed!\n", s); + exit(1); + } + + n = 0; + for(;;){ + i = read(fd, buf, BSIZE); + if(i == 0){ + if(n != MAXFILE){ + printf("%s: read only %d blocks from big", s, n); + exit(1); + } + break; + } else if(i != BSIZE){ + printf("%s: read failed %d\n", s, i); + exit(1); + } + if(((int*)buf)[0] != n){ + printf("%s: read content of block %d is %d\n", s, + n, ((int*)buf)[0]); + exit(1); + } + n++; + } + close(fd); + if(unlink("big") < 0){ + printf("%s: unlink big failed\n", s); + exit(1); + } +} + +// many creates, followed by unlink test +void +createtest(char *s) +{ + int i, fd; + enum { N=52 }; + + char name[3]; + name[0] = 'a'; + name[2] = '\0'; + for(i = 0; i < N; i++){ + name[1] = '0' + i; + fd = open(name, O_CREATE|O_RDWR); + close(fd); + } + name[0] = 'a'; + name[2] = '\0'; + for(i = 0; i < N; i++){ + name[1] = '0' + i; + unlink(name); + } +} + +void dirtest(char *s) +{ + if(mkdir("dir0") < 0){ + printf("%s: mkdir failed\n", s); + exit(1); + } + + if(chdir("dir0") < 0){ + printf("%s: chdir dir0 failed\n", s); + exit(1); + } + + if(chdir("..") < 0){ + printf("%s: chdir .. failed\n", s); + exit(1); + } + + if(unlink("dir0") < 0){ + printf("%s: unlink dir0 failed\n", s); + exit(1); + } +} + +void +exectest(char *s) +{ + int fd, xstatus, pid; + char *echoargv[] = { "echo", "OK", 0 }; + char buf[3]; + + unlink("echo-ok"); + pid = fork(); + if(pid < 0) { + printf("%s: fork failed\n", s); + exit(1); + } + if(pid == 0) { + close(1); + fd = open("echo-ok", O_CREATE|O_WRONLY); + if(fd < 0) { + printf("%s: create failed\n", s); + exit(1); + } + if(fd != 1) { + printf("%s: wrong fd\n", s); + exit(1); + } + if(exec("echo", echoargv) < 0){ + printf("%s: exec echo failed\n", s); + exit(1); + } + // won't get to here + } + if (wait(&xstatus) != pid) { + printf("%s: wait failed!\n", s); + } + if(xstatus != 0) + exit(xstatus); + + fd = open("echo-ok", O_RDONLY); + if(fd < 0) { + printf("%s: open failed\n", s); + exit(1); + } + if (read(fd, buf, 2) != 2) { + printf("%s: read failed\n", s); + exit(1); + } + unlink("echo-ok"); + if(buf[0] == 'O' && buf[1] == 'K') + exit(0); + else { + printf("%s: wrong output\n", s); + exit(1); + } + +} + +// simple fork and pipe read/write + +void +pipe1(char *s) +{ + int fds[2], pid, xstatus; + int seq, i, n, cc, total; + enum { N=5, SZ=1033 }; + + if(pipe(fds) != 0){ + printf("%s: pipe() failed\n", s); + exit(1); + } + pid = fork(); + seq = 0; + if(pid == 0){ + close(fds[0]); + for(n = 0; n < N; n++){ + for(i = 0; i < SZ; i++) + buf[i] = seq++; + if(write(fds[1], buf, SZ) != SZ){ + printf("%s: pipe1 oops 1\n", s); + exit(1); + } + } + exit(0); + } else if(pid > 0){ + close(fds[1]); + total = 0; + cc = 1; + while((n = read(fds[0], buf, cc)) > 0){ + for(i = 0; i < n; i++){ + if((buf[i] & 0xff) != (seq++ & 0xff)){ + printf("%s: pipe1 oops 2\n", s); + return; + } + } + total += n; + cc = cc * 2; + if(cc > sizeof(buf)) + cc = sizeof(buf); + } + if(total != N * SZ){ + printf("%s: pipe1 oops 3 total %d\n", s, total); + exit(1); + } + close(fds[0]); + wait(&xstatus); + exit(xstatus); + } else { + printf("%s: fork() failed\n", s); + exit(1); + } +} + + +// test if child is killed (status = -1) +void +killstatus(char *s) +{ + int xst; + + for(int i = 0; i < 100; i++){ + int pid1 = fork(); + if(pid1 < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid1 == 0){ + while(1) { + getpid(); + } + exit(0); + } + sleep(1); + kill(pid1); + wait(&xst); + if(xst != -1) { + printf("%s: status should be -1\n", s); + exit(1); + } + } + exit(0); +} + +// meant to be run w/ at most two CPUs +void +preempt(char *s) +{ + int pid1, pid2, pid3; + int pfds[2]; + + pid1 = fork(); + if(pid1 < 0) { + printf("%s: fork failed", s); + exit(1); + } + if(pid1 == 0) + for(;;) + ; + + pid2 = fork(); + if(pid2 < 0) { + printf("%s: fork failed\n", s); + exit(1); + } + if(pid2 == 0) + for(;;) + ; + + pipe(pfds); + pid3 = fork(); + if(pid3 < 0) { + printf("%s: fork failed\n", s); + exit(1); + } + if(pid3 == 0){ + close(pfds[0]); + if(write(pfds[1], "x", 1) != 1) + printf("%s: preempt write error", s); + close(pfds[1]); + for(;;) + ; + } + + close(pfds[1]); + if(read(pfds[0], buf, sizeof(buf)) != 1){ + printf("%s: preempt read error", s); + return; + } + close(pfds[0]); + printf("kill... "); + kill(pid1); + kill(pid2); + kill(pid3); + printf("wait... "); + wait(0); + wait(0); + wait(0); +} + +// try to find any races between exit and wait +void +exitwait(char *s) +{ + int i, pid; + + for(i = 0; i < 100; i++){ + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid){ + int xstate; + if(wait(&xstate) != pid){ + printf("%s: wait wrong pid\n", s); + exit(1); + } + if(i != xstate) { + printf("%s: wait wrong exit status\n", s); + exit(1); + } + } else { + exit(i); + } + } +} + +// try to find races in the reparenting +// code that handles a parent exiting +// when it still has live children. +void +reparent(char *s) +{ + int master_pid = getpid(); + for(int i = 0; i < 200; i++){ + int pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid){ + if(wait(0) != pid){ + printf("%s: wait wrong pid\n", s); + exit(1); + } + } else { + int pid2 = fork(); + if(pid2 < 0){ + kill(master_pid); + exit(1); + } + exit(0); + } + } + exit(0); +} + +// what if two children exit() at the same time? +void +twochildren(char *s) +{ + for(int i = 0; i < 1000; i++){ + int pid1 = fork(); + if(pid1 < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid1 == 0){ + exit(0); + } else { + int pid2 = fork(); + if(pid2 < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid2 == 0){ + exit(0); + } else { + wait(0); + wait(0); + } + } + } +} + +// concurrent forks to try to expose locking bugs. +void +forkfork(char *s) +{ + enum { N=2 }; + + for(int i = 0; i < N; i++){ + int pid = fork(); + if(pid < 0){ + printf("%s: fork failed", s); + exit(1); + } + if(pid == 0){ + for(int j = 0; j < 200; j++){ + int pid1 = fork(); + if(pid1 < 0){ + exit(1); + } + if(pid1 == 0){ + exit(0); + } + wait(0); + } + exit(0); + } + } + + int xstatus; + for(int i = 0; i < N; i++){ + wait(&xstatus); + if(xstatus != 0) { + printf("%s: fork in child failed", s); + exit(1); + } + } +} + +void +forkforkfork(char *s) +{ + unlink("stopforking"); + + int pid = fork(); + if(pid < 0){ + printf("%s: fork failed", s); + exit(1); + } + if(pid == 0){ + while(1){ + int fd = open("stopforking", 0); + if(fd >= 0){ + exit(0); + } + if(fork() < 0){ + close(open("stopforking", O_CREATE|O_RDWR)); + } + } + + exit(0); + } + + sleep(20); // two seconds + close(open("stopforking", O_CREATE|O_RDWR)); + wait(0); + sleep(10); // one second +} + +// regression test. does reparent() violate the parent-then-child +// locking order when giving away a child to init, so that exit() +// deadlocks against init's wait()? also used to trigger a "panic: +// release" due to exit() releasing a different p->parent->lock than +// it acquired. +void +reparent2(char *s) +{ + for(int i = 0; i < 800; i++){ + int pid1 = fork(); + if(pid1 < 0){ + printf("fork failed\n"); + exit(1); + } + if(pid1 == 0){ + fork(); + fork(); + exit(0); + } + wait(0); + } + + exit(0); +} + +// allocate all mem, free it, and allocate again +void +mem(char *s) +{ + void *m1, *m2; + int pid; + + if((pid = fork()) == 0){ + m1 = 0; + while((m2 = malloc(10001)) != 0){ + *(char**)m2 = m1; + m1 = m2; + } + while(m1){ + m2 = *(char**)m1; + free(m1); + m1 = m2; + } + m1 = malloc(1024*20); + if(m1 == 0){ + printf("%s: couldn't allocate mem?!!\n", s); + exit(1); + } + free(m1); + exit(0); + } else { + int xstatus; + wait(&xstatus); + if(xstatus == -1){ + // probably page fault, so might be lazy lab, + // so OK. + exit(0); + } + exit(xstatus); + } +} + +// More file system tests + +// two processes write to the same file descriptor +// is the offset shared? does inode locking work? +void +sharedfd(char *s) +{ + int fd, pid, i, n, nc, np; + enum { N = 1000, SZ=10}; + char buf[SZ]; + + unlink("sharedfd"); + fd = open("sharedfd", O_CREATE|O_RDWR); + if(fd < 0){ + printf("%s: cannot open sharedfd for writing", s); + exit(1); + } + pid = fork(); + memset(buf, pid==0?'c':'p', sizeof(buf)); + for(i = 0; i < N; i++){ + if(write(fd, buf, sizeof(buf)) != sizeof(buf)){ + printf("%s: write sharedfd failed\n", s); + exit(1); + } + } + if(pid == 0) { + exit(0); + } else { + int xstatus; + wait(&xstatus); + if(xstatus != 0) + exit(xstatus); + } + + close(fd); + fd = open("sharedfd", 0); + if(fd < 0){ + printf("%s: cannot open sharedfd for reading\n", s); + exit(1); + } + nc = np = 0; + while((n = read(fd, buf, sizeof(buf))) > 0){ + for(i = 0; i < sizeof(buf); i++){ + if(buf[i] == 'c') + nc++; + if(buf[i] == 'p') + np++; + } + } + close(fd); + unlink("sharedfd"); + if(nc == N*SZ && np == N*SZ){ + exit(0); + } else { + printf("%s: nc/np test fails\n", s); + exit(1); + } +} + +// four processes write different files at the same +// time, to test block allocation. +void +fourfiles(char *s) +{ + int fd, pid, i, j, n, total, pi; + char *names[] = { "f0", "f1", "f2", "f3" }; + char *fname; + enum { N=12, NCHILD=4, SZ=500 }; + + for(pi = 0; pi < NCHILD; pi++){ + fname = names[pi]; + unlink(fname); + + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + + if(pid == 0){ + fd = open(fname, O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: create failed\n", s); + exit(1); + } + + memset(buf, '0'+pi, SZ); + for(i = 0; i < N; i++){ + if((n = write(fd, buf, SZ)) != SZ){ + printf("write failed %d\n", n); + exit(1); + } + } + exit(0); + } + } + + int xstatus; + for(pi = 0; pi < NCHILD; pi++){ + wait(&xstatus); + if(xstatus != 0) + exit(xstatus); + } + + for(i = 0; i < NCHILD; i++){ + fname = names[i]; + fd = open(fname, 0); + total = 0; + while((n = read(fd, buf, sizeof(buf))) > 0){ + for(j = 0; j < n; j++){ + if(buf[j] != '0'+i){ + printf("%s: wrong char\n", s); + exit(1); + } + } + total += n; + } + close(fd); + if(total != N*SZ){ + printf("wrong length %d\n", total); + exit(1); + } + unlink(fname); + } +} + +// four processes create and delete different files in same directory +void +createdelete(char *s) +{ + enum { N = 20, NCHILD=4 }; + int pid, i, fd, pi; + char name[32]; + + for(pi = 0; pi < NCHILD; pi++){ + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + + if(pid == 0){ + name[0] = 'p' + pi; + name[2] = '\0'; + for(i = 0; i < N; i++){ + name[1] = '0' + i; + fd = open(name, O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: create failed\n", s); + exit(1); + } + close(fd); + if(i > 0 && (i % 2 ) == 0){ + name[1] = '0' + (i / 2); + if(unlink(name) < 0){ + printf("%s: unlink failed\n", s); + exit(1); + } + } + } + exit(0); + } + } + + int xstatus; + for(pi = 0; pi < NCHILD; pi++){ + wait(&xstatus); + if(xstatus != 0) + exit(1); + } + + name[0] = name[1] = name[2] = 0; + for(i = 0; i < N; i++){ + for(pi = 0; pi < NCHILD; pi++){ + name[0] = 'p' + pi; + name[1] = '0' + i; + fd = open(name, 0); + if((i == 0 || i >= N/2) && fd < 0){ + printf("%s: oops createdelete %s didn't exist\n", s, name); + exit(1); + } else if((i >= 1 && i < N/2) && fd >= 0){ + printf("%s: oops createdelete %s did exist\n", s, name); + exit(1); + } + if(fd >= 0) + close(fd); + } + } + + for(i = 0; i < N; i++){ + for(pi = 0; pi < NCHILD; pi++){ + name[0] = 'p' + pi; + name[1] = '0' + i; + unlink(name); + } + } +} + +// can I unlink a file and still read it? +void +unlinkread(char *s) +{ + enum { SZ = 5 }; + int fd, fd1; + + fd = open("unlinkread", O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: create unlinkread failed\n", s); + exit(1); + } + write(fd, "hello", SZ); + close(fd); + + fd = open("unlinkread", O_RDWR); + if(fd < 0){ + printf("%s: open unlinkread failed\n", s); + exit(1); + } + if(unlink("unlinkread") != 0){ + printf("%s: unlink unlinkread failed\n", s); + exit(1); + } + + fd1 = open("unlinkread", O_CREATE | O_RDWR); + write(fd1, "yyy", 3); + close(fd1); + + if(read(fd, buf, sizeof(buf)) != SZ){ + printf("%s: unlinkread read failed", s); + exit(1); + } + if(buf[0] != 'h'){ + printf("%s: unlinkread wrong data\n", s); + exit(1); + } + if(write(fd, buf, 10) != 10){ + printf("%s: unlinkread write failed\n", s); + exit(1); + } + close(fd); + unlink("unlinkread"); +} + +void +linktest(char *s) +{ + enum { SZ = 5 }; + int fd; + + unlink("lf1"); + unlink("lf2"); + + fd = open("lf1", O_CREATE|O_RDWR); + if(fd < 0){ + printf("%s: create lf1 failed\n", s); + exit(1); + } + if(write(fd, "hello", SZ) != SZ){ + printf("%s: write lf1 failed\n", s); + exit(1); + } + close(fd); + + if(link("lf1", "lf2") < 0){ + printf("%s: link lf1 lf2 failed\n", s); + exit(1); + } + unlink("lf1"); + + if(open("lf1", 0) >= 0){ + printf("%s: unlinked lf1 but it is still there!\n", s); + exit(1); + } + + fd = open("lf2", 0); + if(fd < 0){ + printf("%s: open lf2 failed\n", s); + exit(1); + } + if(read(fd, buf, sizeof(buf)) != SZ){ + printf("%s: read lf2 failed\n", s); + exit(1); + } + close(fd); + + if(link("lf2", "lf2") >= 0){ + printf("%s: link lf2 lf2 succeeded! oops\n", s); + exit(1); + } + + unlink("lf2"); + if(link("lf2", "lf1") >= 0){ + printf("%s: link non-existent succeeded! oops\n", s); + exit(1); + } + + if(link(".", "lf1") >= 0){ + printf("%s: link . lf1 succeeded! oops\n", s); + exit(1); + } +} + +// test concurrent create/link/unlink of the same file +void +concreate(char *s) +{ + enum { N = 40 }; + char file[3]; + int i, pid, n, fd; + char fa[N]; + struct { + ushort inum; + char name[DIRSIZ]; + } de; + + file[0] = 'C'; + file[2] = '\0'; + for(i = 0; i < N; i++){ + file[1] = '0' + i; + unlink(file); + pid = fork(); + if(pid && (i % 3) == 1){ + link("C0", file); + } else if(pid == 0 && (i % 5) == 1){ + link("C0", file); + } else { + fd = open(file, O_CREATE | O_RDWR); + if(fd < 0){ + printf("concreate create %s failed\n", file); + exit(1); + } + close(fd); + } + if(pid == 0) { + exit(0); + } else { + int xstatus; + wait(&xstatus); + if(xstatus != 0) + exit(1); + } + } + + memset(fa, 0, sizeof(fa)); + fd = open(".", 0); + n = 0; + while(read(fd, &de, sizeof(de)) > 0){ + if(de.inum == 0) + continue; + if(de.name[0] == 'C' && de.name[2] == '\0'){ + i = de.name[1] - '0'; + if(i < 0 || i >= sizeof(fa)){ + printf("%s: concreate weird file %s\n", s, de.name); + exit(1); + } + if(fa[i]){ + printf("%s: concreate duplicate file %s\n", s, de.name); + exit(1); + } + fa[i] = 1; + n++; + } + } + close(fd); + + if(n != N){ + printf("%s: concreate not enough files in directory listing\n", s); + exit(1); + } + + for(i = 0; i < N; i++){ + file[1] = '0' + i; + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(((i % 3) == 0 && pid == 0) || + ((i % 3) == 1 && pid != 0)){ + close(open(file, 0)); + close(open(file, 0)); + close(open(file, 0)); + close(open(file, 0)); + close(open(file, 0)); + close(open(file, 0)); + } else { + unlink(file); + unlink(file); + unlink(file); + unlink(file); + unlink(file); + unlink(file); + } + if(pid == 0) + exit(0); + else + wait(0); + } +} + +// another concurrent link/unlink/create test, +// to look for deadlocks. +void +linkunlink(char *s) +{ + int pid, i; + + unlink("x"); + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + + unsigned int x = (pid ? 1 : 97); + for(i = 0; i < 100; i++){ + x = x * 1103515245 + 12345; + if((x % 3) == 0){ + close(open("x", O_RDWR | O_CREATE)); + } else if((x % 3) == 1){ + link("cat", "x"); + } else { + unlink("x"); + } + } + + if(pid) + wait(0); + else + exit(0); +} + + +void +subdir(char *s) +{ + int fd, cc; + + unlink("ff"); + if(mkdir("dd") != 0){ + printf("%s: mkdir dd failed\n", s); + exit(1); + } + + fd = open("dd/ff", O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: create dd/ff failed\n", s); + exit(1); + } + write(fd, "ff", 2); + close(fd); + + if(unlink("dd") >= 0){ + printf("%s: unlink dd (non-empty dir) succeeded!\n", s); + exit(1); + } + + if(mkdir("/dd/dd") != 0){ + printf("%s: subdir mkdir dd/dd failed\n", s); + exit(1); + } + + fd = open("dd/dd/ff", O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: create dd/dd/ff failed\n", s); + exit(1); + } + write(fd, "FF", 2); + close(fd); + + fd = open("dd/dd/../ff", 0); + if(fd < 0){ + printf("%s: open dd/dd/../ff failed\n", s); + exit(1); + } + cc = read(fd, buf, sizeof(buf)); + if(cc != 2 || buf[0] != 'f'){ + printf("%s: dd/dd/../ff wrong content\n", s); + exit(1); + } + close(fd); + + if(link("dd/dd/ff", "dd/dd/ffff") != 0){ + printf("%s: link dd/dd/ff dd/dd/ffff failed\n", s); + exit(1); + } + + if(unlink("dd/dd/ff") != 0){ + printf("%s: unlink dd/dd/ff failed\n", s); + exit(1); + } + if(open("dd/dd/ff", O_RDONLY) >= 0){ + printf("%s: open (unlinked) dd/dd/ff succeeded\n", s); + exit(1); + } + + if(chdir("dd") != 0){ + printf("%s: chdir dd failed\n", s); + exit(1); + } + if(chdir("dd/../../dd") != 0){ + printf("%s: chdir dd/../../dd failed\n", s); + exit(1); + } + if(chdir("dd/../../../dd") != 0){ + printf("%s: chdir dd/../../../dd failed\n", s); + exit(1); + } + if(chdir("./..") != 0){ + printf("%s: chdir ./.. failed\n", s); + exit(1); + } + + fd = open("dd/dd/ffff", 0); + if(fd < 0){ + printf("%s: open dd/dd/ffff failed\n", s); + exit(1); + } + if(read(fd, buf, sizeof(buf)) != 2){ + printf("%s: read dd/dd/ffff wrong len\n", s); + exit(1); + } + close(fd); + + if(open("dd/dd/ff", O_RDONLY) >= 0){ + printf("%s: open (unlinked) dd/dd/ff succeeded!\n", s); + exit(1); + } + + if(open("dd/ff/ff", O_CREATE|O_RDWR) >= 0){ + printf("%s: create dd/ff/ff succeeded!\n", s); + exit(1); + } + if(open("dd/xx/ff", O_CREATE|O_RDWR) >= 0){ + printf("%s: create dd/xx/ff succeeded!\n", s); + exit(1); + } + if(open("dd", O_CREATE) >= 0){ + printf("%s: create dd succeeded!\n", s); + exit(1); + } + if(open("dd", O_RDWR) >= 0){ + printf("%s: open dd rdwr succeeded!\n", s); + exit(1); + } + if(open("dd", O_WRONLY) >= 0){ + printf("%s: open dd wronly succeeded!\n", s); + exit(1); + } + if(link("dd/ff/ff", "dd/dd/xx") == 0){ + printf("%s: link dd/ff/ff dd/dd/xx succeeded!\n", s); + exit(1); + } + if(link("dd/xx/ff", "dd/dd/xx") == 0){ + printf("%s: link dd/xx/ff dd/dd/xx succeeded!\n", s); + exit(1); + } + if(link("dd/ff", "dd/dd/ffff") == 0){ + printf("%s: link dd/ff dd/dd/ffff succeeded!\n", s); + exit(1); + } + if(mkdir("dd/ff/ff") == 0){ + printf("%s: mkdir dd/ff/ff succeeded!\n", s); + exit(1); + } + if(mkdir("dd/xx/ff") == 0){ + printf("%s: mkdir dd/xx/ff succeeded!\n", s); + exit(1); + } + if(mkdir("dd/dd/ffff") == 0){ + printf("%s: mkdir dd/dd/ffff succeeded!\n", s); + exit(1); + } + if(unlink("dd/xx/ff") == 0){ + printf("%s: unlink dd/xx/ff succeeded!\n", s); + exit(1); + } + if(unlink("dd/ff/ff") == 0){ + printf("%s: unlink dd/ff/ff succeeded!\n", s); + exit(1); + } + if(chdir("dd/ff") == 0){ + printf("%s: chdir dd/ff succeeded!\n", s); + exit(1); + } + if(chdir("dd/xx") == 0){ + printf("%s: chdir dd/xx succeeded!\n", s); + exit(1); + } + + if(unlink("dd/dd/ffff") != 0){ + printf("%s: unlink dd/dd/ff failed\n", s); + exit(1); + } + if(unlink("dd/ff") != 0){ + printf("%s: unlink dd/ff failed\n", s); + exit(1); + } + if(unlink("dd") == 0){ + printf("%s: unlink non-empty dd succeeded!\n", s); + exit(1); + } + if(unlink("dd/dd") < 0){ + printf("%s: unlink dd/dd failed\n", s); + exit(1); + } + if(unlink("dd") < 0){ + printf("%s: unlink dd failed\n", s); + exit(1); + } +} + +// test writes that are larger than the log. +void +bigwrite(char *s) +{ + int fd, sz; + + unlink("bigwrite"); + for(sz = 499; sz < (MAXOPBLOCKS+2)*BSIZE; sz += 471){ + fd = open("bigwrite", O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: cannot create bigwrite\n", s); + exit(1); + } + int i; + for(i = 0; i < 2; i++){ + int cc = write(fd, buf, sz); + if(cc != sz){ + printf("%s: write(%d) ret %d\n", s, sz, cc); + exit(1); + } + } + close(fd); + unlink("bigwrite"); + } +} + + +void +bigfile(char *s) +{ + enum { N = 20, SZ=600 }; + int fd, i, total, cc; + + unlink("bigfile.dat"); + fd = open("bigfile.dat", O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: cannot create bigfile", s); + exit(1); + } + for(i = 0; i < N; i++){ + memset(buf, i, SZ); + if(write(fd, buf, SZ) != SZ){ + printf("%s: write bigfile failed\n", s); + exit(1); + } + } + close(fd); + + fd = open("bigfile.dat", 0); + if(fd < 0){ + printf("%s: cannot open bigfile\n", s); + exit(1); + } + total = 0; + for(i = 0; ; i++){ + cc = read(fd, buf, SZ/2); + if(cc < 0){ + printf("%s: read bigfile failed\n", s); + exit(1); + } + if(cc == 0) + break; + if(cc != SZ/2){ + printf("%s: short read bigfile\n", s); + exit(1); + } + if(buf[0] != i/2 || buf[SZ/2-1] != i/2){ + printf("%s: read bigfile wrong data\n", s); + exit(1); + } + total += cc; + } + close(fd); + if(total != N*SZ){ + printf("%s: read bigfile wrong total\n", s); + exit(1); + } + unlink("bigfile.dat"); +} + +void +fourteen(char *s) +{ + int fd; + + // DIRSIZ is 14. + + if(mkdir("12345678901234") != 0){ + printf("%s: mkdir 12345678901234 failed\n", s); + exit(1); + } + if(mkdir("12345678901234/123456789012345") != 0){ + printf("%s: mkdir 12345678901234/123456789012345 failed\n", s); + exit(1); + } + fd = open("123456789012345/123456789012345/123456789012345", O_CREATE); + if(fd < 0){ + printf("%s: create 123456789012345/123456789012345/123456789012345 failed\n", s); + exit(1); + } + close(fd); + fd = open("12345678901234/12345678901234/12345678901234", 0); + if(fd < 0){ + printf("%s: open 12345678901234/12345678901234/12345678901234 failed\n", s); + exit(1); + } + close(fd); + + if(mkdir("12345678901234/12345678901234") == 0){ + printf("%s: mkdir 12345678901234/12345678901234 succeeded!\n", s); + exit(1); + } + if(mkdir("123456789012345/12345678901234") == 0){ + printf("%s: mkdir 12345678901234/123456789012345 succeeded!\n", s); + exit(1); + } + + // clean up + unlink("123456789012345/12345678901234"); + unlink("12345678901234/12345678901234"); + unlink("12345678901234/12345678901234/12345678901234"); + unlink("123456789012345/123456789012345/123456789012345"); + unlink("12345678901234/123456789012345"); + unlink("12345678901234"); +} + +void +rmdot(char *s) +{ + if(mkdir("dots") != 0){ + printf("%s: mkdir dots failed\n", s); + exit(1); + } + if(chdir("dots") != 0){ + printf("%s: chdir dots failed\n", s); + exit(1); + } + if(unlink(".") == 0){ + printf("%s: rm . worked!\n", s); + exit(1); + } + if(unlink("..") == 0){ + printf("%s: rm .. worked!\n", s); + exit(1); + } + if(chdir("/") != 0){ + printf("%s: chdir / failed\n", s); + exit(1); + } + if(unlink("dots/.") == 0){ + printf("%s: unlink dots/. worked!\n", s); + exit(1); + } + if(unlink("dots/..") == 0){ + printf("%s: unlink dots/.. worked!\n", s); + exit(1); + } + if(unlink("dots") != 0){ + printf("%s: unlink dots failed!\n", s); + exit(1); + } +} + +void +dirfile(char *s) +{ + int fd; + + fd = open("dirfile", O_CREATE); + if(fd < 0){ + printf("%s: create dirfile failed\n", s); + exit(1); + } + close(fd); + if(chdir("dirfile") == 0){ + printf("%s: chdir dirfile succeeded!\n", s); + exit(1); + } + fd = open("dirfile/xx", 0); + if(fd >= 0){ + printf("%s: create dirfile/xx succeeded!\n", s); + exit(1); + } + fd = open("dirfile/xx", O_CREATE); + if(fd >= 0){ + printf("%s: create dirfile/xx succeeded!\n", s); + exit(1); + } + if(mkdir("dirfile/xx") == 0){ + printf("%s: mkdir dirfile/xx succeeded!\n", s); + exit(1); + } + if(unlink("dirfile/xx") == 0){ + printf("%s: unlink dirfile/xx succeeded!\n", s); + exit(1); + } + if(link("README", "dirfile/xx") == 0){ + printf("%s: link to dirfile/xx succeeded!\n", s); + exit(1); + } + if(unlink("dirfile") != 0){ + printf("%s: unlink dirfile failed!\n", s); + exit(1); + } + + fd = open(".", O_RDWR); + if(fd >= 0){ + printf("%s: open . for writing succeeded!\n", s); + exit(1); + } + fd = open(".", 0); + if(write(fd, "x", 1) > 0){ + printf("%s: write . succeeded!\n", s); + exit(1); + } + close(fd); +} + +// test that iput() is called at the end of _namei(). +// also tests empty file names. +void +iref(char *s) +{ + int i, fd; + + for(i = 0; i < NINODE + 1; i++){ + if(mkdir("irefd") != 0){ + printf("%s: mkdir irefd failed\n", s); + exit(1); + } + if(chdir("irefd") != 0){ + printf("%s: chdir irefd failed\n", s); + exit(1); + } + + mkdir(""); + link("README", ""); + fd = open("", O_CREATE); + if(fd >= 0) + close(fd); + fd = open("xx", O_CREATE); + if(fd >= 0) + close(fd); + unlink("xx"); + } + + // clean up + for(i = 0; i < NINODE + 1; i++){ + chdir(".."); + unlink("irefd"); + } + + chdir("/"); +} + +// test that fork fails gracefully +// the forktest binary also does this, but it runs out of proc entries first. +// inside the bigger usertests binary, we run out of memory first. +void +forktest(char *s) +{ + enum{ N = 1000 }; + int n, pid; + + for(n=0; n<N; n++){ + pid = fork(); + if(pid < 0) + break; + if(pid == 0) + exit(0); + } + + if (n == 0) { + printf("%s: no fork at all!\n", s); + exit(1); + } + + if(n == N){ + printf("%s: fork claimed to work 1000 times!\n", s); + exit(1); + } + + for(; n > 0; n--){ + if(wait(0) < 0){ + printf("%s: wait stopped early\n", s); + exit(1); + } + } + + if(wait(0) != -1){ + printf("%s: wait got too many\n", s); + exit(1); + } +} + +void +sbrkbasic(char *s) +{ + enum { TOOMUCH=1024*1024*1024}; + int i, pid, xstatus; + char *c, *a, *b; + + // does sbrk() return the expected failure value? + pid = fork(); + if(pid < 0){ + printf("fork failed in sbrkbasic\n"); + exit(1); + } + if(pid == 0){ + a = sbrk(TOOMUCH); + if(a == (char*)0xffffffffffffffffL){ + // it's OK if this fails. + exit(0); + } + + for(b = a; b < a+TOOMUCH; b += 4096){ + *b = 99; + } + + // we should not get here! either sbrk(TOOMUCH) + // should have failed, or (with lazy allocation) + // a pagefault should have killed this process. + exit(1); + } + + wait(&xstatus); + if(xstatus == 1){ + printf("%s: too much memory allocated!\n", s); + exit(1); + } + + // can one sbrk() less than a page? + a = sbrk(0); + for(i = 0; i < 5000; i++){ + b = sbrk(1); + if(b != a){ + printf("%s: sbrk test failed %d %p %p\n", s, i, a, b); + exit(1); + } + *b = 1; + a = b + 1; + } + pid = fork(); + if(pid < 0){ + printf("%s: sbrk test fork failed\n", s); + exit(1); + } + c = sbrk(1); + c = sbrk(1); + if(c != a + 1){ + printf("%s: sbrk test failed post-fork\n", s); + exit(1); + } + if(pid == 0) + exit(0); + wait(&xstatus); + exit(xstatus); +} + +void +sbrkmuch(char *s) +{ + enum { BIG=100*1024*1024 }; + char *c, *oldbrk, *a, *lastaddr, *p; + uint64 amt; + + oldbrk = sbrk(0); + + // can one grow address space to something big? + a = sbrk(0); + amt = BIG - (uint64)a; + p = sbrk(amt); + if (p != a) { + printf("%s: sbrk test failed to grow big address space; enough phys mem?\n", s); + exit(1); + } + + // touch each page to make sure it exists. + char *eee = sbrk(0); + for(char *pp = a; pp < eee; pp += 4096) + *pp = 1; + + lastaddr = (char*) (BIG-1); + *lastaddr = 99; + + // can one de-allocate? + a = sbrk(0); + c = sbrk(-PGSIZE); + if(c == (char*)0xffffffffffffffffL){ + printf("%s: sbrk could not deallocate\n", s); + exit(1); + } + c = sbrk(0); + if(c != a - PGSIZE){ + printf("%s: sbrk deallocation produced wrong address, a %p c %p\n", s, a, c); + exit(1); + } + + // can one re-allocate that page? + a = sbrk(0); + c = sbrk(PGSIZE); + if(c != a || sbrk(0) != a + PGSIZE){ + printf("%s: sbrk re-allocation failed, a %p c %p\n", s, a, c); + exit(1); + } + if(*lastaddr == 99){ + // should be zero + printf("%s: sbrk de-allocation didn't really deallocate\n", s); + exit(1); + } + + a = sbrk(0); + c = sbrk(-(sbrk(0) - oldbrk)); + if(c != a){ + printf("%s: sbrk downsize failed, a %p c %p\n", s, a, c); + exit(1); + } +} + +// can we read the kernel's memory? +void +kernmem(char *s) +{ + char *a; + int pid; + + for(a = (char*)(KERNBASE); a < (char*) (KERNBASE+2000000); a += 50000){ + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid == 0){ + printf("%s: oops could read %p = %x\n", s, a, *a); + exit(1); + } + int xstatus; + wait(&xstatus); + if(xstatus != -1) // did kernel kill child? + exit(1); + } +} + +// user code should not be able to write to addresses above MAXVA. +void +MAXVAplus(char *s) +{ + volatile uint64 a = MAXVA; + for( ; a != 0; a <<= 1){ + int pid; + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid == 0){ + *(char*)a = 99; + printf("%s: oops wrote %p\n", s, (void*)a); + exit(1); + } + int xstatus; + wait(&xstatus); + if(xstatus != -1) // did kernel kill child? + exit(1); + } +} + +// if we run the system out of memory, does it clean up the last +// failed allocation? +void +sbrkfail(char *s) +{ + enum { BIG=100*1024*1024 }; + int i, xstatus; + int fds[2]; + char scratch; + char *c, *a; + int pids[10]; + int pid; + + if(pipe(fds) != 0){ + printf("%s: pipe() failed\n", s); + exit(1); + } + for(i = 0; i < sizeof(pids)/sizeof(pids[0]); i++){ + if((pids[i] = fork()) == 0){ + // allocate a lot of memory + sbrk(BIG - (uint64)sbrk(0)); + write(fds[1], "x", 1); + // sit around until killed + for(;;) sleep(1000); + } + if(pids[i] != -1) + read(fds[0], &scratch, 1); + } + + // if those failed allocations freed up the pages they did allocate, + // we'll be able to allocate here + c = sbrk(PGSIZE); + for(i = 0; i < sizeof(pids)/sizeof(pids[0]); i++){ + if(pids[i] == -1) + continue; + kill(pids[i]); + wait(0); + } + if(c == (char*)0xffffffffffffffffL){ + printf("%s: failed sbrk leaked memory\n", s); + exit(1); + } + + // test running fork with the above allocated page + pid = fork(); + if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + if(pid == 0){ + // allocate a lot of memory. + // this should produce a page fault, + // and thus not complete. + a = sbrk(0); + sbrk(10*BIG); + int n = 0; + for (i = 0; i < 10*BIG; i += PGSIZE) { + n += *(a+i); + } + // print n so the compiler doesn't optimize away + // the for loop. + printf("%s: allocate a lot of memory succeeded %d\n", s, n); + exit(1); + } + wait(&xstatus); + if(xstatus != -1 && xstatus != 2) + exit(1); +} + + +// test reads/writes from/to allocated memory +void +sbrkarg(char *s) +{ + char *a; + int fd, n; + + a = sbrk(PGSIZE); + fd = open("sbrk", O_CREATE|O_WRONLY); + unlink("sbrk"); + if(fd < 0) { + printf("%s: open sbrk failed\n", s); + exit(1); + } + if ((n = write(fd, a, PGSIZE)) < 0) { + printf("%s: write sbrk failed\n", s); + exit(1); + } + close(fd); + + // test writes to allocated memory + a = sbrk(PGSIZE); + if(pipe((int *) a) != 0){ + printf("%s: pipe() failed\n", s); + exit(1); + } +} + +void +validatetest(char *s) +{ + int hi; + uint64 p; + + hi = 1100*1024; + for(p = 0; p <= (uint)hi; p += PGSIZE){ + // try to crash the kernel by passing in a bad string pointer + if(link("nosuchfile", (char*)p) != -1){ + printf("%s: link should not succeed\n", s); + exit(1); + } + } +} + +// does uninitialized data start out zero? +char uninit[10000]; +void +bsstest(char *s) +{ + int i; + + for(i = 0; i < sizeof(uninit); i++){ + if(uninit[i] != '\0'){ + printf("%s: bss test failed\n", s); + exit(1); + } + } +} + +// does exec return an error if the arguments +// are larger than a page? or does it write +// below the stack and wreck the instructions/data? +void +bigargtest(char *s) +{ + int pid, fd, xstatus; + + unlink("bigarg-ok"); + pid = fork(); + if(pid == 0){ + static char *args[MAXARG]; + int i; + char big[400]; + memset(big, ' ', sizeof(big)); + big[sizeof(big)-1] = '\0'; + for(i = 0; i < MAXARG-1; i++) + args[i] = big; + args[MAXARG-1] = 0; + // this exec() should fail (and return) because the + // arguments are too large. + exec("echo", args); + fd = open("bigarg-ok", O_CREATE); + close(fd); + exit(0); + } else if(pid < 0){ + printf("%s: bigargtest: fork failed\n", s); + exit(1); + } + + wait(&xstatus); + if(xstatus != 0) + exit(xstatus); + fd = open("bigarg-ok", 0); + if(fd < 0){ + printf("%s: bigarg test failed!\n", s); + exit(1); + } + close(fd); +} + +// what happens when the file system runs out of blocks? +// answer: balloc panics, so this test is not useful. +void +fsfull() +{ + int nfiles; + int fsblocks = 0; + + printf("fsfull test\n"); + + for(nfiles = 0; ; nfiles++){ + char name[64]; + name[0] = 'f'; + name[1] = '0' + nfiles / 1000; + name[2] = '0' + (nfiles % 1000) / 100; + name[3] = '0' + (nfiles % 100) / 10; + name[4] = '0' + (nfiles % 10); + name[5] = '\0'; + printf("writing %s\n", name); + int fd = open(name, O_CREATE|O_RDWR); + if(fd < 0){ + printf("open %s failed\n", name); + break; + } + int total = 0; + while(1){ + int cc = write(fd, buf, BSIZE); + if(cc < BSIZE) + break; + total += cc; + fsblocks++; + } + printf("wrote %d bytes\n", total); + close(fd); + if(total == 0) + break; + } + + while(nfiles >= 0){ + char name[64]; + name[0] = 'f'; + name[1] = '0' + nfiles / 1000; + name[2] = '0' + (nfiles % 1000) / 100; + name[3] = '0' + (nfiles % 100) / 10; + name[4] = '0' + (nfiles % 10); + name[5] = '\0'; + unlink(name); + nfiles--; + } + + printf("fsfull test finished\n"); +} + +void argptest(char *s) +{ + int fd; + fd = open("init", O_RDONLY); + if (fd < 0) { + printf("%s: open failed\n", s); + exit(1); + } + read(fd, sbrk(0) - 1, -1); + close(fd); +} + +// check that there's an invalid page beneath +// the user stack, to catch stack overflow. +void +stacktest(char *s) +{ + int pid; + int xstatus; + + pid = fork(); + if(pid == 0) { + char *sp = (char *) r_sp(); + sp -= USERSTACK*PGSIZE; + // the *sp should cause a trap. + printf("%s: stacktest: read below stack %d\n", s, *sp); + exit(1); + } else if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + wait(&xstatus); + if(xstatus == -1) // kernel killed child? + exit(0); + else + exit(xstatus); +} + +// check that writes to a few forbidden addresses +// cause a fault, e.g. process's text and TRAMPOLINE. +void +nowrite(char *s) +{ + int pid; + int xstatus; + uint64 addrs[] = { 0, 0x80000000LL, 0x3fffffe000, 0x3ffffff000, 0x4000000000, + 0xffffffffffffffff }; + + for(int ai = 0; ai < sizeof(addrs)/sizeof(addrs[0]); ai++){ + pid = fork(); + if(pid == 0) { + volatile int *addr = (int *) addrs[ai]; + *addr = 10; + printf("%s: write to %p did not fail!\n", s, addr); + exit(0); + } else if(pid < 0){ + printf("%s: fork failed\n", s); + exit(1); + } + wait(&xstatus); + if(xstatus == 0){ + // kernel did not kill child! + exit(1); + } + } + exit(0); +} + +// regression test. copyin(), copyout(), and copyinstr() used to cast +// the virtual page address to uint, which (with certain wild system +// call arguments) resulted in a kernel page faults. +void *big = (void*) 0xeaeb0b5b00002f5e; +void +pgbug(char *s) +{ + char *argv[1]; + argv[0] = 0; + exec(big, argv); + pipe(big); + + exit(0); +} + +// regression test. does the kernel panic if a process sbrk()s its +// size to be less than a page, or zero, or reduces the break by an +// amount too small to cause a page to be freed? +void +sbrkbugs(char *s) +{ + int pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(1); + } + if(pid == 0){ + int sz = (uint64) sbrk(0); + // free all user memory; there used to be a bug that + // would not adjust p->sz correctly in this case, + // causing exit() to panic. + sbrk(-sz); + // user page fault here. + exit(0); + } + wait(0); + + pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(1); + } + if(pid == 0){ + int sz = (uint64) sbrk(0); + // set the break to somewhere in the very first + // page; there used to be a bug that would incorrectly + // free the first page. + sbrk(-(sz - 3500)); + exit(0); + } + wait(0); + + pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(1); + } + if(pid == 0){ + // set the break in the middle of a page. + sbrk((10*4096 + 2048) - (uint64)sbrk(0)); + + // reduce the break a bit, but not enough to + // cause a page to be freed. this used to cause + // a panic. + sbrk(-10); + + exit(0); + } + wait(0); + + exit(0); +} + +// if process size was somewhat more than a page boundary, and then +// shrunk to be somewhat less than that page boundary, can the kernel +// still copyin() from addresses in the last page? +void +sbrklast(char *s) +{ + uint64 top = (uint64) sbrk(0); + if((top % 4096) != 0) + sbrk(4096 - (top % 4096)); + sbrk(4096); + sbrk(10); + sbrk(-20); + top = (uint64) sbrk(0); + char *p = (char *) (top - 64); + p[0] = 'x'; + p[1] = '\0'; + int fd = open(p, O_RDWR|O_CREATE); + write(fd, p, 1); + close(fd); + fd = open(p, O_RDWR); + p[0] = '\0'; + read(fd, p, 1); + if(p[0] != 'x') + exit(1); +} + + +// does sbrk handle signed int32 wrap-around with +// negative arguments? +void +sbrk8000(char *s) +{ + sbrk(0x80000004); + volatile char *top = sbrk(0); + *(top-1) = *(top-1) + 1; +} + + + +// regression test. test whether exec() leaks memory if one of the +// arguments is invalid. the test passes if the kernel doesn't panic. +void +badarg(char *s) +{ + for(int i = 0; i < 50000; i++){ + char *argv[2]; + argv[0] = (char*)0xffffffff; + argv[1] = 0; + exec("echo", argv); + } + + exit(0); +} + +struct test { + void (*f)(char *); + char *s; +} quicktests[] = { + {copyin, "copyin"}, + {copyout, "copyout"}, + {copyinstr1, "copyinstr1"}, + {copyinstr2, "copyinstr2"}, + {copyinstr3, "copyinstr3"}, + {rwsbrk, "rwsbrk" }, + {truncate1, "truncate1"}, + {truncate2, "truncate2"}, + {truncate3, "truncate3"}, + {openiputtest, "openiput"}, + {exitiputtest, "exitiput"}, + {iputtest, "iput"}, + {opentest, "opentest"}, + {writetest, "writetest"}, + {writebig, "writebig"}, + {createtest, "createtest"}, + {dirtest, "dirtest"}, + {exectest, "exectest"}, + {pipe1, "pipe1"}, + {killstatus, "killstatus"}, + {preempt, "preempt"}, + {exitwait, "exitwait"}, + {reparent, "reparent" }, + {twochildren, "twochildren"}, + {forkfork, "forkfork"}, + {forkforkfork, "forkforkfork"}, + {reparent2, "reparent2"}, + {mem, "mem"}, + {sharedfd, "sharedfd"}, + {fourfiles, "fourfiles"}, + {createdelete, "createdelete"}, + {unlinkread, "unlinkread"}, + {linktest, "linktest"}, + {concreate, "concreate"}, + {linkunlink, "linkunlink"}, + {subdir, "subdir"}, + {bigwrite, "bigwrite"}, + {bigfile, "bigfile"}, + {fourteen, "fourteen"}, + {rmdot, "rmdot"}, + {dirfile, "dirfile"}, + {iref, "iref"}, + {forktest, "forktest"}, + {sbrkbasic, "sbrkbasic"}, + {sbrkmuch, "sbrkmuch"}, + {kernmem, "kernmem"}, + {MAXVAplus, "MAXVAplus"}, + {sbrkfail, "sbrkfail"}, + {sbrkarg, "sbrkarg"}, + {validatetest, "validatetest"}, + {bsstest, "bsstest"}, + {bigargtest, "bigargtest"}, + {argptest, "argptest"}, + {stacktest, "stacktest"}, + {nowrite, "nowrite"}, + {pgbug, "pgbug" }, + {sbrkbugs, "sbrkbugs" }, + {sbrklast, "sbrklast"}, + {sbrk8000, "sbrk8000"}, + {badarg, "badarg" }, + + { 0, 0}, +}; + +// +// Section with tests that take a fair bit of time +// + +// directory that uses indirect blocks +void +bigdir(char *s) +{ + enum { N = 500 }; + int i, fd; + char name[10]; + + unlink("bd"); + + fd = open("bd", O_CREATE); + if(fd < 0){ + printf("%s: bigdir create failed\n", s); + exit(1); + } + close(fd); + + for(i = 0; i < N; i++){ + name[0] = 'x'; + name[1] = '0' + (i / 64); + name[2] = '0' + (i % 64); + name[3] = '\0'; + if(link("bd", name) != 0){ + printf("%s: bigdir i=%d link(bd, %s) failed\n", s, i, name); + exit(1); + } + } + + unlink("bd"); + for(i = 0; i < N; i++){ + name[0] = 'x'; + name[1] = '0' + (i / 64); + name[2] = '0' + (i % 64); + name[3] = '\0'; + if(unlink(name) != 0){ + printf("%s: bigdir unlink failed", s); + exit(1); + } + } +} + +// concurrent writes to try to provoke deadlock in the virtio disk +// driver. +void +manywrites(char *s) +{ + int nchildren = 4; + int howmany = 30; // increase to look for deadlock + + for(int ci = 0; ci < nchildren; ci++){ + int pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(1); + } + + if(pid == 0){ + char name[3]; + name[0] = 'b'; + name[1] = 'a' + ci; + name[2] = '\0'; + unlink(name); + + for(int iters = 0; iters < howmany; iters++){ + for(int i = 0; i < ci+1; i++){ + int fd = open(name, O_CREATE | O_RDWR); + if(fd < 0){ + printf("%s: cannot create %s\n", s, name); + exit(1); + } + int sz = sizeof(buf); + int cc = write(fd, buf, sz); + if(cc != sz){ + printf("%s: write(%d) ret %d\n", s, sz, cc); + exit(1); + } + close(fd); + } + unlink(name); + } + + unlink(name); + exit(0); + } + } + + for(int ci = 0; ci < nchildren; ci++){ + int st = 0; + wait(&st); + if(st != 0) + exit(st); + } + exit(0); +} + +// regression test. does write() with an invalid buffer pointer cause +// a block to be allocated for a file that is then not freed when the +// file is deleted? if the kernel has this bug, it will panic: balloc: +// out of blocks. assumed_free may need to be raised to be more than +// the number of free blocks. this test takes a long time. +void +badwrite(char *s) +{ + int assumed_free = 600; + + unlink("junk"); + for(int i = 0; i < assumed_free; i++){ + int fd = open("junk", O_CREATE|O_WRONLY); + if(fd < 0){ + printf("open junk failed\n"); + exit(1); + } + write(fd, (char*)0xffffffffffL, 1); + close(fd); + unlink("junk"); + } + + int fd = open("junk", O_CREATE|O_WRONLY); + if(fd < 0){ + printf("open junk failed\n"); + exit(1); + } + if(write(fd, "x", 1) != 1){ + printf("write failed\n"); + exit(1); + } + close(fd); + unlink("junk"); + + exit(0); +} + +// test the exec() code that cleans up if it runs out +// of memory. it's really a test that such a condition +// doesn't cause a panic. +void +execout(char *s) +{ + for(int avail = 0; avail < 15; avail++){ + int pid = fork(); + if(pid < 0){ + printf("fork failed\n"); + exit(1); + } else if(pid == 0){ + // allocate all of memory. + while(1){ + uint64 a = (uint64) sbrk(4096); + if(a == 0xffffffffffffffffLL) + break; + *(char*)(a + 4096 - 1) = 1; + } + + // free a few pages, in order to let exec() make some + // progress. + for(int i = 0; i < avail; i++) + sbrk(-4096); + + close(1); + char *args[] = { "echo", "x", 0 }; + exec("echo", args); + exit(0); + } else { + wait((int*)0); + } + } + + exit(0); +} + +// can the kernel tolerate running out of disk space? +void +diskfull(char *s) +{ + int fi; + int done = 0; + + unlink("diskfulldir"); + + for(fi = 0; done == 0 && '0' + fi < 0177; fi++){ + char name[32]; + name[0] = 'b'; + name[1] = 'i'; + name[2] = 'g'; + name[3] = '0' + fi; + name[4] = '\0'; + unlink(name); + int fd = open(name, O_CREATE|O_RDWR|O_TRUNC); + if(fd < 0){ + // oops, ran out of inodes before running out of blocks. + printf("%s: could not create file %s\n", s, name); + done = 1; + break; + } + for(int i = 0; i < MAXFILE; i++){ + char buf[BSIZE]; + if(write(fd, buf, BSIZE) != BSIZE){ + done = 1; + close(fd); + break; + } + } + close(fd); + } + + // now that there are no free blocks, test that dirlink() + // merely fails (doesn't panic) if it can't extend + // directory content. one of these file creations + // is expected to fail. + int nzz = 128; + for(int i = 0; i < nzz; i++){ + char name[32]; + name[0] = 'z'; + name[1] = 'z'; + name[2] = '0' + (i / 32); + name[3] = '0' + (i % 32); + name[4] = '\0'; + unlink(name); + int fd = open(name, O_CREATE|O_RDWR|O_TRUNC); + if(fd < 0) + break; + close(fd); + } + + // this mkdir() is expected to fail. + if(mkdir("diskfulldir") == 0) + printf("%s: mkdir(diskfulldir) unexpectedly succeeded!\n", s); + + unlink("diskfulldir"); + + for(int i = 0; i < nzz; i++){ + char name[32]; + name[0] = 'z'; + name[1] = 'z'; + name[2] = '0' + (i / 32); + name[3] = '0' + (i % 32); + name[4] = '\0'; + unlink(name); + } + + for(int i = 0; '0' + i < 0177; i++){ + char name[32]; + name[0] = 'b'; + name[1] = 'i'; + name[2] = 'g'; + name[3] = '0' + i; + name[4] = '\0'; + unlink(name); + } +} + +void +outofinodes(char *s) +{ + int nzz = 32*32; + for(int i = 0; i < nzz; i++){ + char name[32]; + name[0] = 'z'; + name[1] = 'z'; + name[2] = '0' + (i / 32); + name[3] = '0' + (i % 32); + name[4] = '\0'; + unlink(name); + int fd = open(name, O_CREATE|O_RDWR|O_TRUNC); + if(fd < 0){ + // failure is eventually expected. + break; + } + close(fd); + } + + for(int i = 0; i < nzz; i++){ + char name[32]; + name[0] = 'z'; + name[1] = 'z'; + name[2] = '0' + (i / 32); + name[3] = '0' + (i % 32); + name[4] = '\0'; + unlink(name); + } +} + +struct test slowtests[] = { + {bigdir, "bigdir"}, + {manywrites, "manywrites"}, + {badwrite, "badwrite" }, + {execout, "execout"}, + {diskfull, "diskfull"}, + {outofinodes, "outofinodes"}, + + { 0, 0}, +}; + +// +// drive tests +// + +// run each test in its own process. run returns 1 if child's exit() +// indicates success. +int +run(void f(char *), char *s) { + int pid; + int xstatus; + + printf("test %s: ", s); + if((pid = fork()) < 0) { + printf("runtest: fork error\n"); + exit(1); + } + if(pid == 0) { + f(s); + exit(0); + } else { + wait(&xstatus); + if(xstatus != 0) + printf("FAILED\n"); + else + printf("OK\n"); + return xstatus == 0; + } +} + +int +runtests(struct test *tests, char *justone, int continuous) { + for (struct test *t = tests; t->s != 0; t++) { + if((justone == 0) || strcmp(t->s, justone) == 0) { + if(!run(t->f, t->s)){ + if(continuous != 2){ + printf("SOME TESTS FAILED\n"); + return 1; + } + } + } + } + return 0; +} + + +// +// use sbrk() to count how many free physical memory pages there are. +// touches the pages to force allocation. +// because out of memory with lazy allocation results in the process +// taking a fault and being killed, fork and report back. +// +int +countfree() +{ + int fds[2]; + + if(pipe(fds) < 0){ + printf("pipe() failed in countfree()\n"); + exit(1); + } + + int pid = fork(); + + if(pid < 0){ + printf("fork failed in countfree()\n"); + exit(1); + } + + if(pid == 0){ + close(fds[0]); + + while(1){ + uint64 a = (uint64) sbrk(4096); + if(a == 0xffffffffffffffff){ + break; + } + + // modify the memory to make sure it's really allocated. + *(char *)(a + 4096 - 1) = 1; + + // report back one more page. + if(write(fds[1], "x", 1) != 1){ + printf("write() failed in countfree()\n"); + exit(1); + } + } + + exit(0); + } + + close(fds[1]); + + int n = 0; + while(1){ + char c; + int cc = read(fds[0], &c, 1); + if(cc < 0){ + printf("read() failed in countfree()\n"); + exit(1); + } + if(cc == 0) + break; + n += 1; + } + + close(fds[0]); + wait((int*)0); + + return n; +} + +int +drivetests(int quick, int continuous, char *justone) { + do { + printf("usertests starting\n"); + int free0 = countfree(); + int free1 = 0; + if (runtests(quicktests, justone, continuous)) { + if(continuous != 2) { + return 1; + } + } + if(!quick) { + if (justone == 0) + printf("usertests slow tests starting\n"); + if (runtests(slowtests, justone, continuous)) { + if(continuous != 2) { + return 1; + } + } + } + if((free1 = countfree()) < free0) { + printf("FAILED -- lost some free pages %d (out of %d)\n", free1, free0); + if(continuous != 2) { + return 1; + } + } + } while(continuous); + return 0; +} + +int +main(int argc, char *argv[]) +{ + int continuous = 0; + int quick = 0; + char *justone = 0; + + if(argc == 2 && strcmp(argv[1], "-q") == 0){ + quick = 1; + } else if(argc == 2 && strcmp(argv[1], "-c") == 0){ + continuous = 1; + } else if(argc == 2 && strcmp(argv[1], "-C") == 0){ + continuous = 2; + } else if(argc == 2 && argv[1][0] != '-'){ + justone = argv[1]; + } else if(argc > 1){ + printf("Usage: usertests [-c] [-C] [-q] [testname]\n"); + exit(1); + } + if (drivetests(quick, continuous, justone)) { + exit(1); + } + printf("ALL TESTS PASSED\n"); + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/usys.pl b/1/xv6-riscv-riscv/user/usys.pl new file mode 100755 index 0000000000000000000000000000000000000000..01e426e6b36c706967da4dfc41f6377df9da9e31 --- /dev/null +++ b/1/xv6-riscv-riscv/user/usys.pl @@ -0,0 +1,38 @@ +#!/usr/bin/perl -w + +# Generate usys.S, the stubs for syscalls. + +print "# generated by usys.pl - do not edit\n"; + +print "#include \"kernel/syscall.h\"\n"; + +sub entry { + my $name = shift; + print ".global $name\n"; + print "${name}:\n"; + print " li a7, SYS_${name}\n"; + print " ecall\n"; + print " ret\n"; +} + +entry("fork"); +entry("exit"); +entry("wait"); +entry("pipe"); +entry("read"); +entry("write"); +entry("close"); +entry("kill"); +entry("exec"); +entry("open"); +entry("mknod"); +entry("unlink"); +entry("fstat"); +entry("link"); +entry("mkdir"); +entry("chdir"); +entry("dup"); +entry("getpid"); +entry("sbrk"); +entry("sleep"); +entry("uptime"); diff --git a/1/xv6-riscv-riscv/user/wc.c b/1/xv6-riscv-riscv/user/wc.c new file mode 100644 index 0000000000000000000000000000000000000000..d8f3b2ad010f2c0fc0227702b6c10dfe8c5ee2d2 --- /dev/null +++ b/1/xv6-riscv-riscv/user/wc.c @@ -0,0 +1,55 @@ +#include "kernel/types.h" +#include "kernel/stat.h" +#include "kernel/fcntl.h" +#include "user/user.h" + +char buf[512]; + +void +wc(int fd, char *name) +{ + int i, n; + int l, w, c, inword; + + l = w = c = 0; + inword = 0; + while((n = read(fd, buf, sizeof(buf))) > 0){ + for(i=0; i<n; i++){ + c++; + if(buf[i] == '\n') + l++; + if(strchr(" \r\t\n\v", buf[i])) + inword = 0; + else if(!inword){ + w++; + inword = 1; + } + } + } + if(n < 0){ + printf("wc: read error\n"); + exit(1); + } + printf("%d %d %d %s\n", l, w, c, name); +} + +int +main(int argc, char *argv[]) +{ + int fd, i; + + if(argc <= 1){ + wc(0, ""); + exit(0); + } + + for(i = 1; i < argc; i++){ + if((fd = open(argv[i], O_RDONLY)) < 0){ + printf("wc: cannot open %s\n", argv[i]); + exit(1); + } + wc(fd, argv[i]); + close(fd); + } + exit(0); +} diff --git a/1/xv6-riscv-riscv/user/zombie.c b/1/xv6-riscv-riscv/user/zombie.c new file mode 100644 index 0000000000000000000000000000000000000000..8b89a338fcaa0ccc21bef5c6d3c6d0d53c8a9afc --- /dev/null +++ b/1/xv6-riscv-riscv/user/zombie.c @@ -0,0 +1,14 @@ +// Create a zombie process that +// must be reparented at exit. + +#include "kernel/types.h" +#include "kernel/stat.h" +#include "user/user.h" + +int +main(void) +{ + if(fork() > 0) + sleep(5); // Let child exit before parent. + exit(0); +}