diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fd26e4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,190 @@ +The modified portion from original pintos, is subject to the following license: + + RECEX SHARED SOURCE LICENSE + version 1.0, 30 May 2013 + + Copyright (c) 2020 Casys lab, KAIST. + Everyone is permitted to copy and distribute verbatim + copies of this license document, but changing it is + not allowed. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 1.0 of RecEx Shared + Source License. + + "The program", "the source", "the source code", "these + files", "this file" refers to any copyrightable work + licensed under this License. + + "You" refers to the Licensee. The licensee could be a + person or a organisation. + + "personal use" refers to usage by, only and only, the + licensee, and not by any other person or organisation + related in any way to the licensee. + + 1. Usage + + You are free to download, copy, compile, study, and + refer to the source code for any personal use of yours. + + Usage by you of any work covered by this license should + not, directly or indirectly, enable its usage by any + other individual or organisation. + + 2. Modifications + + You are free to make any modifications to the source + covered by this license. You are also free to compile + the source after modifying it and using the compiled + product obtained thereafter in compliance with this + License. + + 3. Redistribution + + You may NOT under any circumstance copy, redistribute + and/or republish the source or a work based on it (which + includes binary or object code compiled from it) in part + or whole. + + 4. Non-compliance + + You may not copy, modify, sublicense, or distribute the + Program except as expressly provided under this License. + Any attempt otherwise to copy, modify, sublicense or + distribute the Program is void, and will automatically + terminate your rights under this License. + + 5. Acceptance of License + + You are not required to accept this License, since you have + not signed it. However, nothing else grants you permission + to modify or use the source. These actions are prohibited + by law if you do not accept this License. Therefore, by + modifying or using the source (or any work based on the + source), you indicate your acceptance of this License to do + so, and all its terms and conditions for copying, + redistributing or modifying the source or works based on it. + + 6. Permissions + + If you intend to incorporate the source code, in part or whole, + into any free or proprietary program, you need to explicitly + write to the original author(s) to ask for permission. + + 7. No Warranty + + The source code licensed under this license is shared "as is". + Since it is intended for sharing as reference, there is no + warranty, of either the source code, or the program compiled + from it. The entire risk of quality or performance of the + program is with you. + + THE ORIGINAL AUTHOR OF THE PROGRAM IS NOT LIABLE TO YOU FOR + DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO + USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR + DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR + THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY + OTHER PROGRAMS) + + END OF TERMS AND CONDITIONS + +The original version of the Pintos, including its documentation, is subject to +the following license: + + Copyright (C) 2004, 2005, 2006 Board of Trustees, Leland Stanford + Jr. University. All rights reserved. + + 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. + +A few individual files in Pintos were originally derived from other +projects, but they have been extensively modified for use in Pintos. +The original code falls under the original license, and modifications +for Pintos are additionally covered by the Pintos license above. + +In particular, code derived from Nachos is subject to the following +license: + +/* Copyright (c) 1992-1996 The Regents of the University of California. + All rights reserved. + + Permission to use, copy, modify, and distribute this software + and its documentation for any purpose, without fee, and + without written agreement is hereby granted, provided that the + above copyright notice and the following two paragraphs appear + in all copies of this software. + + IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO + ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE + AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" + BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO + PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR + MODIFICATIONS. +*/ + +Also, code derived from MIT's 6.828 course code is subject to the +following license: + +/* + * Copyright (C) 1997 Massachusetts Institute of Technology + * + * This software is being provided by the copyright holders under the + * following license. By obtaining, using and/or copying this software, + * you agree that you have read, understood, and will comply with the + * following terms and conditions: + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose and without fee or royalty is + * hereby granted, provided that the full text of this NOTICE appears on + * ALL copies of the software and documentation or portions thereof, + * including modifications, that you make. + * + * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, + * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR + * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR + * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY + * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT + * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR + * DOCUMENTATION. + * + * The name and trademarks of copyright holders may NOT be used in + * advertising or publicity pertaining to the software without specific, + * written prior permission. Title to copyright in this software and any + * associated documentation will at all times remain with copyright + * holders. See the file AUTHORS which should have accompanied this software + * for a list of all copyright holders. + * + * This file may be derived from previously copyrighted software. This + * copyright applies only to those changes made by the copyright + * holders listed in the AUTHORS file. The rest of this file is covered by + * the copyright notices, if any, listed below. + */ diff --git a/Make.config b/Make.config new file mode 100644 index 0000000..153014f --- /dev/null +++ b/Make.config @@ -0,0 +1,36 @@ +# -*- makefile -*- + +SHELL = /bin/sh + +VPATH = $(SRCDIR) + +# We use x86_64 for now. +CC = gcc +LD = ld +OBJCOPY = objcopy + +ifeq ($(strip $(shell command -v $(CC) 2> /dev/null)),) +$(warning *** Compiler ($(CC)) not found. Did you set $$PATH properly? Please refer to the Getting Started section in the documentation for details. ***) +endif + +# Compiler and assembler invocation. +DEFINES = +WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers +CFLAGS = -g -msoft-float -Os -fno-omit-frame-pointer -mno-red-zone +CFLAGS += -mcmodel=large -fno-plt -fno-pic -mno-sse +CPPFLAGS = -nostdinc -I$(SRCDIR) -I$(SRCDIR)/include/lib -I$(SRCDIR)/include +CPPFLAGS += -I$(SRCDIR)/include/lib/kernel +ASFLAGS = -Wa,--gstabs -mcmodel=large +LDFLAGS = --no-relax +DEPS = -MMD -MF $(@:.o=.d) + +# Turn off -fstack-protector, which we don't support. +ifeq ($(strip $(shell echo | $(CC) -fno-stack-protector -E - > /dev/null 2>&1; echo $$?)),0) +CFLAGS += -fno-stack-protector +endif + +%.o: %.c + $(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) $(DEPS) + +%.o: %.S + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) $(DEPS) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a88a299 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +BUILD_SUBDIRS = threads filesys + +all:: + @echo "Run 'make' in subdirectories: $(BUILD_SUBDIRS)." + @echo "This top-level make has only 'clean' targets." + +CLEAN_SUBDIRS = $(BUILD_SUBDIRS) examples + +clean:: + for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done + rm -f TAGS tags + +distclean:: clean + find . -name '*~' -exec rm '{}' \; + +TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib +TAGS_SOURCES = find $(TAGS_SUBDIRS) -name \*.[chS] -print + +TAGS:: + etags `$(TAGS_SOURCES)` + +tags:: + ctags `$(TAGS_SOURCES)` + +cscope.files:: + $(TAGS_SOURCES) > cscope.files + +cscope:: cscope.files + cscope -b -q -k diff --git a/Makefile.build b/Makefile.build new file mode 100644 index 0000000..65235d6 --- /dev/null +++ b/Makefile.build @@ -0,0 +1,64 @@ +# -*- makefile -*- + +SRCDIR = ../.. + +all: os.dsk + +include ../../Make.config +include ../Make.vars +include ../../tests/Make.tests + +# Compiler and assembler options. +os.dsk: CPPFLAGS += -I$(SRCDIR)/lib/kernel + +# Core kernel. +include ../../threads/targets.mk +# User process code. +# include ../../userprog/targets.mk +# Virtual memory code. +# include ../../vm/targets.mk +# Filesystem code. +include ../../filesys/targets.mk +# Library code shared between kernel and user programs. +include ../../lib/targets.mk +# Kernel-specific library code. +include ../../lib/kernel/targets.mk +# Device driver code. +include ../../devices/targets.mk + +SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC)) +OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES))) +DEPENDS = $(patsubst %.o,%.d,$(OBJECTS)) + +threads/kernel.lds.s: CPPFLAGS += -P +threads/kernel.lds.s: threads/kernel.lds.S + +kernel.o: threads/kernel.lds.s $(OBJECTS) + $(LD) $(LDFLAGS) -T $< -o $@ $(OBJECTS) + +kernel.bin: kernel.o + $(OBJCOPY) -O binary -R .note -R .comment -S $< $@.tmp + dd if=$@.tmp of=$@ bs=4096 conv=sync + rm $@.tmp + +threads/loader.o: threads/loader.S kernel.bin + $(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) -DKERNEL_LOAD_PAGES=`perl -e 'print +(-s "kernel.bin") / 4096;'` + +loader.bin: threads/loader.o + $(LD) $(LDFLAGS) -N -e start -Ttext 0x7c00 --oformat binary -o $@ $< + +os.dsk: loader.bin kernel.bin + cat $^ > $@ + +clean:: + rm -f $(OBJECTS) $(DEPENDS) + rm -f threads/loader.o threads/kernel.lds.s threads/loader.d + rm -f kernel.o kernel.lds.s + rm -f kernel.bin loader.bin os.dsk + rm -f bochsout.txt bochsrc.txt + rm -f results grade + +Makefile: $(SRCDIR)/Makefile.build + cp $< $@ + +-include $(DEPENDS) diff --git a/Makefile.kernel b/Makefile.kernel new file mode 100644 index 0000000..162a411 --- /dev/null +++ b/Makefile.kernel @@ -0,0 +1,20 @@ +# -*- makefile -*- + +all: + +include Make.vars + +DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user)) + +all grade check: $(DIRS) build/Makefile + cd build && $(MAKE) $@ +$(DIRS): + mkdir -p $@ +build/Makefile: ../Makefile.build + cp $< $@ + +build/%: $(DIRS) build/Makefile + cd build && $(MAKE) $* + +clean: + rm -rf build diff --git a/Makefile.userprog b/Makefile.userprog new file mode 100644 index 0000000..4a6255e --- /dev/null +++ b/Makefile.userprog @@ -0,0 +1,52 @@ +# -*- makefile -*- + +$(PROGS): CPPFLAGS += -I$(SRCDIR)/include/lib/user -I. +$(PROGS): CFLAGS := -fno-stack-protector + +# Linker flags. +$(PROGS): LDFLAGS = -nostdlib -static -Wl,-T,$(LDSCRIPT) +$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/user.lds + +# Library code shared between kernel and user programs. +lib_SRC = lib/debug.c # Debug code. +lib_SRC += lib/random.c # Pseudo-random numbers. +lib_SRC += lib/stdio.c # I/O library. +lib_SRC += lib/stdlib.c # Utility functions. +lib_SRC += lib/string.c # String functions. +lib_SRC += lib/arithmetic.c + +# User level only library code. +lib/user_SRC = lib/user/debug.c # Debug helpers. +lib/user_SRC += lib/user/syscall.c # System calls. +lib/user_SRC += lib/user/console.c # Console code. + +LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(lib_SRC) $(lib/user_SRC))) +LIB_DEP = $(patsubst %.o,%.d,$(LIB_OBJ)) +LIB = lib/user/entry.o libc.a + +PROGS_SRC = $(foreach prog,$(PROGS),$($(prog)_SRC)) +PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC))) +PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ)) + +all: $(PROGS) + +define TEMPLATE +$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC))) +$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT) + $$(CC) $$(CFLAGS) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) -o $$@ +endef + +$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog)))) + +libc.a: $(LIB_OBJ) + rm -f $@ + ar r $@ $^ + ranlib $@ + +clean:: + rm -f $(PROGS) $(PROGS_OBJ) $(PROGS_DEP) + rm -f $(LIB_DEP) $(LIB_OBJ) lib/user/entry.[do] libc.a + +.PHONY: all clean + +-include $(LIB_DEP) $(PROGS_DEP) diff --git a/activate b/activate new file mode 100755 index 0000000..128021d --- /dev/null +++ b/activate @@ -0,0 +1,11 @@ +#!/bin/bash + +SOURCE="${BASH_SOURCE[0]}" +while [ -h "$SOURCE" ]; do + DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" + SOURCE="$(readlink "$SOURCE")" + [[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" +done +DIR="$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd )" + +PATH=$DIR/utils:$PATH diff --git a/devices/disk.c b/devices/disk.c new file mode 100644 index 0000000..3b559d9 --- /dev/null +++ b/devices/disk.c @@ -0,0 +1,529 @@ +#include "devices/disk.h" +#include +#include +#include +#include +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/synch.h" + +/* The code in this file is an interface to an ATA (IDE) + controller. It attempts to comply to [ATA-3]. */ + +/* ATA command block port addresses. */ +#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */ +#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */ +#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */ +#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */ +#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */ +#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */ +#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */ +#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */ +#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */ + +/* ATA control block port addresses. + (If we supported non-legacy ATA controllers this would not be + flexible enough, but it's fine for what we do.) */ +#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */ +#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */ + +/* Alternate Status Register bits. */ +#define STA_BSY 0x80 /* Busy. */ +#define STA_DRDY 0x40 /* Device Ready. */ +#define STA_DRQ 0x08 /* Data Request. */ + +/* Control Register bits. */ +#define CTL_SRST 0x04 /* Software Reset. */ + +/* Device Register bits. */ +#define DEV_MBS 0xa0 /* Must be set. */ +#define DEV_LBA 0x40 /* Linear based addressing. */ +#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */ + +/* Commands. + Many more are defined but this is the small subset that we + use. */ +#define CMD_IDENTIFY_DEVICE 0xec /* IDENTIFY DEVICE. */ +#define CMD_READ_SECTOR_RETRY 0x20 /* READ SECTOR with retries. */ +#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */ + +/* An ATA device. */ +struct disk { + char name[8]; /* Name, e.g. "hd0:1". */ + struct channel *channel; /* Channel disk is on. */ + int dev_no; /* Device 0 or 1 for master or slave. */ + + bool is_ata; /* 1=This device is an ATA disk. */ + disk_sector_t capacity; /* Capacity in sectors (if is_ata). */ + + long long read_cnt; /* Number of sectors read. */ + long long write_cnt; /* Number of sectors written. */ +}; + +/* An ATA channel (aka controller). + Each channel can control up to two disks. */ +struct channel { + char name[8]; /* Name, e.g. "hd0". */ + uint16_t reg_base; /* Base I/O port. */ + uint8_t irq; /* Interrupt in use. */ + + struct lock lock; /* Must acquire to access the controller. */ + bool expecting_interrupt; /* True if an interrupt is expected, false if + any interrupt would be spurious. */ + struct semaphore completion_wait; /* Up'd by interrupt handler. */ + + struct disk devices[2]; /* The devices on this channel. */ +}; + +/* We support the two "legacy" ATA channels found in a standard PC. */ +#define CHANNEL_CNT 2 +static struct channel channels[CHANNEL_CNT]; + +static void reset_channel (struct channel *); +static bool check_device_type (struct disk *); +static void identify_ata_device (struct disk *); + +static void select_sector (struct disk *, disk_sector_t); +static void issue_pio_command (struct channel *, uint8_t command); +static void input_sector (struct channel *, void *); +static void output_sector (struct channel *, const void *); + +static void wait_until_idle (const struct disk *); +static bool wait_while_busy (const struct disk *); +static void select_device (const struct disk *); +static void select_device_wait (const struct disk *); + +static void interrupt_handler (struct intr_frame *); + +/* Initialize the disk subsystem and detect disks. */ +void +disk_init (void) { + size_t chan_no; + + for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) { + struct channel *c = &channels[chan_no]; + int dev_no; + + /* Initialize channel. */ + snprintf (c->name, sizeof c->name, "hd%zu", chan_no); + switch (chan_no) { + case 0: + c->reg_base = 0x1f0; + c->irq = 14 + 0x20; + break; + case 1: + c->reg_base = 0x170; + c->irq = 15 + 0x20; + break; + default: + NOT_REACHED (); + } + lock_init (&c->lock); + c->expecting_interrupt = false; + sema_init (&c->completion_wait, 0); + + /* Initialize devices. */ + for (dev_no = 0; dev_no < 2; dev_no++) { + struct disk *d = &c->devices[dev_no]; + snprintf (d->name, sizeof d->name, "%s:%d", c->name, dev_no); + d->channel = c; + d->dev_no = dev_no; + + d->is_ata = false; + d->capacity = 0; + + d->read_cnt = d->write_cnt = 0; + } + + /* Register interrupt handler. */ + intr_register_ext (c->irq, interrupt_handler, c->name); + + /* Reset hardware. */ + reset_channel (c); + + /* Distinguish ATA hard disks from other devices. */ + if (check_device_type (&c->devices[0])) + check_device_type (&c->devices[1]); + + /* Read hard disk identity information. */ + for (dev_no = 0; dev_no < 2; dev_no++) + if (c->devices[dev_no].is_ata) + identify_ata_device (&c->devices[dev_no]); + } +} + +/* Prints disk statistics. */ +void +disk_print_stats (void) { + int chan_no; + + for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++) { + int dev_no; + + for (dev_no = 0; dev_no < 2; dev_no++) { + struct disk *d = disk_get (chan_no, dev_no); + if (d != NULL && d->is_ata) + printf ("%s: %lld reads, %lld writes\n", + d->name, d->read_cnt, d->write_cnt); + } + } +} + +/* Returns the disk numbered DEV_NO--either 0 or 1 for master or + slave, respectively--within the channel numbered CHAN_NO. + + Pintos uses disks this way: +0:0 - boot loader, command line args, and operating system kernel +0:1 - file system +1:0 - scratch +1:1 - swap +*/ +struct disk * +disk_get (int chan_no, int dev_no) { + ASSERT (dev_no == 0 || dev_no == 1); + + if (chan_no < (int) CHANNEL_CNT) { + struct disk *d = &channels[chan_no].devices[dev_no]; + if (d->is_ata) + return d; + } + return NULL; +} + +/* Returns the size of disk D, measured in DISK_SECTOR_SIZE-byte + sectors. */ +disk_sector_t +disk_size (struct disk *d) { + ASSERT (d != NULL); + + return d->capacity; +} + +/* Reads sector SEC_NO from disk D into BUFFER, which must have + room for DISK_SECTOR_SIZE bytes. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +void +disk_read (struct disk *d, disk_sector_t sec_no, void *buffer) { + struct channel *c; + + ASSERT (d != NULL); + ASSERT (buffer != NULL); + + c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_READ_SECTOR_RETRY); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) + PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no); + input_sector (c, buffer); + d->read_cnt++; + lock_release (&c->lock); +} + +/* Write sector SEC_NO to disk D from BUFFER, which must contain + DISK_SECTOR_SIZE bytes. Returns after the disk has + acknowledged receiving the data. + Internally synchronizes accesses to disks, so external + per-disk locking is unneeded. */ +void +disk_write (struct disk *d, disk_sector_t sec_no, const void *buffer) { + struct channel *c; + + ASSERT (d != NULL); + ASSERT (buffer != NULL); + + c = d->channel; + lock_acquire (&c->lock); + select_sector (d, sec_no); + issue_pio_command (c, CMD_WRITE_SECTOR_RETRY); + if (!wait_while_busy (d)) + PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no); + output_sector (c, buffer); + sema_down (&c->completion_wait); + d->write_cnt++; + lock_release (&c->lock); +} + +/* Disk detection and identification. */ + +static void print_ata_string (char *string, size_t size); + +/* Resets an ATA channel and waits for any devices present on it + to finish the reset. */ +static void +reset_channel (struct channel *c) { + bool present[2]; + int dev_no; + + /* The ATA reset sequence depends on which devices are present, + so we start by detecting device presence. */ + for (dev_no = 0; dev_no < 2; dev_no++) { + struct disk *d = &c->devices[dev_no]; + + select_device (d); + + outb (reg_nsect (c), 0x55); + outb (reg_lbal (c), 0xaa); + + outb (reg_nsect (c), 0xaa); + outb (reg_lbal (c), 0x55); + + outb (reg_nsect (c), 0x55); + outb (reg_lbal (c), 0xaa); + + present[dev_no] = (inb (reg_nsect (c)) == 0x55 + && inb (reg_lbal (c)) == 0xaa); + } + + /* Issue soft reset sequence, which selects device 0 as a side effect. + Also enable interrupts. */ + outb (reg_ctl (c), 0); + timer_usleep (10); + outb (reg_ctl (c), CTL_SRST); + timer_usleep (10); + outb (reg_ctl (c), 0); + + timer_msleep (150); + + /* Wait for device 0 to clear BSY. */ + if (present[0]) { + select_device (&c->devices[0]); + wait_while_busy (&c->devices[0]); + } + + /* Wait for device 1 to clear BSY. */ + if (present[1]) { + int i; + + select_device (&c->devices[1]); + for (i = 0; i < 3000; i++) { + if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1) + break; + timer_msleep (10); + } + wait_while_busy (&c->devices[1]); + } +} + +/* Checks whether device D is an ATA disk and sets D's is_ata + member appropriately. If D is device 0 (master), returns true + if it's possible that a slave (device 1) exists on this + channel. If D is device 1 (slave), the return value is not + meaningful. */ +static bool +check_device_type (struct disk *d) { + struct channel *c = d->channel; + uint8_t error, lbam, lbah, status; + + select_device (d); + + error = inb (reg_error (c)); + lbam = inb (reg_lbam (c)); + lbah = inb (reg_lbah (c)); + status = inb (reg_status (c)); + + if ((error != 1 && (error != 0x81 || d->dev_no == 1)) + || (status & STA_DRDY) == 0 + || (status & STA_BSY) != 0) { + d->is_ata = false; + return error != 0x81; + } else { + d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3); + return true; + } +} + +/* Sends an IDENTIFY DEVICE command to disk D and reads the + response. Initializes D's capacity member based on the result + and prints a message describing the disk to the console. */ +static void +identify_ata_device (struct disk *d) { + struct channel *c = d->channel; + uint16_t id[DISK_SECTOR_SIZE / 2]; + + ASSERT (d->is_ata); + + /* Send the IDENTIFY DEVICE command, wait for an interrupt + indicating the device's response is ready, and read the data + into our buffer. */ + select_device_wait (d); + issue_pio_command (c, CMD_IDENTIFY_DEVICE); + sema_down (&c->completion_wait); + if (!wait_while_busy (d)) { + d->is_ata = false; + return; + } + input_sector (c, id); + + /* Calculate capacity. */ + d->capacity = id[60] | ((uint32_t) id[61] << 16); + + /* Print identification message. */ + printf ("%s: detected %'"PRDSNu" sector (", d->name, d->capacity); + if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024 * 1024) + printf ("%"PRDSNu" GB", + d->capacity / (1024 / DISK_SECTOR_SIZE * 1024 * 1024)); + else if (d->capacity > 1024 / DISK_SECTOR_SIZE * 1024) + printf ("%"PRDSNu" MB", d->capacity / (1024 / DISK_SECTOR_SIZE * 1024)); + else if (d->capacity > 1024 / DISK_SECTOR_SIZE) + printf ("%"PRDSNu" kB", d->capacity / (1024 / DISK_SECTOR_SIZE)); + else + printf ("%"PRDSNu" byte", d->capacity * DISK_SECTOR_SIZE); + printf (") disk, model \""); + print_ata_string ((char *) &id[27], 40); + printf ("\", serial \""); + print_ata_string ((char *) &id[10], 20); + printf ("\"\n"); +} + +/* Prints STRING, which consists of SIZE bytes in a funky format: + each pair of bytes is in reverse order. Does not print + trailing whitespace and/or nulls. */ +static void +print_ata_string (char *string, size_t size) { + size_t i; + + /* Find the last non-white, non-null character. */ + for (; size > 0; size--) { + int c = string[(size - 1) ^ 1]; + if (c != '\0' && !isspace (c)) + break; + } + + /* Print. */ + for (i = 0; i < size; i++) + printf ("%c", string[i ^ 1]); +} + +/* Selects device D, waiting for it to become ready, and then + writes SEC_NO to the disk's sector selection registers. (We + use LBA mode.) */ +static void +select_sector (struct disk *d, disk_sector_t sec_no) { + struct channel *c = d->channel; + + ASSERT (sec_no < d->capacity); + ASSERT (sec_no < (1UL << 28)); + + select_device_wait (d); + outb (reg_nsect (c), 1); + outb (reg_lbal (c), sec_no); + outb (reg_lbam (c), sec_no >> 8); + outb (reg_lbah (c), (sec_no >> 16)); + outb (reg_device (c), + DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24)); +} + +/* Writes COMMAND to channel C and prepares for receiving a + completion interrupt. */ +static void +issue_pio_command (struct channel *c, uint8_t command) { + /* Interrupts must be enabled or our semaphore will never be + up'd by the completion handler. */ + ASSERT (intr_get_level () == INTR_ON); + + c->expecting_interrupt = true; + outb (reg_command (c), command); +} + +/* Reads a sector from channel C's data register in PIO mode into + SECTOR, which must have room for DISK_SECTOR_SIZE bytes. */ +static void +input_sector (struct channel *c, void *sector) { + insw (reg_data (c), sector, DISK_SECTOR_SIZE / 2); +} + +/* Writes SECTOR to channel C's data register in PIO mode. + SECTOR must contain DISK_SECTOR_SIZE bytes. */ +static void +output_sector (struct channel *c, const void *sector) { + outsw (reg_data (c), sector, DISK_SECTOR_SIZE / 2); +} + +/* Low-level ATA primitives. */ + +/* Wait up to 10 seconds for the controller to become idle, that + is, for the BSY and DRQ bits to clear in the status register. + + As a side effect, reading the status register clears any + pending interrupt. */ +static void +wait_until_idle (const struct disk *d) { + int i; + + for (i = 0; i < 1000; i++) { + if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0) + return; + timer_usleep (10); + } + + printf ("%s: idle timeout\n", d->name); +} + +/* Wait up to 30 seconds for disk D to clear BSY, + and then return the status of the DRQ bit. + The ATA standards say that a disk may take as long as that to + complete its reset. */ +static bool +wait_while_busy (const struct disk *d) { + struct channel *c = d->channel; + int i; + + for (i = 0; i < 3000; i++) { + if (i == 700) + printf ("%s: busy, waiting...", d->name); + if (!(inb (reg_alt_status (c)) & STA_BSY)) { + if (i >= 700) + printf ("ok\n"); + return (inb (reg_alt_status (c)) & STA_DRQ) != 0; + } + timer_msleep (10); + } + + printf ("failed\n"); + return false; +} + +/* Program D's channel so that D is now the selected disk. */ +static void +select_device (const struct disk *d) { + struct channel *c = d->channel; + uint8_t dev = DEV_MBS; + if (d->dev_no == 1) + dev |= DEV_DEV; + outb (reg_device (c), dev); + inb (reg_alt_status (c)); + timer_nsleep (400); +} + +/* Select disk D in its channel, as select_device(), but wait for + the channel to become idle before and after. */ +static void +select_device_wait (const struct disk *d) { + wait_until_idle (d); + select_device (d); + wait_until_idle (d); +} + +/* ATA interrupt handler. */ +static void +interrupt_handler (struct intr_frame *f) { + struct channel *c; + + for (c = channels; c < channels + CHANNEL_CNT; c++) + if (f->vec_no == c->irq) { + if (c->expecting_interrupt) { + inb (reg_status (c)); /* Acknowledge interrupt. */ + sema_up (&c->completion_wait); /* Wake up waiter. */ + } else + printf ("%s: unexpected interrupt\n", c->name); + return; + } + + NOT_REACHED (); +} + + diff --git a/devices/input.c b/devices/input.c new file mode 100644 index 0000000..1901297 --- /dev/null +++ b/devices/input.c @@ -0,0 +1,48 @@ +#include "devices/input.h" +#include +#include "devices/intq.h" +#include "devices/serial.h" + +/* Stores keys from the keyboard and serial port. */ +static struct intq buffer; + +/* Initializes the input buffer. */ +void +input_init (void) { + intq_init (&buffer); +} + +/* Adds a key to the input buffer. + Interrupts must be off and the buffer must not be full. */ +void +input_putc (uint8_t key) { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (!intq_full (&buffer)); + + intq_putc (&buffer, key); + serial_notify (); +} + +/* Retrieves a key from the input buffer. + If the buffer is empty, waits for a key to be pressed. */ +uint8_t +input_getc (void) { + enum intr_level old_level; + uint8_t key; + + old_level = intr_disable (); + key = intq_getc (&buffer); + serial_notify (); + intr_set_level (old_level); + + return key; +} + +/* Returns true if the input buffer is full, + false otherwise. + Interrupts must be off. */ +bool +input_full (void) { + ASSERT (intr_get_level () == INTR_OFF); + return intq_full (&buffer); +} diff --git a/devices/intq.c b/devices/intq.c new file mode 100644 index 0000000..d9bbbe2 --- /dev/null +++ b/devices/intq.c @@ -0,0 +1,105 @@ +#include "devices/intq.h" +#include +#include "threads/thread.h" + +static int next (int pos); +static void wait (struct intq *q, struct thread **waiter); +static void signal (struct intq *q, struct thread **waiter); + +/* Initializes interrupt queue Q. */ +void +intq_init (struct intq *q) { + lock_init (&q->lock); + q->not_full = q->not_empty = NULL; + q->head = q->tail = 0; +} + +/* Returns true if Q is empty, false otherwise. */ +bool +intq_empty (const struct intq *q) { + ASSERT (intr_get_level () == INTR_OFF); + return q->head == q->tail; +} + +/* Returns true if Q is full, false otherwise. */ +bool +intq_full (const struct intq *q) { + ASSERT (intr_get_level () == INTR_OFF); + return next (q->head) == q->tail; +} + +/* Removes a byte from Q and returns it. + Q must not be empty if called from an interrupt handler. + Otherwise, if Q is empty, first sleeps until a byte is + added. */ +uint8_t +intq_getc (struct intq *q) { + uint8_t byte; + + ASSERT (intr_get_level () == INTR_OFF); + while (intq_empty (q)) { + ASSERT (!intr_context ()); + lock_acquire (&q->lock); + wait (q, &q->not_empty); + lock_release (&q->lock); + } + + byte = q->buf[q->tail]; + q->tail = next (q->tail); + signal (q, &q->not_full); + return byte; +} + +/* Adds BYTE to the end of Q. + Q must not be full if called from an interrupt handler. + Otherwise, if Q is full, first sleeps until a byte is + removed. */ +void +intq_putc (struct intq *q, uint8_t byte) { + ASSERT (intr_get_level () == INTR_OFF); + while (intq_full (q)) { + ASSERT (!intr_context ()); + lock_acquire (&q->lock); + wait (q, &q->not_full); + lock_release (&q->lock); + } + + q->buf[q->head] = byte; + q->head = next (q->head); + signal (q, &q->not_empty); +} + +/* Returns the position after POS within an intq. */ +static int +next (int pos) { + return (pos + 1) % INTQ_BUFSIZE; +} + +/* WAITER must be the address of Q's not_empty or not_full + member. Waits until the given condition is true. */ +static void +wait (struct intq *q UNUSED, struct thread **waiter) { + ASSERT (!intr_context ()); + ASSERT (intr_get_level () == INTR_OFF); + ASSERT ((waiter == &q->not_empty && intq_empty (q)) + || (waiter == &q->not_full && intq_full (q))); + + *waiter = thread_current (); + thread_block (); +} + +/* WAITER must be the address of Q's not_empty or not_full + member, and the associated condition must be true. If a + thread is waiting for the condition, wakes it up and resets + the waiting thread. */ +static void +signal (struct intq *q UNUSED, struct thread **waiter) { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT ((waiter == &q->not_empty && !intq_empty (q)) + || (waiter == &q->not_full && !intq_full (q))); + + if (*waiter != NULL) { + thread_unblock (*waiter); + *waiter = NULL; + } +} diff --git a/devices/kbd.c b/devices/kbd.c new file mode 100644 index 0000000..779a1f8 --- /dev/null +++ b/devices/kbd.c @@ -0,0 +1,186 @@ +#include "devices/kbd.h" +#include +#include +#include +#include +#include "devices/input.h" +#include "threads/interrupt.h" +#include "threads/io.h" + +/* Keyboard data register port. */ +#define DATA_REG 0x60 + +/* Current state of shift keys. + True if depressed, false otherwise. */ +static bool left_shift, right_shift; /* Left and right Shift keys. */ +static bool left_alt, right_alt; /* Left and right Alt keys. */ +static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */ + +/* Status of Caps Lock. + True when on, false when off. */ +static bool caps_lock; + +/* Number of keys pressed. */ +static int64_t key_cnt; + +static intr_handler_func keyboard_interrupt; + +/* Initializes the keyboard. */ +void +kbd_init (void) { + intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard"); +} + +/* Prints keyboard statistics. */ +void +kbd_print_stats (void) { + printf ("Keyboard: %lld keys pressed\n", key_cnt); +} + +/* Maps a set of contiguous scancodes into characters. */ +struct keymap { + uint8_t first_scancode; /* First scancode. */ + const char *chars; /* chars[0] has scancode first_scancode, + chars[1] has scancode first_scancode + 1, + and so on to the end of the string. */ +}; + +/* Keys that produce the same characters regardless of whether + the Shift keys are down. Case of letters is an exception + that we handle elsewhere. */ +static const struct keymap invariant_keymap[] = { + {0x01, "\033"}, + {0x0e, "\b"}, + {0x0f, "\tQWERTYUIOP"}, + {0x1c, "\r"}, + {0x1e, "ASDFGHJKL"}, + {0x2c, "ZXCVBNM"}, + {0x37, "*"}, + {0x39, " "}, + {0, NULL}, +}; + +/* Characters for keys pressed without Shift, for those keys + where it matters. */ +static const struct keymap unshifted_keymap[] = { + {0x02, "1234567890-="}, + {0x1a, "[]"}, + {0x27, ";'`"}, + {0x2b, "\\"}, + {0x33, ",./"}, + {0, NULL}, +}; + +/* Characters for keys pressed with Shift, for those keys where + it matters. */ +static const struct keymap shifted_keymap[] = { + {0x02, "!@#$%^&*()_+"}, + {0x1a, "{}"}, + {0x27, ":\"~"}, + {0x2b, "|"}, + {0x33, "<>?"}, + {0, NULL}, +}; + +static bool map_key (const struct keymap[], unsigned scancode, uint8_t *); + +static void +keyboard_interrupt (struct intr_frame *args UNUSED) { + /* Status of shift keys. */ + bool shift = left_shift || right_shift; + bool alt = left_alt || right_alt; + bool ctrl = left_ctrl || right_ctrl; + + /* Keyboard scancode. */ + unsigned code; + + /* False if key pressed, true if key released. */ + bool release; + + /* Character that corresponds to `code'. */ + uint8_t c; + + /* Read scancode, including second byte if prefix code. */ + code = inb (DATA_REG); + if (code == 0xe0) + code = (code << 8) | inb (DATA_REG); + + /* Bit 0x80 distinguishes key press from key release + (even if there's a prefix). */ + release = (code & 0x80) != 0; + code &= ~0x80u; + + /* Interpret key. */ + if (code == 0x3a) { + /* Caps Lock. */ + if (!release) + caps_lock = !caps_lock; + } else if (map_key (invariant_keymap, code, &c) + || (!shift && map_key (unshifted_keymap, code, &c)) + || (shift && map_key (shifted_keymap, code, &c))) { + /* Ordinary character. */ + if (!release) { + /* Handle Ctrl, Shift. + Note that Ctrl overrides Shift. */ + if (ctrl && c >= 0x40 && c < 0x60) { + /* A is 0x41, Ctrl+A is 0x01, etc. */ + c -= 0x40; + } else if (shift == caps_lock) + c = tolower (c); + + /* Handle Alt by setting the high bit. + This 0x80 is unrelated to the one used to + distinguish key press from key release. */ + if (alt) + c += 0x80; + + /* Append to keyboard buffer. */ + if (!input_full ()) { + key_cnt++; + input_putc (c); + } + } + } else { + /* Maps a keycode into a shift state variable. */ + struct shift_key { + unsigned scancode; + bool *state_var; + }; + + /* Table of shift keys. */ + static const struct shift_key shift_keys[] = { + { 0x2a, &left_shift}, + { 0x36, &right_shift}, + { 0x38, &left_alt}, + {0xe038, &right_alt}, + { 0x1d, &left_ctrl}, + {0xe01d, &right_ctrl}, + {0, NULL}, + }; + + const struct shift_key *key; + + /* Scan the table. */ + for (key = shift_keys; key->scancode != 0; key++) + if (key->scancode == code) { + *key->state_var = !release; + break; + } + } +} + +/* Scans the array of keymaps K for SCANCODE. + If found, sets *C to the corresponding character and returns + true. + If not found, returns false and C is ignored. */ +static bool +map_key (const struct keymap k[], unsigned scancode, uint8_t *c) { + for (; k->first_scancode != 0; k++) + if (scancode >= k->first_scancode + && scancode < k->first_scancode + strlen (k->chars)) { + *c = k->chars[scancode - k->first_scancode]; + return true; + } + + return false; +} diff --git a/devices/serial.c b/devices/serial.c new file mode 100644 index 0000000..fdb6083 --- /dev/null +++ b/devices/serial.c @@ -0,0 +1,215 @@ +#include "devices/serial.h" +#include +#include "devices/input.h" +#include "devices/intq.h" +#include "devices/timer.h" +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/synch.h" +#include "threads/thread.h" + +/* Register definitions for the 16550A UART used in PCs. + The 16550A has a lot more going on than shown here, but this + is all we need. + + Refer to [PC16650D] for hardware information. */ + +/* I/O port base address for the first serial port. */ +#define IO_BASE 0x3f8 + +/* DLAB=0 registers. */ +#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */ +#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */ +#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */ + +/* DLAB=1 registers. */ +#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */ +#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */ + +/* DLAB-insensitive registers. */ +#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */ +#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */ +#define LCR_REG (IO_BASE + 3) /* Line Control Register. */ +#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */ +#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */ + +/* Interrupt Enable Register bits. */ +#define IER_RECV 0x01 /* Interrupt when data received. */ +#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */ + +/* Line Control Register bits. */ +#define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */ +#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */ + +/* MODEM Control Register. */ +#define MCR_OUT2 0x08 /* Output line 2. */ + +/* Line Status Register. */ +#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */ +#define LSR_THRE 0x20 /* THR Empty. */ + +/* Transmission mode. */ +static enum { UNINIT, POLL, QUEUE } mode; + +/* Data to be transmitted. */ +static struct intq txq; + +static void set_serial (int bps); +static void putc_poll (uint8_t); +static void write_ier (void); +static intr_handler_func serial_interrupt; + +/* Initializes the serial port device for polling mode. + Polling mode busy-waits for the serial port to become free + before writing to it. It's slow, but until interrupts have + been initialized it's all we can do. */ +static void +init_poll (void) { + ASSERT (mode == UNINIT); + outb (IER_REG, 0); /* Turn off all interrupts. */ + outb (FCR_REG, 0); /* Disable FIFO. */ + set_serial (115200); /* 115.2 kbps, N-8-1. */ + outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */ + intq_init (&txq); + mode = POLL; +} + +/* Initializes the serial port device for queued interrupt-driven + I/O. With interrupt-driven I/O we don't waste CPU time + waiting for the serial device to become ready. */ +void +serial_init_queue (void) { + enum intr_level old_level; + + if (mode == UNINIT) + init_poll (); + ASSERT (mode == POLL); + + intr_register_ext (0x20 + 4, serial_interrupt, "serial"); + mode = QUEUE; + old_level = intr_disable (); + write_ier (); + intr_set_level (old_level); +} + +/* Sends BYTE to the serial port. */ +void +serial_putc (uint8_t byte) { + enum intr_level old_level = intr_disable (); + + if (mode != QUEUE) { + /* If we're not set up for interrupt-driven I/O yet, + use dumb polling to transmit a byte. */ + if (mode == UNINIT) + init_poll (); + putc_poll (byte); + } else { + /* Otherwise, queue a byte and update the interrupt enable + register. */ + if (old_level == INTR_OFF && intq_full (&txq)) { + /* Interrupts are off and the transmit queue is full. + If we wanted to wait for the queue to empty, + we'd have to reenable interrupts. + That's impolite, so we'll send a character via + polling instead. */ + putc_poll (intq_getc (&txq)); + } + + intq_putc (&txq, byte); + write_ier (); + } + + intr_set_level (old_level); +} + +/* Flushes anything in the serial buffer out the port in polling + mode. */ +void +serial_flush (void) { + enum intr_level old_level = intr_disable (); + while (!intq_empty (&txq)) + putc_poll (intq_getc (&txq)); + intr_set_level (old_level); +} + +/* The fullness of the input buffer may have changed. Reassess + whether we should block receive interrupts. + Called by the input buffer routines when characters are added + to or removed from the buffer. */ +void +serial_notify (void) { + ASSERT (intr_get_level () == INTR_OFF); + if (mode == QUEUE) + write_ier (); +} + +/* Configures the serial port for BPS bits per second. */ +static void +set_serial (int bps) { + int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */ + uint16_t divisor = base_rate / bps; /* Clock rate divisor. */ + + ASSERT (bps >= 300 && bps <= 115200); + + /* Enable DLAB. */ + outb (LCR_REG, LCR_N81 | LCR_DLAB); + + /* Set data rate. */ + outb (LS_REG, divisor & 0xff); + outb (MS_REG, divisor >> 8); + + /* Reset DLAB. */ + outb (LCR_REG, LCR_N81); +} + +/* Update interrupt enable register. */ +static void +write_ier (void) { + uint8_t ier = 0; + + ASSERT (intr_get_level () == INTR_OFF); + + /* Enable transmit interrupt if we have any characters to + transmit. */ + if (!intq_empty (&txq)) + ier |= IER_XMIT; + + /* Enable receive interrupt if we have room to store any + characters we receive. */ + if (!input_full ()) + ier |= IER_RECV; + + outb (IER_REG, ier); +} + +/* Polls the serial port until it's ready, + and then transmits BYTE. */ +static void +putc_poll (uint8_t byte) { + ASSERT (intr_get_level () == INTR_OFF); + + while ((inb (LSR_REG) & LSR_THRE) == 0) + continue; + outb (THR_REG, byte); +} + +/* Serial interrupt handler. */ +static void +serial_interrupt (struct intr_frame *f UNUSED) { + /* Inquire about interrupt in UART. Without this, we can + occasionally miss an interrupt running under QEMU. */ + inb (IIR_REG); + + /* As long as we have room to receive a byte, and the hardware + has a byte for us, receive a byte. */ + while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0) + input_putc (inb (RBR_REG)); + + /* As long as we have a byte to transmit, and the hardware is + ready to accept a byte for transmission, transmit a byte. */ + while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0) + outb (THR_REG, intq_getc (&txq)); + + /* Update interrupt enable register based on queue status. */ + write_ier (); +} diff --git a/devices/targets.mk b/devices/targets.mk new file mode 100644 index 0000000..ae82a25 --- /dev/null +++ b/devices/targets.mk @@ -0,0 +1,7 @@ +devices_SRC = devices/timer.c # Timer device. +devices_SRC += devices/kbd.c # Keyboard device. +devices_SRC += devices/vga.c # Video device. +devices_SRC += devices/serial.c # Serial port device. +devices_SRC += devices/disk.c # IDE disk device. +devices_SRC += devices/input.c # Serial and keyboard input. +devices_SRC += devices/intq.c # Interrupt queue. diff --git a/devices/timer.c b/devices/timer.c new file mode 100644 index 0000000..796f5a8 --- /dev/null +++ b/devices/timer.c @@ -0,0 +1,186 @@ +#include "devices/timer.h" +#include +#include +#include +#include +#include "threads/interrupt.h" +#include "threads/io.h" +#include "threads/synch.h" +#include "threads/thread.h" + +/* See [8254] for hardware details of the 8254 timer chip. */ + +#if TIMER_FREQ < 19 +#error 8254 timer requires TIMER_FREQ >= 19 +#endif +#if TIMER_FREQ > 1000 +#error TIMER_FREQ <= 1000 recommended +#endif + +/* Number of timer ticks since OS booted. */ +static int64_t ticks; + +/* Number of loops per timer tick. + Initialized by timer_calibrate(). */ +static unsigned loops_per_tick; + +static intr_handler_func timer_interrupt; +static bool too_many_loops (unsigned loops); +static void busy_wait (int64_t loops); +static void real_time_sleep (int64_t num, int32_t denom); + +/* Sets up the 8254 Programmable Interval Timer (PIT) to + interrupt PIT_FREQ times per second, and registers the + corresponding interrupt. */ +void +timer_init (void) { + /* 8254 input frequency divided by TIMER_FREQ, rounded to + nearest. */ + uint16_t count = (1193180 + TIMER_FREQ / 2) / TIMER_FREQ; + + outb (0x43, 0x34); /* CW: counter 0, LSB then MSB, mode 2, binary. */ + outb (0x40, count & 0xff); + outb (0x40, count >> 8); + + intr_register_ext (0x20, timer_interrupt, "8254 Timer"); +} + +/* Calibrates loops_per_tick, used to implement brief delays. */ +void +timer_calibrate (void) { + unsigned high_bit, test_bit; + + ASSERT (intr_get_level () == INTR_ON); + printf ("Calibrating timer... "); + + /* Approximate loops_per_tick as the largest power-of-two + still less than one timer tick. */ + loops_per_tick = 1u << 10; + while (!too_many_loops (loops_per_tick << 1)) { + loops_per_tick <<= 1; + ASSERT (loops_per_tick != 0); + } + + /* Refine the next 8 bits of loops_per_tick. */ + high_bit = loops_per_tick; + for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1) + if (!too_many_loops (high_bit | test_bit)) + loops_per_tick |= test_bit; + + printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ); +} + +/* Returns the number of timer ticks since the OS booted. */ +int64_t +timer_ticks (void) { + enum intr_level old_level = intr_disable (); + int64_t t = ticks; + intr_set_level (old_level); + barrier (); + return t; +} + +/* Returns the number of timer ticks elapsed since THEN, which + should be a value once returned by timer_ticks(). */ +int64_t +timer_elapsed (int64_t then) { + return timer_ticks () - then; +} + +/* Suspends execution for approximately TICKS timer ticks. */ +void +timer_sleep (int64_t ticks) { + int64_t start = timer_ticks (); + + ASSERT (intr_get_level () == INTR_ON); + while (timer_elapsed (start) < ticks) + thread_yield (); +} + +/* Suspends execution for approximately MS milliseconds. */ +void +timer_msleep (int64_t ms) { + real_time_sleep (ms, 1000); +} + +/* Suspends execution for approximately US microseconds. */ +void +timer_usleep (int64_t us) { + real_time_sleep (us, 1000 * 1000); +} + +/* Suspends execution for approximately NS nanoseconds. */ +void +timer_nsleep (int64_t ns) { + real_time_sleep (ns, 1000 * 1000 * 1000); +} + +/* Prints timer statistics. */ +void +timer_print_stats (void) { + printf ("Timer: %"PRId64" ticks\n", timer_ticks ()); +} + +/* Timer interrupt handler. */ +static void +timer_interrupt (struct intr_frame *args UNUSED) { + ticks++; + thread_tick (); +} + +/* Returns true if LOOPS iterations waits for more than one timer + tick, otherwise false. */ +static bool +too_many_loops (unsigned loops) { + /* Wait for a timer tick. */ + int64_t start = ticks; + while (ticks == start) + barrier (); + + /* Run LOOPS loops. */ + start = ticks; + busy_wait (loops); + + /* If the tick count changed, we iterated too long. */ + barrier (); + return start != ticks; +} + +/* Iterates through a simple loop LOOPS times, for implementing + brief delays. + + Marked NO_INLINE because code alignment can significantly + affect timings, so that if this function was inlined + differently in different places the results would be difficult + to predict. */ +static void NO_INLINE +busy_wait (int64_t loops) { + while (loops-- > 0) + barrier (); +} + +/* Sleep for approximately NUM/DENOM seconds. */ +static void +real_time_sleep (int64_t num, int32_t denom) { + /* Convert NUM/DENOM seconds into timer ticks, rounding down. + + (NUM / DENOM) s + ---------------------- = NUM * TIMER_FREQ / DENOM ticks. + 1 s / TIMER_FREQ ticks + */ + int64_t ticks = num * TIMER_FREQ / denom; + + ASSERT (intr_get_level () == INTR_ON); + if (ticks > 0) { + /* We're waiting for at least one full timer tick. Use + timer_sleep() because it will yield the CPU to other + processes. */ + timer_sleep (ticks); + } else { + /* Otherwise, use a busy-wait loop for more accurate + sub-tick timing. We scale the numerator and denominator + down by 1000 to avoid the possibility of overflow. */ + ASSERT (denom % 1000 == 0); + busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000)); + } +} diff --git a/devices/vga.c b/devices/vga.c new file mode 100644 index 0000000..cf9a903 --- /dev/null +++ b/devices/vga.c @@ -0,0 +1,156 @@ +#include "devices/vga.h" +#include +#include +#include +#include +#include "threads/io.h" +#include "threads/interrupt.h" +#include "threads/vaddr.h" + +/* VGA text screen support. See [FREEVGA] for more information. */ + +/* Number of columns and rows on the text display. */ +#define COL_CNT 80 +#define ROW_CNT 25 + +/* Current cursor position. (0,0) is in the upper left corner of + the display. */ +static size_t cx, cy; + +/* Attribute value for gray text on a black background. */ +#define GRAY_ON_BLACK 0x07 + +/* Framebuffer. See [FREEVGA] under "VGA Text Mode Operation". + The character at (x,y) is fb[y][x][0]. + The attribute at (x,y) is fb[y][x][1]. */ +static uint8_t (*fb)[COL_CNT][2]; + +static void clear_row (size_t y); +static void cls (void); +static void newline (void); +static void move_cursor (void); +static void find_cursor (size_t *x, size_t *y); + +/* Initializes the VGA text display. */ +static void +init (void) { + /* Already initialized? */ + static bool inited; + if (!inited) { + fb = ptov (0xb8000); + find_cursor (&cx, &cy); + inited = true; + } +} + +/* Writes C to the VGA text display, interpreting control + characters in the conventional ways. */ +void +vga_putc (int c) { + /* Disable interrupts to lock out interrupt handlers + that might write to the console. */ + enum intr_level old_level = intr_disable (); + + init (); + + switch (c) { + case '\n': + newline (); + break; + + case '\f': + cls (); + break; + + case '\b': + if (cx > 0) + cx--; + break; + + case '\r': + cx = 0; + break; + + case '\t': + cx = ROUND_UP (cx + 1, 8); + if (cx >= COL_CNT) + newline (); + break; + + default: + fb[cy][cx][0] = c; + fb[cy][cx][1] = GRAY_ON_BLACK; + if (++cx >= COL_CNT) + newline (); + break; + } + + /* Update cursor position. */ + move_cursor (); + + intr_set_level (old_level); +} + +/* Clears the screen and moves the cursor to the upper left. */ +static void +cls (void) { + size_t y; + + for (y = 0; y < ROW_CNT; y++) + clear_row (y); + + cx = cy = 0; + move_cursor (); +} + +/* Clears row Y to spaces. */ +static void +clear_row (size_t y) { + size_t x; + + for (x = 0; x < COL_CNT; x++) + { + fb[y][x][0] = ' '; + fb[y][x][1] = GRAY_ON_BLACK; + } +} + +/* Advances the cursor to the first column in the next line on + the screen. If the cursor is already on the last line on the + screen, scrolls the screen upward one line. */ +static void +newline (void) { + cx = 0; + cy++; + if (cy >= ROW_CNT) + { + cy = ROW_CNT - 1; + memmove (&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1)); + clear_row (ROW_CNT - 1); + } +} + +/* Moves the hardware cursor to (cx,cy). */ +static void +move_cursor (void) { + /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */ + uint16_t cp = cx + COL_CNT * cy; + outw (0x3d4, 0x0e | (cp & 0xff00)); + outw (0x3d4, 0x0f | (cp << 8)); +} + +/* Reads the current hardware cursor position into (*X,*Y). */ +static void +find_cursor (size_t *x, size_t *y) { + /* See [FREEVGA] under "Manipulating the Text-mode Cursor". */ + uint16_t cp; + + outb (0x3d4, 0x0e); + cp = inb (0x3d5) << 8; + + outb (0x3d4, 0x0f); + cp |= inb (0x3d5); + + *x = cp % COL_CNT; + *y = cp / COL_CNT; +} diff --git a/examples/Makefile b/examples/Makefile new file mode 100644 index 0000000..2128cc2 --- /dev/null +++ b/examples/Makefile @@ -0,0 +1,34 @@ +SRCDIR = .. + +# Test programs to compile, and a list of sources for each. +# To add a new test, put its name on the PROGS list +# and then add a name_SRC line that lists its source files. +PROGS = cat cmp cp echo halt hex-dump ls mcat mcp mkdir pwd rm shell \ + bubsort insult lineup matmult recursor + +# Should work from project 2 onward. +cat_SRC = cat.c +cmp_SRC = cmp.c +cp_SRC = cp.c +echo_SRC = echo.c +halt_SRC = halt.c +hex-dump_SRC = hex-dump.c +insult_SRC = insult.c +lineup_SRC = lineup.c +ls_SRC = ls.c +recursor_SRC = recursor.c +rm_SRC = rm.c + +# Should work in project 3; also in project 4 if VM is included. +bubsort_SRC = bubsort.c +matmult_SRC = matmult.c +mcat_SRC = mcat.c +mcp_SRC = mcp.c + +# Should work in project 4. +mkdir_SRC = mkdir.c +pwd_SRC = pwd.c +shell_SRC = shell.c + +include $(SRCDIR)/Make.config +include $(SRCDIR)/Makefile.userprog diff --git a/examples/bubsort.c b/examples/bubsort.c new file mode 100644 index 0000000..343219e --- /dev/null +++ b/examples/bubsort.c @@ -0,0 +1,38 @@ +/* sort.c + + Test program to sort a large number of integers. + + Intention is to stress virtual memory system. + + Ideally, we could read the unsorted array off of the file + system, and store the result back to the file system! */ +#include + +/* Size of array to sort. */ +#define SORT_SIZE 128 + +int +main (void) +{ + /* Array to sort. Static to reduce stack usage. */ + static int array[SORT_SIZE]; + + int i, j, tmp; + + /* First initialize the array in descending order. */ + for (i = 0; i < SORT_SIZE; i++) + array[i] = SORT_SIZE - i - 1; + + /* Then sort in ascending order. */ + for (i = 0; i < SORT_SIZE - 1; i++) + for (j = 0; j < SORT_SIZE - 1 - i; j++) + if (array[j] > array[j + 1]) + { + tmp = array[j]; + array[j] = array[j + 1]; + array[j + 1] = tmp; + } + + printf ("sort exiting with code %d\n", array[0]); + return array[0]; +} diff --git a/examples/cat.c b/examples/cat.c new file mode 100644 index 0000000..c8d229d --- /dev/null +++ b/examples/cat.c @@ -0,0 +1,34 @@ +/* cat.c + + Prints files specified on command line to the console. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + bool success = true; + int i; + + for (i = 1; i < argc; i++) + { + int fd = open (argv[i]); + if (fd < 0) + { + printf ("%s: open failed\n", argv[i]); + success = false; + continue; + } + for (;;) + { + char buffer[1024]; + int bytes_read = read (fd, buffer, sizeof buffer); + if (bytes_read == 0) + break; + write (STDOUT_FILENO, buffer, bytes_read); + } + close (fd); + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/cmp.c b/examples/cmp.c new file mode 100644 index 0000000..94b406d --- /dev/null +++ b/examples/cmp.c @@ -0,0 +1,68 @@ +/* cat.c + + Compares two files. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + int fd[2]; + + if (argc != 3) + { + printf ("usage: cmp A B\n"); + return EXIT_FAILURE; + } + + /* Open files. */ + fd[0] = open (argv[1]); + if (fd[0] < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + fd[1] = open (argv[2]); + if (fd[1] < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + + /* Compare data. */ + for (;;) + { + int pos; + char buffer[2][1024]; + int bytes_read[2]; + int min_read; + int i; + + pos = tell (fd[0]); + bytes_read[0] = read (fd[0], buffer[0], sizeof buffer[0]); + bytes_read[1] = read (fd[1], buffer[1], sizeof buffer[1]); + min_read = bytes_read[0] < bytes_read[1] ? bytes_read[0] : bytes_read[1]; + if (min_read == 0) + break; + + for (i = 0; i < min_read; i++) + if (buffer[0][i] != buffer[1][i]) + { + printf ("Byte %d is %02hhx ('%c') in %s but %02hhx ('%c') in %s\n", + pos + i, + buffer[0][i], buffer[0][i], argv[1], + buffer[1][i], buffer[1][i], argv[2]); + return EXIT_FAILURE; + } + + if (min_read < bytes_read[1]) + printf ("%s is shorter than %s\n", argv[1], argv[2]); + else if (min_read < bytes_read[0]) + printf ("%s is shorter than %s\n", argv[2], argv[1]); + } + + printf ("%s and %s are identical\n", argv[1], argv[2]); + + return EXIT_SUCCESS; +} diff --git a/examples/cp.c b/examples/cp.c new file mode 100644 index 0000000..86a5cd7 --- /dev/null +++ b/examples/cp.c @@ -0,0 +1,55 @@ +/* cat.c + +Copies one file to another. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + int in_fd, out_fd; + + if (argc != 3) + { + printf ("usage: cp OLD NEW\n"); + return EXIT_FAILURE; + } + + /* Open input file. */ + in_fd = open (argv[1]); + if (in_fd < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + + /* Create and open output file. */ + if (!create (argv[2], filesize (in_fd))) + { + printf ("%s: create failed\n", argv[2]); + return EXIT_FAILURE; + } + out_fd = open (argv[2]); + if (out_fd < 0) + { + printf ("%s: open failed\n", argv[2]); + return EXIT_FAILURE; + } + + /* Copy data. */ + for (;;) + { + char buffer[1024]; + int bytes_read = read (in_fd, buffer, sizeof buffer); + if (bytes_read == 0) + break; + if (write (out_fd, buffer, bytes_read) != bytes_read) + { + printf ("%s: write failed\n", argv[2]); + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} diff --git a/examples/echo.c b/examples/echo.c new file mode 100644 index 0000000..1b136f2 --- /dev/null +++ b/examples/echo.c @@ -0,0 +1,14 @@ +#include +#include + +int +main (int argc, char **argv) +{ + int i; + + for (i = 0; i < argc; i++) + printf ("%s ", argv[i]); + printf ("\n"); + + return EXIT_SUCCESS; +} diff --git a/examples/halt.c b/examples/halt.c new file mode 100644 index 0000000..bad7250 --- /dev/null +++ b/examples/halt.c @@ -0,0 +1,14 @@ +/* halt.c + + Simple program to test whether running a user program works. + + Just invokes a system call that shuts down the OS. */ + +#include + +int +main (void) +{ + halt (); + /* not reached */ +} diff --git a/examples/hex-dump.c b/examples/hex-dump.c new file mode 100644 index 0000000..ee313f2 --- /dev/null +++ b/examples/hex-dump.c @@ -0,0 +1,35 @@ +/* hex-dump.c + + Prints files specified on command line to the console in hex. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + bool success = true; + int i; + + for (i = 1; i < argc; i++) + { + int fd = open (argv[i]); + if (fd < 0) + { + printf ("%s: open failed\n", argv[i]); + success = false; + continue; + } + for (;;) + { + char buffer[1024]; + int pos = tell (fd); + int bytes_read = read (fd, buffer, sizeof buffer); + if (bytes_read == 0) + break; + hex_dump (pos, buffer, bytes_read, true); + } + close (fd); + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/insult.c b/examples/insult.c new file mode 100644 index 0000000..98c4e6a --- /dev/null +++ b/examples/insult.c @@ -0,0 +1,369 @@ +/* Insult.c + + This is a version of the famous CS 107 random sentence + generator. I wrote a program that reads a grammar definition + file and writes a C file containing that grammar as hard code + static C strings. Thus the majority of the code below in + machine generated and totally unreadable. The arrays created + are specially designed to make generating the sentences as + easy as possible. + + Originally by Greg Hutchins, March 1998. + Modified by Ben Pfaff for Pintos, Sept 2004. */ +char *start[] = + { "You", "1", "5", ".", "May", "13", ".", "With", "the", "19", "of", "18", +",", "may", "13", "." +}; +char startLoc[] = { 3, 0, 4, 7, 16 }; +char *adj[] = { "3", "4", "2", ",", "1" }; +char adjLoc[] = { 3, 0, 1, 2, 5 }; +char *adj3[] = { "3", "4" }; +char adj3Loc[] = { 2, 0, 1, 2 }; +char *adj1[] = + { "lame", "dried", "up", "par-broiled", "bloated", "half-baked", "spiteful", +"egotistical", "ungrateful", "stupid", "moronic", "fat", "ugly", "puny", "pitiful", +"insignificant", "blithering", "repulsive", "worthless", "blundering", "retarded", +"useless", "obnoxious", "low-budget", "assinine", "neurotic", "subhuman", "crochety", +"indescribable", "contemptible", "unspeakable", "sick", "lazy", "good-for-nothing", +"slutty", "mentally-deficient", "creepy", "sloppy", "dismal", "pompous", "pathetic", +"friendless", "revolting", "slovenly", "cantankerous", "uncultured", "insufferable", +"gross", "unkempt", "defective", "crumby" +}; +char adj1Loc[] = + { 50, 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, +43, 44, 45, 46, 47, 48, 49, 50, 51 }; +char *adj2[] = + { "putrefied", "festering", "funky", "moldy", "leprous", "curdled", "fetid", +"slimy", "crusty", "sweaty", "damp", "deranged", "smelly", "stenchy", "malignant", +"noxious", "grimy", "reeky", "nasty", "mutilated", "sloppy", "gruesome", "grisly", +"sloshy", "wormy", "mealy", "spoiled", "contaminated", "rancid", "musty", +"fly-covered", "moth-eaten", "decaying", "decomposed", "freeze-dried", "defective", +"petrified", "rotting", "scabrous", "hirsute" +}; +char adj2Loc[] = + { 40, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, +20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 }; +char *name[] = + { "10", ",", "bad", "excuse", "for", "6", ",", "6", "for", "brains", ",", +"4", "11", "8", "for", "brains", "offspring", "of", "a", "motherless", "10", "7", "6", +"7", "4", "11", "8" +}; +char nameLoc[] = { 7, 0, 1, 6, 10, 16, 21, 23, 27 }; +char *stuff[] = + { "shit", "toe", "jam", "filth", "puss", "earwax", "leaf", "clippings", +"bat", "guano", "mucus", "fungus", "mung", "refuse", "earwax", "spittoon", "spittle", +"phlegm" +}; +char stuffLoc[] = { 14, 0, 1, 3, 4, 5, 6, 8, 10, 11, 12, 13, 14, 15, 17, 18 }; +char *noun_and_prep[] = + { "bit", "of", "piece", "of", "vat", "of", "lump", "of", "crock", "of", +"ball", "of", "tub", "of", "load", "of", "bucket", "of", "mound", "of", "glob", "of", "bag", +"of", "heap", "of", "mountain", "of", "load", "of", "barrel", "of", "sack", "of", "blob", "of", +"pile", "of", "truckload", "of", "vat", "of" +}; +char noun_and_prepLoc[] = + { 21, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, +38, 40, 42 }; +char *organics[] = + { "droppings", "mung", "zits", "puckies", "tumors", "cysts", "tumors", +"livers", "froth", "parts", "scabs", "guts", "entrails", "blubber", "carcuses", "gizards", +"9" +}; +char organicsLoc[] = + { 17, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 }; +char *body_parts[] = + { "kidneys", "genitals", "buttocks", "earlobes", "innards", "feet" +}; +char body_partsLoc[] = { 6, 0, 1, 2, 3, 4, 5, 6 }; +char *noun[] = + { "pop", "tart", "warthog", "twinkie", "barnacle", "fondue", "pot", +"cretin", "fuckwad", "moron", "ass", "neanderthal", "nincompoop", "simpleton", "11" +}; +char nounLoc[] = { 13, 0, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; +char *animal[] = + { "donkey", "llama", "dingo", "lizard", "gekko", "lemur", "moose", "camel", +"goat", "eel" +}; +char animalLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +char *good_verb[] = + { "love", "cuddle", "fondle", "adore", "smooch", "hug", "caress", "worship", +"look", "at", "touch" +}; +char good_verbLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11 }; +char *curse[] = + { "14", "20", "23", "14", "17", "20", "23", "14", "find", "your", "9", +"suddenly", "delectable", "14", "and", "14", "seek", "a", "battleground", "23" +}; +char curseLoc[] = { 4, 0, 3, 7, 13, 20 }; +char *afflictors[] = + { "15", "21", "15", "21", "15", "21", "15", "21", "a", "22", "Rush", +"Limbaugh", "the", "hosts", "of", "Hades" +}; +char afflictorsLoc[] = { 6, 0, 2, 4, 6, 8, 12, 16 }; +char *quantity[] = + { "a", "4", "hoard", "of", "a", "4", "pack", "of", "a", "truckload", "of", +"a", "swarm", "of", "many", "an", "army", "of", "a", "4", "heard", "of", "a", "4", +"platoon", "of", "a", "4", "and", "4", "group", "of", "16" +}; +char quantityLoc[] = { 10, 0, 4, 8, 11, 14, 15, 18, 22, 26, 32, 33 }; +char *numbers[] = + { "a", "thousand", "three", "million", "ninty-nine", "nine-hundred,", +"ninty-nine", "forty-two", "a", "gazillion", "sixty-eight", "times", "thirty-three" +}; +char numbersLoc[] = { 7, 0, 2, 4, 5, 7, 8, 10, 13 }; +char *adv[] = + { "viciously", "manicly", "merrily", "happily", ",", "with", "the", "19", +"of", "18", ",", "gleefully", ",", "with", "much", "ritualistic", "celebration", ",", +"franticly" +}; +char advLoc[] = { 8, 0, 1, 2, 3, 4, 11, 12, 18, 19 }; +char *metaphor[] = + { "an", "irate", "manticore", "Thor's", "belch", "Alah's", "fist", "16", +"titans", "a", "particularly", "vicious", "she-bear", "in", "the", "midst", "of", "her", +"menstrual", "cycle", "a", "pissed-off", "Jabberwock" +}; +char metaphorLoc[] = { 6, 0, 3, 5, 7, 9, 20, 23 }; +char *force[] = { "force", "fury", "power", "rage" }; +char forceLoc[] = { 4, 0, 1, 2, 3, 4 }; +char *bad_action[] = + { "spit", "shimmy", "slobber", "find", "refuge", "find", "shelter", "dance", +"retch", "vomit", "defecate", "erect", "a", "strip", "mall", "build", "a", "26", "have", "a", +"religious", "experience", "discharge", "bodily", "waste", "fart", "dance", "drool", +"lambada", "spill", "16", "rusty", "tacks", "bite", "you", "sneeze", "sing", "16", +"campfire", "songs", "smite", "you", "16", "times", "construct", "a", "new", "home", "throw", +"a", "party", "procreate" +}; +char bad_actionLoc[] = + { 25, 0, 1, 2, 3, 5, 7, 8, 9, 10, 11, 15, 18, 22, 25, 26, 27, 28, 29, 33, +35, 36, 40, 44, 48, 51, 52 }; +char *beasties[] = + { "yaks", "22", "maggots", "22", "cockroaches", "stinging", "scorpions", +"fleas", "22", "weasels", "22", "gnats", "South", "American", "killer", "bees", "spiders", +"4", "monkeys", "22", "wiener-dogs", "22", "rats", "22", "wolverines", "4", ",", "22", +"pit-fiends" +}; +char beastiesLoc[] = + { 14, 0, 1, 3, 5, 7, 8, 10, 12, 16, 17, 19, 21, 23, 25, 29 }; +char *condition[] = + { "frothing", "manic", "crazed", "plague-ridden", "disease-carrying", +"biting", "rabid", "blood-thirsty", "ravaging", "slavering" +}; +char conditionLoc[] = { 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; +char *place[] = + { "in", "24", "25", "upon", "your", "mother's", "grave", "on", "24", "best", +"rug", "in", "the", "26", "you", "call", "home", "upon", "your", "heinie" +}; +char placeLoc[] = { 5, 0, 3, 7, 11, 17, 20 }; +char *relation[] = + { "your", "your", "your", "your", "father's", "your", "mother's", "your", +"grandma's" +}; +char relationLoc[] = { 6, 0, 1, 2, 3, 5, 7, 9 }; +char *in_something[] = + { "entrails", "anal", "cavity", "shoes", "house", "pantry", "general", +"direction", "pants", "bed" +}; +char in_somethingLoc[] = { 8, 0, 1, 3, 4, 5, 6, 8, 9, 10 }; +char *bad_place[] = + { "rat", "hole", "sewer", "toxic", "dump", "oil", "refinery", "landfill", +"porto-pottie" +}; +char bad_placeLoc[] = { 6, 0, 2, 3, 5, 7, 8, 9 }; +char **daGrammar[27]; +char *daGLoc[27]; + +static void +init_grammar (void) +{ + daGrammar[0] = start; + daGLoc[0] = startLoc; + daGrammar[1] = adj; + daGLoc[1] = adjLoc; + daGrammar[2] = adj3; + daGLoc[2] = adj3Loc; + daGrammar[3] = adj1; + daGLoc[3] = adj1Loc; + daGrammar[4] = adj2; + daGLoc[4] = adj2Loc; + daGrammar[5] = name; + daGLoc[5] = nameLoc; + daGrammar[6] = stuff; + daGLoc[6] = stuffLoc; + daGrammar[7] = noun_and_prep; + daGLoc[7] = noun_and_prepLoc; + daGrammar[8] = organics; + daGLoc[8] = organicsLoc; + daGrammar[9] = body_parts; + daGLoc[9] = body_partsLoc; + daGrammar[10] = noun; + daGLoc[10] = nounLoc; + daGrammar[11] = animal; + daGLoc[11] = animalLoc; + daGrammar[12] = good_verb; + daGLoc[12] = good_verbLoc; + daGrammar[13] = curse; + daGLoc[13] = curseLoc; + daGrammar[14] = afflictors; + daGLoc[14] = afflictorsLoc; + daGrammar[15] = quantity; + daGLoc[15] = quantityLoc; + daGrammar[16] = numbers; + daGLoc[16] = numbersLoc; + daGrammar[17] = adv; + daGLoc[17] = advLoc; + daGrammar[18] = metaphor; + daGLoc[18] = metaphorLoc; + daGrammar[19] = force; + daGLoc[19] = forceLoc; + daGrammar[20] = bad_action; + daGLoc[20] = bad_actionLoc; + daGrammar[21] = beasties; + daGLoc[21] = beastiesLoc; + daGrammar[22] = condition; + daGLoc[22] = conditionLoc; + daGrammar[23] = place; + daGLoc[23] = placeLoc; + daGrammar[24] = relation; + daGLoc[24] = relationLoc; + daGrammar[25] = in_something; + daGLoc[25] = in_somethingLoc; + daGrammar[26] = bad_place; + daGLoc[26] = bad_placeLoc; +} + +#include +#include +#include +#include +#include +#include +#include + +void expand (int num, char **grammar[], char *location[], int handle); + +static void +usage (int ret_code, const char *message, ...) PRINTF_FORMAT (2, 3); + +static void +usage (int ret_code, const char *message, ...) +{ + va_list args; + + if (message != NULL) + { + va_start (args, message); + vprintf (message, args); + va_end (args); + } + + printf ("\n" + "Usage: insult [OPTION]...\n" + "Prints random insults to screen.\n\n" + " -h: this help message\n" + " -s : set the random seed (default 4951)\n" + " -n : choose number of insults (default 4)\n" + " -f : redirect output to \n"); + + exit (ret_code); +} + +int +main (int argc, char *argv[]) +{ + int sentence_cnt, new_seed, i, file_flag, sent_flag, seed_flag; + int handle; + + new_seed = 4951; + sentence_cnt = 4; + file_flag = 0; + seed_flag = 0; + sent_flag = 0; + handle = STDOUT_FILENO; + + for (i = 1; i < argc; i++) + { + if (strcmp (argv[1], "-h") == 0) + usage (0, NULL); + else if (strcmp (argv[i], "-s") == 0) + { + if (seed_flag++) + usage (-1, "Can't have more than one seed"); + if (++i >= argc) + usage (-1, "Missing value for -s"); + new_seed = atoi (argv[i]); + } + else if (strcmp (argv[i], "-n") == 0) + { + if (sent_flag++) + usage (-1, "Can't have more than one sentence option"); + if (++i >= argc) + usage (-1, "Missing value for -n"); + sentence_cnt = atoi (argv[i]); + if (sentence_cnt < 1) + usage (-1, "Must have at least one sentence"); + } + else if (strcmp (argv[i], "-f") == 0) + { + if (file_flag++) + usage (-1, "Can't have more than one output file"); + if (++i >= argc) + usage (-1, "Missing value for -f"); + + /* Because files have fixed length in the basic Pintos + file system, the 0 argument means that this option + will not be useful until project 4 is + implemented. */ + create (argv[i], 0); + handle = open (argv[i]); + if (handle < 0) + { + printf ("%s: open failed\n", argv[i]); + return EXIT_FAILURE; + } + } + else + usage (-1, "Unrecognized flag"); + } + + init_grammar (); + + random_init (new_seed); + hprintf (handle, "\n"); + + for (i = 0; i < sentence_cnt; i++) + { + hprintf (handle, "\n"); + expand (0, daGrammar, daGLoc, handle); + hprintf (handle, "\n\n"); + } + + if (file_flag) + close (handle); + + return EXIT_SUCCESS; +} + +void +expand (int num, char **grammar[], char *location[], int handle) +{ + char *word; + int i, which, listStart, listEnd; + + which = random_ulong () % location[num][0] + 1; + listStart = location[num][which]; + listEnd = location[num][which + 1]; + for (i = listStart; i < listEnd; i++) + { + word = grammar[num][i]; + if (!isdigit (*word)) + { + if (!ispunct (*word)) + hprintf (handle, " "); + hprintf (handle, "%s", word); + } + else + expand (atoi (word), grammar, location, handle); + } + +} diff --git a/examples/lineup.c b/examples/lineup.c new file mode 100644 index 0000000..60402d0 --- /dev/null +++ b/examples/lineup.c @@ -0,0 +1,46 @@ +/* lineup.c + + Converts a file to uppercase in-place. + + Incidentally, another way to do this while avoiding the seeks + would be to open the input file, then remove() it and reopen + it under another handle. Because of Unix deletion semantics + this works fine. */ + +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + char buf[1024]; + int handle; + + if (argc != 2) + exit (1); + + handle = open (argv[1]); + if (handle < 0) + exit (2); + + for (;;) + { + int n, i; + + n = read (handle, buf, sizeof buf); + if (n <= 0) + break; + + for (i = 0; i < n; i++) + buf[i] = toupper ((unsigned char) buf[i]); + + seek (handle, tell (handle) - n); + if (write (handle, buf, n) != n) + printf ("write failed\n"); + } + + close (handle); + + return EXIT_SUCCESS; +} diff --git a/examples/ls.c b/examples/ls.c new file mode 100644 index 0000000..fbe27a1 --- /dev/null +++ b/examples/ls.c @@ -0,0 +1,90 @@ +/* ls.c + + Lists the contents of the directory or directories named on + the command line, or of the current directory if none are + named. + + By default, only the name of each file is printed. If "-l" is + given as the first argument, the type, size, and inumber of + each file is also printed. This won't work until project 4. */ + +#include +#include +#include + +static bool +list_dir (const char *dir, bool verbose) +{ + int dir_fd = open (dir); + if (dir_fd == -1) + { + printf ("%s: not found\n", dir); + return false; + } + + if (isdir (dir_fd)) + { + char name[READDIR_MAX_LEN]; + + printf ("%s", dir); + if (verbose) + printf (" (inumber %d)", inumber (dir_fd)); + printf (":\n"); + + while (readdir (dir_fd, name)) + { + printf ("%s", name); + if (verbose) + { + char full_name[128]; + int entry_fd; + + snprintf (full_name, sizeof full_name, "%s/%s", dir, name); + entry_fd = open (full_name); + + printf (": "); + if (entry_fd != -1) + { + if (isdir (entry_fd)) + printf ("directory"); + else + printf ("%d-byte file", filesize (entry_fd)); + printf (", inumber %d", inumber (entry_fd)); + } + else + printf ("open failed"); + close (entry_fd); + } + printf ("\n"); + } + } + else + printf ("%s: not a directory\n", dir); + close (dir_fd); + return true; +} + +int +main (int argc, char *argv[]) +{ + bool success = true; + bool verbose = false; + + if (argc > 1 && !strcmp (argv[1], "-l")) + { + verbose = true; + argv++; + argc--; + } + + if (argc <= 1) + success = list_dir (".", verbose); + else + { + int i; + for (i = 1; i < argc; i++) + if (!list_dir (argv[i], verbose)) + success = false; + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/matmult.c b/examples/matmult.c new file mode 100644 index 0000000..4f0615f --- /dev/null +++ b/examples/matmult.c @@ -0,0 +1,57 @@ +/* matmult.c + + Test program to do matrix multiplication on large arrays. + + Intended to stress virtual memory system. + + Ideally, we could read the matrices off of the file system, + and store the result back to the file system! + */ + +#include +#include + +/* You should define DIM to be large enough that the arrays + don't fit in physical memory. + + Dim Memory + ------ -------- + 16 3 kB + 64 48 kB + 128 192 kB + 256 768 kB + 512 3,072 kB + 1,024 12,288 kB + 2,048 49,152 kB + 4,096 196,608 kB + 8,192 786,432 kB + 16,384 3,145,728 kB */ +#define DIM 128 + +int A[DIM][DIM]; +int B[DIM][DIM]; +int C[DIM][DIM]; + +int +main (void) +{ + int i, j, k; + + /* Initialize the matrices. */ + for (i = 0; i < DIM; i++) + for (j = 0; j < DIM; j++) + { + A[i][j] = i; + B[i][j] = j; + C[i][j] = 0; + } + + /* Multiply matrices. */ + for (i = 0; i < DIM; i++) + for (j = 0; j < DIM; j++) + for (k = 0; k < DIM; k++) + C[i][j] += A[i][k] * B[k][j]; + + /* Done. */ + exit (C[DIM - 1][DIM - 1]); +} diff --git a/examples/mcat.c b/examples/mcat.c new file mode 100644 index 0000000..7b39760 --- /dev/null +++ b/examples/mcat.c @@ -0,0 +1,45 @@ +/* mcat.c + + Prints files specified on command line to the console, using + mmap. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + int i; + + for (i = 1; i < argc; i++) + { + int fd; + mapid_t map; + void *data = (void *) 0x10000000; + int size; + + /* Open input file. */ + fd = open (argv[i]); + if (fd < 0) + { + printf ("%s: open failed\n", argv[i]); + return EXIT_FAILURE; + } + size = filesize (fd); + + /* Map files. */ + map = mmap (fd, data); + if (map == MAP_FAILED) + { + printf ("%s: mmap failed\n", argv[i]); + return EXIT_FAILURE; + } + + /* Write file to console. */ + write (STDOUT_FILENO, data, size); + + /* Unmap files (optional). */ + munmap (map); + } + return EXIT_SUCCESS; +} diff --git a/examples/mcp.c b/examples/mcp.c new file mode 100644 index 0000000..6091dc8 --- /dev/null +++ b/examples/mcp.c @@ -0,0 +1,68 @@ +/* mcp.c + + Copies one file to another, using mmap. */ + +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + int in_fd, out_fd; + mapid_t in_map, out_map; + void *in_data = (void *) 0x10000000; + void *out_data = (void *) 0x20000000; + int size; + + if (argc != 3) + { + printf ("usage: cp OLD NEW\n"); + return EXIT_FAILURE; + } + + /* Open input file. */ + in_fd = open (argv[1]); + if (in_fd < 0) + { + printf ("%s: open failed\n", argv[1]); + return EXIT_FAILURE; + } + size = filesize (in_fd); + + /* Create and open output file. */ + if (!create (argv[2], size)) + { + printf ("%s: create failed\n", argv[2]); + return EXIT_FAILURE; + } + out_fd = open (argv[2]); + if (out_fd < 0) + { + printf ("%s: open failed\n", argv[2]); + return EXIT_FAILURE; + } + + /* Map files. */ + in_map = mmap (in_fd, in_data); + if (in_map == MAP_FAILED) + { + printf ("%s: mmap failed\n", argv[1]); + return EXIT_FAILURE; + } + out_map = mmap (out_fd, out_data); + if (out_map == MAP_FAILED) + { + printf ("%s: mmap failed\n", argv[2]); + return EXIT_FAILURE; + } + + /* Copy files. */ + memcpy (out_data, in_data, size); + + /* Unmap files (optional). */ + munmap (in_map); + munmap (out_map); + + return EXIT_SUCCESS; +} diff --git a/examples/mkdir.c b/examples/mkdir.c new file mode 100644 index 0000000..7ddbc3f --- /dev/null +++ b/examples/mkdir.c @@ -0,0 +1,24 @@ +/* mkdir.c + + Creates a directory. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + if (argc != 2) + { + printf ("usage: %s DIRECTORY\n", argv[0]); + return EXIT_FAILURE; + } + + if (!mkdir (argv[1])) + { + printf ("%s: mkdir failed\n", argv[1]); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/examples/pwd.c b/examples/pwd.c new file mode 100644 index 0000000..d2305cf --- /dev/null +++ b/examples/pwd.c @@ -0,0 +1,152 @@ +/* pwd.c + + Prints the absolute name of the present working directory. */ + +#include +#include +#include +#include + +static bool getcwd (char *cwd, size_t cwd_size); + +int +main (void) +{ + char cwd[128]; + if (getcwd (cwd, sizeof cwd)) + { + printf ("%s\n", cwd); + return EXIT_SUCCESS; + } + else + { + printf ("error\n"); + return EXIT_FAILURE; + } +} + +/* Stores the inode number for FILE_NAME in *INUM. + Returns true if successful, false if the file could not be + opened. */ +static bool +get_inumber (const char *file_name, int *inum) +{ + int fd = open (file_name); + if (fd >= 0) + { + *inum = inumber (fd); + close (fd); + return true; + } + else + return false; +} + +/* Prepends PREFIX to the characters stored in the final *DST_LEN + bytes of the DST_SIZE-byte buffer that starts at DST. + Returns true if successful, false if adding that many + characters, plus a null terminator, would overflow the buffer. + (No null terminator is actually added or depended upon, but + its space is accounted for.) */ +static bool +prepend (const char *prefix, + char *dst, size_t *dst_len, size_t dst_size) +{ + size_t prefix_len = strlen (prefix); + if (prefix_len + *dst_len + 1 <= dst_size) + { + *dst_len += prefix_len; + memcpy ((dst + dst_size) - *dst_len, prefix, prefix_len); + return true; + } + else + return false; +} + +/* Stores the current working directory, as a null-terminated + string, in the CWD_SIZE bytes in CWD. + Returns true if successful, false on error. Errors include + system errors, directory trees deeper than MAX_LEVEL levels, + and insufficient space in CWD. */ +static bool +getcwd (char *cwd, size_t cwd_size) +{ + size_t cwd_len = 0; + +#define MAX_LEVEL 20 + char name[MAX_LEVEL * 3 + 1 + READDIR_MAX_LEN + 1]; + char *namep; + + int child_inum; + + /* Make sure there's enough space for at least "/". */ + if (cwd_size < 2) + return false; + + /* Get inumber for current directory. */ + if (!get_inumber (".", &child_inum)) + return false; + + namep = name; + for (;;) + { + int parent_inum, parent_fd; + + /* Compose "../../../..", etc., in NAME. */ + if ((namep - name) > MAX_LEVEL * 3) + return false; + *namep++ = '.'; + *namep++ = '.'; + *namep = '\0'; + + /* Open directory. */ + parent_fd = open (name); + if (parent_fd < 0) + return false; + *namep++ = '/'; + + /* If parent and child have the same inumber, + then we've arrived at the root. */ + parent_inum = inumber (parent_fd); + if (parent_inum == child_inum) + break; + + /* Find name of file in parent directory with the child's + inumber. */ + for (;;) + { + int test_inum; + if (!readdir (parent_fd, namep) || !get_inumber (name, &test_inum)) + { + close (parent_fd); + return false; + } + if (test_inum == child_inum) + break; + } + close (parent_fd); + + /* Prepend "/name" to CWD. */ + if (!prepend (namep - 1, cwd, &cwd_len, cwd_size)) + return false; + + /* Move up. */ + child_inum = parent_inum; + } + + /* Finalize CWD. */ + if (cwd_len > 0) + { + /* Move the string to the beginning of CWD, + and null-terminate it. */ + memmove (cwd, (cwd + cwd_size) - cwd_len, cwd_len); + cwd[cwd_len] = '\0'; + } + else + { + /* Special case for the root. */ + strlcpy (cwd, "/", cwd_size); + } + + return true; +} diff --git a/examples/recursor.c b/examples/recursor.c new file mode 100644 index 0000000..79c784a --- /dev/null +++ b/examples/recursor.c @@ -0,0 +1,34 @@ +#include +#include +#include + +int +main (int argc, char *argv[]) +{ + char buffer[128]; + pid_t pid; + int retval = 0; + + if (argc != 4) + { + printf ("usage: recursor \n"); + exit (1); + } + + /* Print args. */ + printf ("%s %s %s %s\n", argv[0], argv[1], argv[2], argv[3]); + + /* Execute child and wait for it to finish if requested. */ + if (atoi (argv[2]) != 0) + { + snprintf (buffer, sizeof buffer, + "recursor %s %d %s", argv[1], atoi (argv[2]) - 1, argv[3]); + pid = exec (buffer); + if (atoi (argv[3])) + retval = wait (pid); + } + + /* Done. */ + printf ("%s %s: dying, retval=%d\n", argv[1], argv[2], retval); + exit (retval); +} diff --git a/examples/rm.c b/examples/rm.c new file mode 100644 index 0000000..0db7f7b --- /dev/null +++ b/examples/rm.c @@ -0,0 +1,21 @@ +/* rm.c + + Removes files specified on command line. */ + +#include +#include + +int +main (int argc, char *argv[]) +{ + bool success = true; + int i; + + for (i = 1; i < argc; i++) + if (!remove (argv[i])) + { + printf ("%s: remove failed\n", argv[i]); + success = false; + } + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/examples/shell.c b/examples/shell.c new file mode 100644 index 0000000..93641b4 --- /dev/null +++ b/examples/shell.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include + +static void read_line (char line[], size_t); +static bool backspace (char **pos, char line[]); + +int +main (void) +{ + printf ("Shell starting...\n"); + for (;;) + { + char command[80]; + + /* Read command. */ + printf ("--"); + read_line (command, sizeof command); + + /* Execute command. */ + if (!strcmp (command, "exit")) + break; + else if (!memcmp (command, "cd ", 3)) + { + if (!chdir (command + 3)) + printf ("\"%s\": chdir failed\n", command + 3); + } + else if (command[0] == '\0') + { + /* Empty command. */ + } + else + { + pid_t pid = exec (command); + if (pid != PID_ERROR) + printf ("\"%s\": exit code %d\n", command, wait (pid)); + else + printf ("exec failed\n"); + } + } + + printf ("Shell exiting."); + return EXIT_SUCCESS; +} + +/* Reads a line of input from the user into LINE, which has room + for SIZE bytes. Handles backspace and Ctrl+U in the ways + expected by Unix users. On return, LINE will always be + null-terminated and will not end in a new-line character. */ +static void +read_line (char line[], size_t size) +{ + char *pos = line; + for (;;) + { + char c; + read (STDIN_FILENO, &c, 1); + + switch (c) + { + case '\r': + *pos = '\0'; + putchar ('\n'); + return; + + case '\b': + backspace (&pos, line); + break; + + case ('U' - 'A') + 1: /* Ctrl+U. */ + while (backspace (&pos, line)) + continue; + break; + + default: + /* Add character to line. */ + if (pos < line + size - 1) + { + putchar (c); + *pos++ = c; + } + break; + } + } +} + +/* If *POS is past the beginning of LINE, backs up one character + position. Returns true if successful, false if nothing was + done. */ +static bool +backspace (char **pos, char line[]) +{ + if (*pos > line) + { + /* Back up cursor, overwrite character, back up + again. */ + printf ("\b \b"); + (*pos)--; + return true; + } + else + return false; +} diff --git a/filesys/Make.vars b/filesys/Make.vars new file mode 100644 index 0000000..d8050cd --- /dev/null +++ b/filesys/Make.vars @@ -0,0 +1,13 @@ +# -*- makefile -*- + +os.dsk: DEFINES = -DUSERPROG -DFILESYS +KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys +TEST_SUBDIRS = tests/userprog tests/filesys/base tests/filesys/extended +GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.no-vm +SIMULATOR = --qemu + +# Uncomment the lines below to enable VM. +#os.dsk: DEFINES += -DVM +#KERNEL_SUBDIRS += vm +#TEST_SUBDIRS += tests/vm +#GRADING_FILE = $(SRCDIR)/tests/filesys/Grading.with-vm diff --git a/filesys/Makefile b/filesys/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/filesys/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/filesys/directory.c b/filesys/directory.c new file mode 100644 index 0000000..e46a8d9 --- /dev/null +++ b/filesys/directory.c @@ -0,0 +1,216 @@ +#include "filesys/directory.h" +#include +#include +#include +#include "filesys/filesys.h" +#include "filesys/inode.h" +#include "threads/malloc.h" + +/* A directory. */ +struct dir { + struct inode *inode; /* Backing store. */ + off_t pos; /* Current position. */ +}; + +/* A single directory entry. */ +struct dir_entry { + disk_sector_t inode_sector; /* Sector number of header. */ + char name[NAME_MAX + 1]; /* Null terminated file name. */ + bool in_use; /* In use or free? */ +}; + +/* Creates a directory with space for ENTRY_CNT entries in the + * given SECTOR. Returns true if successful, false on failure. */ +bool +dir_create (disk_sector_t sector, size_t entry_cnt) { + return inode_create (sector, entry_cnt * sizeof (struct dir_entry)); +} + +/* Opens and returns the directory for the given INODE, of which + * it takes ownership. Returns a null pointer on failure. */ +struct dir * +dir_open (struct inode *inode) { + struct dir *dir = calloc (1, sizeof *dir); + if (inode != NULL && dir != NULL) { + dir->inode = inode; + dir->pos = 0; + return dir; + } else { + inode_close (inode); + free (dir); + return NULL; + } +} + +/* Opens the root directory and returns a directory for it. + * Return true if successful, false on failure. */ +struct dir * +dir_open_root (void) { + return dir_open (inode_open (ROOT_DIR_SECTOR)); +} + +/* Opens and returns a new directory for the same inode as DIR. + * Returns a null pointer on failure. */ +struct dir * +dir_reopen (struct dir *dir) { + return dir_open (inode_reopen (dir->inode)); +} + +/* Destroys DIR and frees associated resources. */ +void +dir_close (struct dir *dir) { + if (dir != NULL) { + inode_close (dir->inode); + free (dir); + } +} + +/* Returns the inode encapsulated by DIR. */ +struct inode * +dir_get_inode (struct dir *dir) { + return dir->inode; +} + +/* Searches DIR for a file with the given NAME. + * If successful, returns true, sets *EP to the directory entry + * if EP is non-null, and sets *OFSP to the byte offset of the + * directory entry if OFSP is non-null. + * otherwise, returns false and ignores EP and OFSP. */ +static bool +lookup (const struct dir *dir, const char *name, + struct dir_entry *ep, off_t *ofsp) { + struct dir_entry e; + size_t ofs; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + ofs += sizeof e) + if (e.in_use && !strcmp (name, e.name)) { + if (ep != NULL) + *ep = e; + if (ofsp != NULL) + *ofsp = ofs; + return true; + } + return false; +} + +/* Searches DIR for a file with the given NAME + * and returns true if one exists, false otherwise. + * On success, sets *INODE to an inode for the file, otherwise to + * a null pointer. The caller must close *INODE. */ +bool +dir_lookup (const struct dir *dir, const char *name, + struct inode **inode) { + struct dir_entry e; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + if (lookup (dir, name, &e, NULL)) + *inode = inode_open (e.inode_sector); + else + *inode = NULL; + + return *inode != NULL; +} + +/* Adds a file named NAME to DIR, which must not already contain a + * file by that name. The file's inode is in sector + * INODE_SECTOR. + * Returns true if successful, false on failure. + * Fails if NAME is invalid (i.e. too long) or a disk or memory + * error occurs. */ +bool +dir_add (struct dir *dir, const char *name, disk_sector_t inode_sector) { + struct dir_entry e; + off_t ofs; + bool success = false; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + /* Check NAME for validity. */ + if (*name == '\0' || strlen (name) > NAME_MAX) + return false; + + /* Check that NAME is not in use. */ + if (lookup (dir, name, NULL, NULL)) + goto done; + + /* Set OFS to offset of free slot. + * If there are no free slots, then it will be set to the + * current end-of-file. + + * inode_read_at() will only return a short read at end of file. + * Otherwise, we'd need to verify that we didn't get a short + * read due to something intermittent such as low memory. */ + for (ofs = 0; inode_read_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + ofs += sizeof e) + if (!e.in_use) + break; + + /* Write slot. */ + e.in_use = true; + strlcpy (e.name, name, sizeof e.name); + e.inode_sector = inode_sector; + success = inode_write_at (dir->inode, &e, sizeof e, ofs) == sizeof e; + +done: + return success; +} + +/* Removes any entry for NAME in DIR. + * Returns true if successful, false on failure, + * which occurs only if there is no file with the given NAME. */ +bool +dir_remove (struct dir *dir, const char *name) { + struct dir_entry e; + struct inode *inode = NULL; + bool success = false; + off_t ofs; + + ASSERT (dir != NULL); + ASSERT (name != NULL); + + /* Find directory entry. */ + if (!lookup (dir, name, &e, &ofs)) + goto done; + + /* Open inode. */ + inode = inode_open (e.inode_sector); + if (inode == NULL) + goto done; + + /* Erase directory entry. */ + e.in_use = false; + if (inode_write_at (dir->inode, &e, sizeof e, ofs) != sizeof e) + goto done; + + /* Remove inode. */ + inode_remove (inode); + success = true; + +done: + inode_close (inode); + return success; +} + +/* Reads the next directory entry in DIR and stores the name in + * NAME. Returns true if successful, false if the directory + * contains no more entries. */ +bool +dir_readdir (struct dir *dir, char name[NAME_MAX + 1]) { + struct dir_entry e; + + while (inode_read_at (dir->inode, &e, sizeof e, dir->pos) == sizeof e) { + dir->pos += sizeof e; + if (e.in_use) { + strlcpy (name, e.name, NAME_MAX + 1); + return true; + } + } + return false; +} diff --git a/filesys/file.c b/filesys/file.c new file mode 100644 index 0000000..b4257a0 --- /dev/null +++ b/filesys/file.c @@ -0,0 +1,148 @@ +#include "filesys/file.h" +#include +#include "filesys/inode.h" +#include "threads/malloc.h" + +/* An open file. */ +struct file { + struct inode *inode; /* File's inode. */ + off_t pos; /* Current position. */ + bool deny_write; /* Has file_deny_write() been called? */ +}; + +/* Opens a file for the given INODE, of which it takes ownership, + * and returns the new file. Returns a null pointer if an + * allocation fails or if INODE is null. */ +struct file * +file_open (struct inode *inode) { + struct file *file = calloc (1, sizeof *file); + if (inode != NULL && file != NULL) { + file->inode = inode; + file->pos = 0; + file->deny_write = false; + return file; + } else { + inode_close (inode); + free (file); + return NULL; + } +} + +/* Opens and returns a new file for the same inode as FILE. + * Returns a null pointer if unsuccessful. */ +struct file * +file_reopen (struct file *file) { + return file_open (inode_reopen (file->inode)); +} + +/* Closes FILE. */ +void +file_close (struct file *file) { + if (file != NULL) { + file_allow_write (file); + inode_close (file->inode); + free (file); + } +} + +/* Returns the inode encapsulated by FILE. */ +struct inode * +file_get_inode (struct file *file) { + return file->inode; +} + +/* Reads SIZE bytes from FILE into BUFFER, + * starting at the file's current position. + * Returns the number of bytes actually read, + * which may be less than SIZE if end of file is reached. + * Advances FILE's position by the number of bytes read. */ +off_t +file_read (struct file *file, void *buffer, off_t size) { + off_t bytes_read = inode_read_at (file->inode, buffer, size, file->pos); + file->pos += bytes_read; + return bytes_read; +} + +/* Reads SIZE bytes from FILE into BUFFER, + * starting at offset FILE_OFS in the file. + * Returns the number of bytes actually read, + * which may be less than SIZE if end of file is reached. + * The file's current position is unaffected. */ +off_t +file_read_at (struct file *file, void *buffer, off_t size, off_t file_ofs) { + return inode_read_at (file->inode, buffer, size, file_ofs); +} + +/* Writes SIZE bytes from BUFFER into FILE, + * starting at the file's current position. + * Returns the number of bytes actually written, + * which may be less than SIZE if end of file is reached. + * (Normally we'd grow the file in that case, but file growth is + * not yet implemented.) + * Advances FILE's position by the number of bytes read. */ +off_t +file_write (struct file *file, const void *buffer, off_t size) { + off_t bytes_written = inode_write_at (file->inode, buffer, size, file->pos); + file->pos += bytes_written; + return bytes_written; +} + +/* Writes SIZE bytes from BUFFER into FILE, + * starting at offset FILE_OFS in the file. + * Returns the number of bytes actually written, + * which may be less than SIZE if end of file is reached. + * (Normally we'd grow the file in that case, but file growth is + * not yet implemented.) + * The file's current position is unaffected. */ +off_t +file_write_at (struct file *file, const void *buffer, off_t size, + off_t file_ofs) { + return inode_write_at (file->inode, buffer, size, file_ofs); +} + +/* Prevents write operations on FILE's underlying inode + * until file_allow_write() is called or FILE is closed. */ +void +file_deny_write (struct file *file) { + ASSERT (file != NULL); + if (!file->deny_write) { + file->deny_write = true; + inode_deny_write (file->inode); + } +} + +/* Re-enables write operations on FILE's underlying inode. + * (Writes might still be denied by some other file that has the + * same inode open.) */ +void +file_allow_write (struct file *file) { + ASSERT (file != NULL); + if (file->deny_write) { + file->deny_write = false; + inode_allow_write (file->inode); + } +} + +/* Returns the size of FILE in bytes. */ +off_t +file_length (struct file *file) { + ASSERT (file != NULL); + return inode_length (file->inode); +} + +/* Sets the current position in FILE to NEW_POS bytes from the + * start of the file. */ +void +file_seek (struct file *file, off_t new_pos) { + ASSERT (file != NULL); + ASSERT (new_pos >= 0); + file->pos = new_pos; +} + +/* Returns the current position in FILE as a byte offset from the + * start of the file. */ +off_t +file_tell (struct file *file) { + ASSERT (file != NULL); + return file->pos; +} diff --git a/filesys/filesys.c b/filesys/filesys.c new file mode 100644 index 0000000..b22e8da --- /dev/null +++ b/filesys/filesys.c @@ -0,0 +1,98 @@ +#include "filesys/filesys.h" +#include +#include +#include +#include "filesys/file.h" +#include "filesys/free-map.h" +#include "filesys/inode.h" +#include "filesys/directory.h" +#include "devices/disk.h" + +/* The disk that contains the file system. */ +struct disk *filesys_disk; + +static void do_format (void); + +/* Initializes the file system module. + * If FORMAT is true, reformats the file system. */ +void +filesys_init (bool format) { + filesys_disk = disk_get (0, 1); + if (filesys_disk == NULL) + PANIC ("hd0:1 (hdb) not present, file system initialization failed"); + + inode_init (); + free_map_init (); + + if (format) + do_format (); + + free_map_open (); +} + +/* Shuts down the file system module, writing any unwritten data + * to disk. */ +void +filesys_done (void) { + free_map_close (); +} + +/* Creates a file named NAME with the given INITIAL_SIZE. + * Returns true if successful, false otherwise. + * Fails if a file named NAME already exists, + * or if internal memory allocation fails. */ +bool +filesys_create (const char *name, off_t initial_size) { + disk_sector_t inode_sector = 0; + struct dir *dir = dir_open_root (); + bool success = (dir != NULL + && free_map_allocate (1, &inode_sector) + && inode_create (inode_sector, initial_size) + && dir_add (dir, name, inode_sector)); + if (!success && inode_sector != 0) + free_map_release (inode_sector, 1); + dir_close (dir); + + return success; +} + +/* Opens the file with the given NAME. + * Returns the new file if successful or a null pointer + * otherwise. + * Fails if no file named NAME exists, + * or if an internal memory allocation fails. */ +struct file * +filesys_open (const char *name) { + struct dir *dir = dir_open_root (); + struct inode *inode = NULL; + + if (dir != NULL) + dir_lookup (dir, name, &inode); + dir_close (dir); + + return file_open (inode); +} + +/* Deletes the file named NAME. + * Returns true if successful, false on failure. + * Fails if no file named NAME exists, + * or if an internal memory allocation fails. */ +bool +filesys_remove (const char *name) { + struct dir *dir = dir_open_root (); + bool success = dir != NULL && dir_remove (dir, name); + dir_close (dir); + + return success; +} + +/* Formats the file system. */ +static void +do_format (void) { + printf ("Formatting file system..."); + free_map_create (); + if (!dir_create (ROOT_DIR_SECTOR, 16)) + PANIC ("root directory creation failed"); + free_map_close (); + printf ("done.\n"); +} diff --git a/filesys/free-map.c b/filesys/free-map.c new file mode 100644 index 0000000..4d181ab --- /dev/null +++ b/filesys/free-map.c @@ -0,0 +1,77 @@ +#include "filesys/free-map.h" +#include +#include +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "filesys/inode.h" + +static struct file *free_map_file; /* Free map file. */ +static struct bitmap *free_map; /* Free map, one bit per disk sector. */ + +/* Initializes the free map. */ +void +free_map_init (void) { + free_map = bitmap_create (disk_size (filesys_disk)); + if (free_map == NULL) + PANIC ("bitmap creation failed--disk is too large"); + bitmap_mark (free_map, FREE_MAP_SECTOR); + bitmap_mark (free_map, ROOT_DIR_SECTOR); +} + +/* Allocates CNT consecutive sectors from the free map and stores + * the first into *SECTORP. + * Returns true if successful, false if all sectors were + * available. */ +bool +free_map_allocate (size_t cnt, disk_sector_t *sectorp) { + disk_sector_t sector = bitmap_scan_and_flip (free_map, 0, cnt, false); + if (sector != BITMAP_ERROR + && free_map_file != NULL + && !bitmap_write (free_map, free_map_file)) { + bitmap_set_multiple (free_map, sector, cnt, false); + sector = BITMAP_ERROR; + } + if (sector != BITMAP_ERROR) + *sectorp = sector; + return sector != BITMAP_ERROR; +} + +/* Makes CNT sectors starting at SECTOR available for use. */ +void +free_map_release (disk_sector_t sector, size_t cnt) { + ASSERT (bitmap_all (free_map, sector, cnt)); + bitmap_set_multiple (free_map, sector, cnt, false); + bitmap_write (free_map, free_map_file); +} + +/* Opens the free map file and reads it from disk. */ +void +free_map_open (void) { + free_map_file = file_open (inode_open (FREE_MAP_SECTOR)); + if (free_map_file == NULL) + PANIC ("can't open free map"); + if (!bitmap_read (free_map, free_map_file)) + PANIC ("can't read free map"); +} + +/* Writes the free map to disk and closes the free map file. */ +void +free_map_close (void) { + file_close (free_map_file); +} + +/* Creates a new free map file on disk and writes the free map to + * it. */ +void +free_map_create (void) { + /* Create inode. */ + if (!inode_create (FREE_MAP_SECTOR, bitmap_file_size (free_map))) + PANIC ("free map creation failed"); + + /* Write bitmap to file. */ + free_map_file = file_open (inode_open (FREE_MAP_SECTOR)); + if (free_map_file == NULL) + PANIC ("can't open free map"); + if (!bitmap_write (free_map, free_map_file)) + PANIC ("can't write free map"); +} diff --git a/filesys/fsutil.c b/filesys/fsutil.c new file mode 100644 index 0000000..1516268 --- /dev/null +++ b/filesys/fsutil.c @@ -0,0 +1,189 @@ +#include "filesys/fsutil.h" +#include +#include +#include +#include +#include "filesys/directory.h" +#include "filesys/file.h" +#include "filesys/filesys.h" +#include "devices/disk.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/vaddr.h" + +/* List files in the root directory. */ +void +fsutil_ls (char **argv UNUSED) { + struct dir *dir; + char name[NAME_MAX + 1]; + + printf ("Files in the root directory:\n"); + dir = dir_open_root (); + if (dir == NULL) + PANIC ("root dir open failed"); + while (dir_readdir (dir, name)) + printf ("%s\n", name); + printf ("End of listing.\n"); +} + +/* Prints the contents of file ARGV[1] to the system console as + * hex and ASCII. */ +void +fsutil_cat (char **argv) { + const char *file_name = argv[1]; + + struct file *file; + char *buffer; + + printf ("Printing '%s' to the console...\n", file_name); + file = filesys_open (file_name); + if (file == NULL) + PANIC ("%s: open failed", file_name); + buffer = palloc_get_page (PAL_ASSERT); + for (;;) { + off_t pos = file_tell (file); + off_t n = file_read (file, buffer, PGSIZE); + if (n == 0) + break; + + hex_dump (pos, buffer, n, true); + } + palloc_free_page (buffer); + file_close (file); +} + +/* Deletes file ARGV[1]. */ +void +fsutil_rm (char **argv) { + const char *file_name = argv[1]; + + printf ("Deleting '%s'...\n", file_name); + if (!filesys_remove (file_name)) + PANIC ("%s: delete failed\n", file_name); +} + +/* Copies from the "scratch" disk, hdc or hd1:0 to file ARGV[1] + * in the file system. + * + * The current sector on the scratch disk must begin with the + * string "PUT\0" followed by a 32-bit little-endian integer + * indicating the file size in bytes. Subsequent sectors hold + * the file content. + * + * The first call to this function will read starting at the + * beginning of the scratch disk. Later calls advance across the + * disk. This disk position is independent of that used for + * fsutil_get(), so all `put's should precede all `get's. */ +void +fsutil_put (char **argv) { + static disk_sector_t sector = 0; + + const char *file_name = argv[1]; + struct disk *src; + struct file *dst; + off_t size; + void *buffer; + + printf ("Putting '%s' into the file system...\n", file_name); + + /* Allocate buffer. */ + buffer = malloc (DISK_SECTOR_SIZE); + if (buffer == NULL) + PANIC ("couldn't allocate buffer"); + + /* Open source disk and read file size. */ + src = disk_get (1, 0); + if (src == NULL) + PANIC ("couldn't open source disk (hdc or hd1:0)"); + + /* Read file size. */ + disk_read (src, sector++, buffer); + if (memcmp (buffer, "PUT", 4)) + PANIC ("%s: missing PUT signature on scratch disk", file_name); + size = ((int32_t *) buffer)[1]; + if (size < 0) + PANIC ("%s: invalid file size %d", file_name, size); + + /* Create destination file. */ + if (!filesys_create (file_name, size)) + PANIC ("%s: create failed", file_name); + dst = filesys_open (file_name); + if (dst == NULL) + PANIC ("%s: open failed", file_name); + + /* Do copy. */ + while (size > 0) { + int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size; + disk_read (src, sector++, buffer); + if (file_write (dst, buffer, chunk_size) != chunk_size) + PANIC ("%s: write failed with %"PROTd" bytes unwritten", + file_name, size); + size -= chunk_size; + } + + /* Finish up. */ + file_close (dst); + free (buffer); +} + +/* Copies file FILE_NAME from the file system to the scratch disk. + * + * The current sector on the scratch disk will receive "GET\0" + * followed by the file's size in bytes as a 32-bit, + * little-endian integer. Subsequent sectors receive the file's + * data. + * + * The first call to this function will write starting at the + * beginning of the scratch disk. Later calls advance across the + * disk. This disk position is independent of that used for + * fsutil_put(), so all `put's should precede all `get's. */ +void +fsutil_get (char **argv) { + static disk_sector_t sector = 0; + + const char *file_name = argv[1]; + void *buffer; + struct file *src; + struct disk *dst; + off_t size; + + printf ("Getting '%s' from the file system...\n", file_name); + + /* Allocate buffer. */ + buffer = malloc (DISK_SECTOR_SIZE); + if (buffer == NULL) + PANIC ("couldn't allocate buffer"); + + /* Open source file. */ + src = filesys_open (file_name); + if (src == NULL) + PANIC ("%s: open failed", file_name); + size = file_length (src); + + /* Open target disk. */ + dst = disk_get (1, 0); + if (dst == NULL) + PANIC ("couldn't open target disk (hdc or hd1:0)"); + + /* Write size to sector 0. */ + memset (buffer, 0, DISK_SECTOR_SIZE); + memcpy (buffer, "GET", 4); + ((int32_t *) buffer)[1] = size; + disk_write (dst, sector++, buffer); + + /* Do copy. */ + while (size > 0) { + int chunk_size = size > DISK_SECTOR_SIZE ? DISK_SECTOR_SIZE : size; + if (sector >= disk_size (dst)) + PANIC ("%s: out of space on scratch disk", file_name); + if (file_read (src, buffer, chunk_size) != chunk_size) + PANIC ("%s: read failed with %"PROTd" bytes unread", file_name, size); + memset (buffer + chunk_size, 0, DISK_SECTOR_SIZE - chunk_size); + disk_write (dst, sector++, buffer); + size -= chunk_size; + } + + /* Finish up. */ + file_close (src); + free (buffer); +} diff --git a/filesys/inode.c b/filesys/inode.c new file mode 100644 index 0000000..d88dcbd --- /dev/null +++ b/filesys/inode.c @@ -0,0 +1,313 @@ +#include "filesys/inode.h" +#include +#include +#include +#include +#include "filesys/filesys.h" +#include "filesys/free-map.h" +#include "threads/malloc.h" + +/* Identifies an inode. */ +#define INODE_MAGIC 0x494e4f44 + +/* On-disk inode. + * Must be exactly DISK_SECTOR_SIZE bytes long. */ +struct inode_disk { + disk_sector_t start; /* First data sector. */ + off_t length; /* File size in bytes. */ + unsigned magic; /* Magic number. */ + uint32_t unused[125]; /* Not used. */ +}; + +/* Returns the number of sectors to allocate for an inode SIZE + * bytes long. */ +static inline size_t +bytes_to_sectors (off_t size) { + return DIV_ROUND_UP (size, DISK_SECTOR_SIZE); +} + +/* In-memory inode. */ +struct inode { + struct list_elem elem; /* Element in inode list. */ + disk_sector_t sector; /* Sector number of disk location. */ + int open_cnt; /* Number of openers. */ + bool removed; /* True if deleted, false otherwise. */ + int deny_write_cnt; /* 0: writes ok, >0: deny writes. */ + struct inode_disk data; /* Inode content. */ +}; + +/* Returns the disk sector that contains byte offset POS within + * INODE. + * Returns -1 if INODE does not contain data for a byte at offset + * POS. */ +static disk_sector_t +byte_to_sector (const struct inode *inode, off_t pos) { + ASSERT (inode != NULL); + if (pos < inode->data.length) + return inode->data.start + pos / DISK_SECTOR_SIZE; + else + return -1; +} + +/* List of open inodes, so that opening a single inode twice + * returns the same `struct inode'. */ +static struct list open_inodes; + +/* Initializes the inode module. */ +void +inode_init (void) { + list_init (&open_inodes); +} + +/* Initializes an inode with LENGTH bytes of data and + * writes the new inode to sector SECTOR on the file system + * disk. + * Returns true if successful. + * Returns false if memory or disk allocation fails. */ +bool +inode_create (disk_sector_t sector, off_t length) { + struct inode_disk *disk_inode = NULL; + bool success = false; + + ASSERT (length >= 0); + + /* If this assertion fails, the inode structure is not exactly + * one sector in size, and you should fix that. */ + ASSERT (sizeof *disk_inode == DISK_SECTOR_SIZE); + + disk_inode = calloc (1, sizeof *disk_inode); + if (disk_inode != NULL) { + size_t sectors = bytes_to_sectors (length); + disk_inode->length = length; + disk_inode->magic = INODE_MAGIC; + if (free_map_allocate (sectors, &disk_inode->start)) { + disk_write (filesys_disk, sector, disk_inode); + if (sectors > 0) { + static char zeros[DISK_SECTOR_SIZE]; + size_t i; + + for (i = 0; i < sectors; i++) + disk_write (filesys_disk, disk_inode->start + i, zeros); + } + success = true; + } + free (disk_inode); + } + return success; +} + +/* Reads an inode from SECTOR + * and returns a `struct inode' that contains it. + * Returns a null pointer if memory allocation fails. */ +struct inode * +inode_open (disk_sector_t sector) { + struct list_elem *e; + struct inode *inode; + + /* Check whether this inode is already open. */ + for (e = list_begin (&open_inodes); e != list_end (&open_inodes); + e = list_next (e)) { + inode = list_entry (e, struct inode, elem); + if (inode->sector == sector) { + inode_reopen (inode); + return inode; + } + } + + /* Allocate memory. */ + inode = malloc (sizeof *inode); + if (inode == NULL) + return NULL; + + /* Initialize. */ + list_push_front (&open_inodes, &inode->elem); + inode->sector = sector; + inode->open_cnt = 1; + inode->deny_write_cnt = 0; + inode->removed = false; + disk_read (filesys_disk, inode->sector, &inode->data); + return inode; +} + +/* Reopens and returns INODE. */ +struct inode * +inode_reopen (struct inode *inode) { + if (inode != NULL) + inode->open_cnt++; + return inode; +} + +/* Returns INODE's inode number. */ +disk_sector_t +inode_get_inumber (const struct inode *inode) { + return inode->sector; +} + +/* Closes INODE and writes it to disk. + * If this was the last reference to INODE, frees its memory. + * If INODE was also a removed inode, frees its blocks. */ +void +inode_close (struct inode *inode) { + /* Ignore null pointer. */ + if (inode == NULL) + return; + + /* Release resources if this was the last opener. */ + if (--inode->open_cnt == 0) { + /* Remove from inode list and release lock. */ + list_remove (&inode->elem); + + /* Deallocate blocks if removed. */ + if (inode->removed) { + free_map_release (inode->sector, 1); + free_map_release (inode->data.start, + bytes_to_sectors (inode->data.length)); + } + + free (inode); + } +} + +/* Marks INODE to be deleted when it is closed by the last caller who + * has it open. */ +void +inode_remove (struct inode *inode) { + ASSERT (inode != NULL); + inode->removed = true; +} + +/* Reads SIZE bytes from INODE into BUFFER, starting at position OFFSET. + * Returns the number of bytes actually read, which may be less + * than SIZE if an error occurs or end of file is reached. */ +off_t +inode_read_at (struct inode *inode, void *buffer_, off_t size, off_t offset) { + uint8_t *buffer = buffer_; + off_t bytes_read = 0; + uint8_t *bounce = NULL; + + while (size > 0) { + /* Disk sector to read, starting byte offset within sector. */ + disk_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % DISK_SECTOR_SIZE; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; + int sector_left = DISK_SECTOR_SIZE - sector_ofs; + int min_left = inode_left < sector_left ? inode_left : sector_left; + + /* Number of bytes to actually copy out of this sector. */ + int chunk_size = size < min_left ? size : min_left; + if (chunk_size <= 0) + break; + + if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) { + /* Read full sector directly into caller's buffer. */ + disk_read (filesys_disk, sector_idx, buffer + bytes_read); + } else { + /* Read sector into bounce buffer, then partially copy + * into caller's buffer. */ + if (bounce == NULL) { + bounce = malloc (DISK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + disk_read (filesys_disk, sector_idx, bounce); + memcpy (buffer + bytes_read, bounce + sector_ofs, chunk_size); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_read += chunk_size; + } + free (bounce); + + return bytes_read; +} + +/* Writes SIZE bytes from BUFFER into INODE, starting at OFFSET. + * Returns the number of bytes actually written, which may be + * less than SIZE if end of file is reached or an error occurs. + * (Normally a write at end of file would extend the inode, but + * growth is not yet implemented.) */ +off_t +inode_write_at (struct inode *inode, const void *buffer_, off_t size, + off_t offset) { + const uint8_t *buffer = buffer_; + off_t bytes_written = 0; + uint8_t *bounce = NULL; + + if (inode->deny_write_cnt) + return 0; + + while (size > 0) { + /* Sector to write, starting byte offset within sector. */ + disk_sector_t sector_idx = byte_to_sector (inode, offset); + int sector_ofs = offset % DISK_SECTOR_SIZE; + + /* Bytes left in inode, bytes left in sector, lesser of the two. */ + off_t inode_left = inode_length (inode) - offset; + int sector_left = DISK_SECTOR_SIZE - sector_ofs; + int min_left = inode_left < sector_left ? inode_left : sector_left; + + /* Number of bytes to actually write into this sector. */ + int chunk_size = size < min_left ? size : min_left; + if (chunk_size <= 0) + break; + + if (sector_ofs == 0 && chunk_size == DISK_SECTOR_SIZE) { + /* Write full sector directly to disk. */ + disk_write (filesys_disk, sector_idx, buffer + bytes_written); + } else { + /* We need a bounce buffer. */ + if (bounce == NULL) { + bounce = malloc (DISK_SECTOR_SIZE); + if (bounce == NULL) + break; + } + + /* If the sector contains data before or after the chunk + we're writing, then we need to read in the sector + first. Otherwise we start with a sector of all zeros. */ + if (sector_ofs > 0 || chunk_size < sector_left) + disk_read (filesys_disk, sector_idx, bounce); + else + memset (bounce, 0, DISK_SECTOR_SIZE); + memcpy (bounce + sector_ofs, buffer + bytes_written, chunk_size); + disk_write (filesys_disk, sector_idx, bounce); + } + + /* Advance. */ + size -= chunk_size; + offset += chunk_size; + bytes_written += chunk_size; + } + free (bounce); + + return bytes_written; +} + +/* Disables writes to INODE. + May be called at most once per inode opener. */ + void +inode_deny_write (struct inode *inode) +{ + inode->deny_write_cnt++; + ASSERT (inode->deny_write_cnt <= inode->open_cnt); +} + +/* Re-enables writes to INODE. + * Must be called once by each inode opener who has called + * inode_deny_write() on the inode, before closing the inode. */ +void +inode_allow_write (struct inode *inode) { + ASSERT (inode->deny_write_cnt > 0); + ASSERT (inode->deny_write_cnt <= inode->open_cnt); + inode->deny_write_cnt--; +} + +/* Returns the length, in bytes, of INODE's data. */ +off_t +inode_length (const struct inode *inode) { + return inode->data.length; +} diff --git a/filesys/targets.mk b/filesys/targets.mk new file mode 100644 index 0000000..294af60 --- /dev/null +++ b/filesys/targets.mk @@ -0,0 +1,6 @@ +filesys_SRC = filesys/filesys.c # Filesystem core. +filesys_SRC += filesys/free-map.c # Free sector bitmap. +filesys_SRC += filesys/file.c # Files. +filesys_SRC += filesys/directory.c # Directories. +filesys_SRC += filesys/inode.c # File headers. +filesys_SRC += filesys/fsutil.c # Utilities. diff --git a/include/devices/disk.h b/include/devices/disk.h new file mode 100644 index 0000000..bdda51f --- /dev/null +++ b/include/devices/disk.h @@ -0,0 +1,26 @@ +#ifndef DEVICES_DISK_H +#define DEVICES_DISK_H + +#include +#include + +/* Size of a disk sector in bytes. */ +#define DISK_SECTOR_SIZE 512 + +/* Index of a disk sector within a disk. + * Good enough for disks up to 2 TB. */ +typedef uint32_t disk_sector_t; + +/* Format specifier for printf(), e.g.: + * printf ("sector=%"PRDSNu"\n", sector); */ +#define PRDSNu PRIu32 + +void disk_init (void); +void disk_print_stats (void); + +struct disk *disk_get (int chan_no, int dev_no); +disk_sector_t disk_size (struct disk *); +void disk_read (struct disk *, disk_sector_t, void *); +void disk_write (struct disk *, disk_sector_t, const void *); + +#endif /* devices/disk.h */ diff --git a/include/devices/input.h b/include/devices/input.h new file mode 100644 index 0000000..a2f50e9 --- /dev/null +++ b/include/devices/input.h @@ -0,0 +1,12 @@ +#ifndef DEVICES_INPUT_H +#define DEVICES_INPUT_H + +#include +#include + +void input_init (void); +void input_putc (uint8_t); +uint8_t input_getc (void); +bool input_full (void); + +#endif /* devices/input.h */ diff --git a/include/devices/intq.h b/include/devices/intq.h new file mode 100644 index 0000000..2eee3a4 --- /dev/null +++ b/include/devices/intq.h @@ -0,0 +1,42 @@ +#ifndef DEVICES_INTQ_H +#define DEVICES_INTQ_H + +#include "threads/interrupt.h" +#include "threads/synch.h" + +/* An "interrupt queue", a circular buffer shared between + kernel threads and external interrupt handlers. + + Interrupt queue functions can be called from kernel threads or + from external interrupt handlers. Except for intq_init(), + interrupts must be off in either case. + + The interrupt queue has the structure of a "monitor". Locks + and condition variables from threads/synch.h cannot be used in + this case, as they normally would, because they can only + protect kernel threads from one another, not from interrupt + handlers. */ + +/* Queue buffer size, in bytes. */ +#define INTQ_BUFSIZE 64 + +/* A circular queue of bytes. */ +struct intq { + /* Waiting threads. */ + struct lock lock; /* Only one thread may wait at once. */ + struct thread *not_full; /* Thread waiting for not-full condition. */ + struct thread *not_empty; /* Thread waiting for not-empty condition. */ + + /* Queue. */ + uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */ + int head; /* New data is written here. */ + int tail; /* Old data is read here. */ +}; + +void intq_init (struct intq *); +bool intq_empty (const struct intq *); +bool intq_full (const struct intq *); +uint8_t intq_getc (struct intq *); +void intq_putc (struct intq *, uint8_t); + +#endif /* devices/intq.h */ diff --git a/include/devices/kbd.h b/include/devices/kbd.h new file mode 100644 index 0000000..ed9c06b --- /dev/null +++ b/include/devices/kbd.h @@ -0,0 +1,9 @@ +#ifndef DEVICES_KBD_H +#define DEVICES_KBD_H + +#include + +void kbd_init (void); +void kbd_print_stats (void); + +#endif /* devices/kbd.h */ diff --git a/include/devices/serial.h b/include/devices/serial.h new file mode 100644 index 0000000..6e04778 --- /dev/null +++ b/include/devices/serial.h @@ -0,0 +1,11 @@ +#ifndef DEVICES_SERIAL_H +#define DEVICES_SERIAL_H + +#include + +void serial_init_queue (void); +void serial_putc (uint8_t); +void serial_flush (void); +void serial_notify (void); + +#endif /* devices/serial.h */ diff --git a/include/devices/timer.h b/include/devices/timer.h new file mode 100644 index 0000000..45a3f72 --- /dev/null +++ b/include/devices/timer.h @@ -0,0 +1,23 @@ +#ifndef DEVICES_TIMER_H +#define DEVICES_TIMER_H + +#include +#include + +/* Number of timer interrupts per second. */ +#define TIMER_FREQ 100 + +void timer_init (void); +void timer_calibrate (void); + +int64_t timer_ticks (void); +int64_t timer_elapsed (int64_t); + +void timer_sleep (int64_t ticks); +void timer_msleep (int64_t milliseconds); +void timer_usleep (int64_t microseconds); +void timer_nsleep (int64_t nanoseconds); + +void timer_print_stats (void); + +#endif /* devices/timer.h */ diff --git a/include/devices/vga.h b/include/devices/vga.h new file mode 100644 index 0000000..59690fb --- /dev/null +++ b/include/devices/vga.h @@ -0,0 +1,6 @@ +#ifndef DEVICES_VGA_H +#define DEVICES_VGA_H + +void vga_putc (int); + +#endif /* devices/vga.h */ diff --git a/include/filesys/directory.h b/include/filesys/directory.h new file mode 100644 index 0000000..f796582 --- /dev/null +++ b/include/filesys/directory.h @@ -0,0 +1,30 @@ +#ifndef FILESYS_DIRECTORY_H +#define FILESYS_DIRECTORY_H + +#include +#include +#include "devices/disk.h" + +/* Maximum length of a file name component. + * This is the traditional UNIX maximum length. + * After directories are implemented, this maximum length may be + * retained, but much longer full path names must be allowed. */ +#define NAME_MAX 14 + +struct inode; + +/* Opening and closing directories. */ +bool dir_create (disk_sector_t sector, size_t entry_cnt); +struct dir *dir_open (struct inode *); +struct dir *dir_open_root (void); +struct dir *dir_reopen (struct dir *); +void dir_close (struct dir *); +struct inode *dir_get_inode (struct dir *); + +/* Reading and writing. */ +bool dir_lookup (const struct dir *, const char *name, struct inode **); +bool dir_add (struct dir *, const char *name, disk_sector_t); +bool dir_remove (struct dir *, const char *name); +bool dir_readdir (struct dir *, char name[NAME_MAX + 1]); + +#endif /* filesys/directory.h */ diff --git a/include/filesys/file.h b/include/filesys/file.h new file mode 100644 index 0000000..a33c5af --- /dev/null +++ b/include/filesys/file.h @@ -0,0 +1,29 @@ +#ifndef FILESYS_FILE_H +#define FILESYS_FILE_H + +#include "filesys/off_t.h" + +struct inode; + +/* Opening and closing files. */ +struct file *file_open (struct inode *); +struct file *file_reopen (struct file *); +void file_close (struct file *); +struct inode *file_get_inode (struct file *); + +/* Reading and writing. */ +off_t file_read (struct file *, void *, off_t); +off_t file_read_at (struct file *, void *, off_t size, off_t start); +off_t file_write (struct file *, const void *, off_t); +off_t file_write_at (struct file *, const void *, off_t size, off_t start); + +/* Preventing writes. */ +void file_deny_write (struct file *); +void file_allow_write (struct file *); + +/* File position. */ +void file_seek (struct file *, off_t); +off_t file_tell (struct file *); +off_t file_length (struct file *); + +#endif /* filesys/file.h */ diff --git a/include/filesys/filesys.h b/include/filesys/filesys.h new file mode 100644 index 0000000..caef83c --- /dev/null +++ b/include/filesys/filesys.h @@ -0,0 +1,20 @@ +#ifndef FILESYS_FILESYS_H +#define FILESYS_FILESYS_H + +#include +#include "filesys/off_t.h" + +/* Sectors of system file inodes. */ +#define FREE_MAP_SECTOR 0 /* Free map file inode sector. */ +#define ROOT_DIR_SECTOR 1 /* Root directory file inode sector. */ + +/* Disk used for file system. */ +extern struct disk *filesys_disk; + +void filesys_init (bool format); +void filesys_done (void); +bool filesys_create (const char *name, off_t initial_size); +struct file *filesys_open (const char *name); +bool filesys_remove (const char *name); + +#endif /* filesys/filesys.h */ diff --git a/include/filesys/free-map.h b/include/filesys/free-map.h new file mode 100644 index 0000000..ce08f5c --- /dev/null +++ b/include/filesys/free-map.h @@ -0,0 +1,17 @@ +#ifndef FILESYS_FREE_MAP_H +#define FILESYS_FREE_MAP_H + +#include +#include +#include "devices/disk.h" + +void free_map_init (void); +void free_map_read (void); +void free_map_create (void); +void free_map_open (void); +void free_map_close (void); + +bool free_map_allocate (size_t, disk_sector_t *); +void free_map_release (disk_sector_t, size_t); + +#endif /* filesys/free-map.h */ diff --git a/include/filesys/fsutil.h b/include/filesys/fsutil.h new file mode 100644 index 0000000..abebfe2 --- /dev/null +++ b/include/filesys/fsutil.h @@ -0,0 +1,10 @@ +#ifndef FILESYS_FSUTIL_H +#define FILESYS_FSUTIL_H + +void fsutil_ls (char **argv); +void fsutil_cat (char **argv); +void fsutil_rm (char **argv); +void fsutil_put (char **argv); +void fsutil_get (char **argv); + +#endif /* filesys/fsutil.h */ diff --git a/include/filesys/inode.h b/include/filesys/inode.h new file mode 100644 index 0000000..be7df63 --- /dev/null +++ b/include/filesys/inode.h @@ -0,0 +1,23 @@ +#ifndef FILESYS_INODE_H +#define FILESYS_INODE_H + +#include +#include "filesys/off_t.h" +#include "devices/disk.h" + +struct bitmap; + +void inode_init (void); +bool inode_create (disk_sector_t, off_t); +struct inode *inode_open (disk_sector_t); +struct inode *inode_reopen (struct inode *); +disk_sector_t inode_get_inumber (const struct inode *); +void inode_close (struct inode *); +void inode_remove (struct inode *); +off_t inode_read_at (struct inode *, void *, off_t size, off_t offset); +off_t inode_write_at (struct inode *, const void *, off_t size, off_t offset); +void inode_deny_write (struct inode *); +void inode_allow_write (struct inode *); +off_t inode_length (const struct inode *); + +#endif /* filesys/inode.h */ diff --git a/include/filesys/off_t.h b/include/filesys/off_t.h new file mode 100644 index 0000000..33e9d18 --- /dev/null +++ b/include/filesys/off_t.h @@ -0,0 +1,15 @@ +#ifndef FILESYS_OFF_T_H +#define FILESYS_OFF_T_H + +#include + +/* An offset within a file. + * This is a separate header because multiple headers want this + * definition but not any others. */ +typedef int32_t off_t; + +/* Format specifier for printf(), e.g.: + * printf ("offset=%"PROTd"\n", offset); */ +#define PROTd PRId32 + +#endif /* filesys/off_t.h */ diff --git a/include/intrinsic.h b/include/intrinsic.h new file mode 100644 index 0000000..1464c30 --- /dev/null +++ b/include/intrinsic.h @@ -0,0 +1,131 @@ +#ifndef INSTRINSIC_H +#include "threads/mmu.h" + +/* Store the physical address of the page directory into CR3 + aka PDBR (page directory base register). This activates our + new page tables immediately. See [IA32-v2a] "MOV--Move + to/from Control Registers" and [IA32-v3a] 3.7.5 "Base Address + of the Page Directory". */ +__attribute__((always_inline)) +static __inline void lcr3(uint64_t val) { + __asm __volatile("movq %0, %%cr3" : : "r" (val)); +} + +__attribute__((always_inline)) +static __inline void lgdt(const struct desc_ptr *dtr) { + __asm __volatile("lgdt %0" : : "m" (*dtr)); +} + +__attribute__((always_inline)) +static __inline void lldt(uint16_t sel) { + __asm __volatile("lldt %0" : : "r" (sel)); +} + +__attribute__((always_inline)) +static __inline void ltr(uint16_t sel) { + __asm __volatile("ltr %0" : : "r" (sel)); +} + +__attribute__((always_inline)) +static __inline void lidt(const struct desc_ptr *dtr) { + __asm __volatile("lidt %0" : : "m" (*dtr)); +} + +__attribute__((always_inline)) +static __inline void invlpg(uint64_t addr) { + __asm __volatile("invlpg (%0)" : : "r" (addr) : "memory"); +} + +__attribute__((always_inline)) +static __inline uint64_t read_eflags(void) { + uint64_t rflags; + __asm __volatile("pushfq; popq %0" : "=r" (rflags)); + return rflags; +} + +__attribute__((always_inline)) +static __inline uint64_t rcr3(void) { + uint64_t val; + __asm __volatile("movq %%cr3,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rrax(void) { + uint64_t val; + __asm __volatile("movq %%rax,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rrdi(void) { + uint64_t val; + __asm __volatile("movq %%rdi,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rrsi(void) { + uint64_t val; + __asm __volatile("movq %%rsi,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rrdx(void) { + uint64_t val; + __asm __volatile("movq %%rdx,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rr10(void) { + uint64_t val; + __asm __volatile("movq %%r10,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rr8(void) { + uint64_t val; + __asm __volatile("movq %%r8,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rr9(void) { + uint64_t val; + __asm __volatile("movq %%r9,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rrcx(void) { + uint64_t val; + __asm __volatile("movq %%rcx,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline uint64_t rrsp(void) { + uint64_t val; + __asm __volatile("movq %%rsp,%0" : "=r" (val)); + return val; +} +__attribute__((always_inline)) +static __inline uint64_t rcr2(void) { + uint64_t val; + __asm __volatile("movq %%cr2,%0" : "=r" (val)); + return val; +} + +__attribute__((always_inline)) +static __inline void write_msr(uint32_t ecx, uint64_t val) { + uint32_t edx, eax; + eax = (uint32_t) val; + edx = (uint32_t) (val >> 32); + __asm __volatile("wrmsr" + :: "c" (ecx), "d" (edx), "a" (eax) ); +} + +#endif /* intrinsic.h */ diff --git a/include/lib/ctype.h b/include/lib/ctype.h new file mode 100644 index 0000000..12f05c2 --- /dev/null +++ b/include/lib/ctype.h @@ -0,0 +1,28 @@ +#ifndef __LIB_CTYPE_H +#define __LIB_CTYPE_H + +static inline int islower (int c) { return c >= 'a' && c <= 'z'; } +static inline int isupper (int c) { return c >= 'A' && c <= 'Z'; } +static inline int isalpha (int c) { return islower (c) || isupper (c); } +static inline int isdigit (int c) { return c >= '0' && c <= '9'; } +static inline int isalnum (int c) { return isalpha (c) || isdigit (c); } +static inline int isxdigit (int c) { + return isdigit (c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} +static inline int isspace (int c) { + return (c == ' ' || c == '\f' || c == '\n' + || c == '\r' || c == '\t' || c == '\v'); +} +static inline int isblank (int c) { return c == ' ' || c == '\t'; } +static inline int isgraph (int c) { return c > 32 && c < 127; } +static inline int isprint (int c) { return c >= 32 && c < 127; } +static inline int iscntrl (int c) { return (c >= 0 && c < 32) || c == 127; } +static inline int isascii (int c) { return c >= 0 && c < 128; } +static inline int ispunct (int c) { + return isprint (c) && !isalnum (c) && !isspace (c); +} + +static inline int tolower (int c) { return isupper (c) ? c - 'A' + 'a' : c; } +static inline int toupper (int c) { return islower (c) ? c - 'a' + 'A' : c; } + +#endif /* lib/ctype.h */ diff --git a/include/lib/debug.h b/include/lib/debug.h new file mode 100644 index 0000000..4204d4c --- /dev/null +++ b/include/lib/debug.h @@ -0,0 +1,38 @@ +#ifndef __LIB_DEBUG_H +#define __LIB_DEBUG_H + +/* GCC lets us add "attributes" to functions, function + * parameters, etc. to indicate their properties. + * See the GCC manual for details. */ +#define UNUSED __attribute__ ((unused)) +#define NO_RETURN __attribute__ ((noreturn)) +#define NO_INLINE __attribute__ ((noinline)) +#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST))) + +/* Halts the OS, printing the source file name, line number, and + * function name, plus a user-specific message. */ +#define PANIC(...) debug_panic (__FILE__, __LINE__, __func__, __VA_ARGS__) + +void debug_panic (const char *file, int line, const char *function, + const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN; +void debug_backtrace (void); + +#endif + + + +/* This is outside the header guard so that debug.h may be + * included multiple times with different settings of NDEBUG. */ +#undef ASSERT +#undef NOT_REACHED + +#ifndef NDEBUG +#define ASSERT(CONDITION) \ + if ((CONDITION)) { } else { \ + PANIC ("assertion `%s' failed.", #CONDITION); \ + } +#define NOT_REACHED() PANIC ("executed an unreachable statement"); +#else +#define ASSERT(CONDITION) ((void) 0) +#define NOT_REACHED() for (;;) +#endif /* lib/debug.h */ diff --git a/include/lib/inttypes.h b/include/lib/inttypes.h new file mode 100644 index 0000000..f703725 --- /dev/null +++ b/include/lib/inttypes.h @@ -0,0 +1,48 @@ +#ifndef __LIB_INTTYPES_H +#define __LIB_INTTYPES_H + +#include + +#define PRId8 "hhd" +#define PRIi8 "hhi" +#define PRIo8 "hho" +#define PRIu8 "hhu" +#define PRIx8 "hhx" +#define PRIX8 "hhX" + +#define PRId16 "hd" +#define PRIi16 "hi" +#define PRIo16 "ho" +#define PRIu16 "hu" +#define PRIx16 "hx" +#define PRIX16 "hX" + +#define PRId32 "d" +#define PRIi32 "i" +#define PRIo32 "o" +#define PRIu32 "u" +#define PRIx32 "x" +#define PRIX32 "X" + +#define PRId64 "lld" +#define PRIi64 "lli" +#define PRIo64 "llo" +#define PRIu64 "llu" +#define PRIx64 "llx" +#define PRIX64 "llX" + +#define PRIdMAX "jd" +#define PRIiMAX "ji" +#define PRIoMAX "jo" +#define PRIuMAX "ju" +#define PRIxMAX "jx" +#define PRIXMAX "jX" + +#define PRIdPTR "td" +#define PRIiPTR "ti" +#define PRIoPTR "to" +#define PRIuPTR "tu" +#define PRIxPTR "tx" +#define PRIXPTR "tX" + +#endif /* lib/inttypes.h */ diff --git a/include/lib/kernel/bitmap.h b/include/lib/kernel/bitmap.h new file mode 100644 index 0000000..a50593c --- /dev/null +++ b/include/lib/kernel/bitmap.h @@ -0,0 +1,51 @@ +#ifndef __LIB_KERNEL_BITMAP_H +#define __LIB_KERNEL_BITMAP_H + +#include +#include +#include + +/* Bitmap abstract data type. */ + +/* Creation and destruction. */ +struct bitmap *bitmap_create (size_t bit_cnt); +struct bitmap *bitmap_create_in_buf (size_t bit_cnt, void *, size_t byte_cnt); +size_t bitmap_buf_size (size_t bit_cnt); +void bitmap_destroy (struct bitmap *); + +/* Bitmap size. */ +size_t bitmap_size (const struct bitmap *); + +/* Setting and testing single bits. */ +void bitmap_set (struct bitmap *, size_t idx, bool); +void bitmap_mark (struct bitmap *, size_t idx); +void bitmap_reset (struct bitmap *, size_t idx); +void bitmap_flip (struct bitmap *, size_t idx); +bool bitmap_test (const struct bitmap *, size_t idx); + +/* Setting and testing multiple bits. */ +void bitmap_set_all (struct bitmap *, bool); +void bitmap_set_multiple (struct bitmap *, size_t start, size_t cnt, bool); +size_t bitmap_count (const struct bitmap *, size_t start, size_t cnt, bool); +bool bitmap_contains (const struct bitmap *, size_t start, size_t cnt, bool); +bool bitmap_any (const struct bitmap *, size_t start, size_t cnt); +bool bitmap_none (const struct bitmap *, size_t start, size_t cnt); +bool bitmap_all (const struct bitmap *, size_t start, size_t cnt); + +/* Finding set or unset bits. */ +#define BITMAP_ERROR SIZE_MAX +size_t bitmap_scan (const struct bitmap *, size_t start, size_t cnt, bool); +size_t bitmap_scan_and_flip (struct bitmap *, size_t start, size_t cnt, bool); + +/* File input and output. */ +#ifdef FILESYS +struct file; +size_t bitmap_file_size (const struct bitmap *); +bool bitmap_read (struct bitmap *, struct file *); +bool bitmap_write (const struct bitmap *, struct file *); +#endif + +/* Debugging. */ +void bitmap_dump (const struct bitmap *); + +#endif /* lib/kernel/bitmap.h */ diff --git a/include/lib/kernel/console.h b/include/lib/kernel/console.h new file mode 100644 index 0000000..ab99249 --- /dev/null +++ b/include/lib/kernel/console.h @@ -0,0 +1,8 @@ +#ifndef __LIB_KERNEL_CONSOLE_H +#define __LIB_KERNEL_CONSOLE_H + +void console_init (void); +void console_panic (void); +void console_print_stats (void); + +#endif /* lib/kernel/console.h */ diff --git a/include/lib/kernel/hash.h b/include/lib/kernel/hash.h new file mode 100644 index 0000000..b335962 --- /dev/null +++ b/include/lib/kernel/hash.h @@ -0,0 +1,100 @@ +#ifndef __LIB_KERNEL_HASH_H +#define __LIB_KERNEL_HASH_H + +/* Hash table. + * + * This data structure is thoroughly documented in the Tour of + * Pintos for Project 3. + * + * This is a standard hash table with chaining. To locate an + * element in the table, we compute a hash function over the + * element's data and use that as an index into an array of + * doubly linked lists, then linearly search the list. + * + * The chain lists do not use dynamic allocation. Instead, each + * structure that can potentially be in a hash must embed a + * struct hash_elem member. All of the hash functions operate on + * these `struct hash_elem's. The hash_entry macro allows + * conversion from a struct hash_elem back to a structure object + * that contains it. This is the same technique used in the + * linked list implementation. Refer to lib/kernel/list.h for a + * detailed explanation. */ + +#include +#include +#include +#include "list.h" + +/* Hash element. */ +struct hash_elem { + struct list_elem list_elem; +}; + +/* Converts pointer to hash element HASH_ELEM into a pointer to + * the structure that HASH_ELEM is embedded inside. Supply the + * name of the outer structure STRUCT and the member name MEMBER + * of the hash element. See the big comment at the top of the + * file for an example. */ +#define hash_entry(HASH_ELEM, STRUCT, MEMBER) \ + ((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem \ + - offsetof (STRUCT, MEMBER.list_elem))) + +/* Computes and returns the hash value for hash element E, given + * auxiliary data AUX. */ +typedef unsigned hash_hash_func (const struct hash_elem *e, void *aux); + +/* Compares the value of two hash elements A and B, given + * auxiliary data AUX. Returns true if A is less than B, or + * false if A is greater than or equal to B. */ +typedef bool hash_less_func (const struct hash_elem *a, + const struct hash_elem *b, + void *aux); + +/* Performs some operation on hash element E, given auxiliary + * data AUX. */ +typedef void hash_action_func (struct hash_elem *e, void *aux); + +/* Hash table. */ +struct hash { + size_t elem_cnt; /* Number of elements in table. */ + size_t bucket_cnt; /* Number of buckets, a power of 2. */ + struct list *buckets; /* Array of `bucket_cnt' lists. */ + hash_hash_func *hash; /* Hash function. */ + hash_less_func *less; /* Comparison function. */ + void *aux; /* Auxiliary data for `hash' and `less'. */ +}; + +/* A hash table iterator. */ +struct hash_iterator { + struct hash *hash; /* The hash table. */ + struct list *bucket; /* Current bucket. */ + struct hash_elem *elem; /* Current hash element in current bucket. */ +}; + +/* Basic life cycle. */ +bool hash_init (struct hash *, hash_hash_func *, hash_less_func *, void *aux); +void hash_clear (struct hash *, hash_action_func *); +void hash_destroy (struct hash *, hash_action_func *); + +/* Search, insertion, deletion. */ +struct hash_elem *hash_insert (struct hash *, struct hash_elem *); +struct hash_elem *hash_replace (struct hash *, struct hash_elem *); +struct hash_elem *hash_find (struct hash *, struct hash_elem *); +struct hash_elem *hash_delete (struct hash *, struct hash_elem *); + +/* Iteration. */ +void hash_apply (struct hash *, hash_action_func *); +void hash_first (struct hash_iterator *, struct hash *); +struct hash_elem *hash_next (struct hash_iterator *); +struct hash_elem *hash_cur (struct hash_iterator *); + +/* Information. */ +size_t hash_size (struct hash *); +bool hash_empty (struct hash *); + +/* Sample hash functions. */ +unsigned hash_bytes (const void *, size_t); +unsigned hash_string (const char *); +unsigned hash_int (int); + +#endif /* lib/kernel/hash.h */ diff --git a/include/lib/kernel/list.h b/include/lib/kernel/list.h new file mode 100644 index 0000000..92628c5 --- /dev/null +++ b/include/lib/kernel/list.h @@ -0,0 +1,163 @@ +#ifndef __LIB_KERNEL_LIST_H +#define __LIB_KERNEL_LIST_H + +/* Doubly linked list. + * + * This implementation of a doubly linked list does not require + * use of dynamically allocated memory. Instead, each structure + * that is a potential list element must embed a struct list_elem + * member. All of the list functions operate on these `struct + * list_elem's. The list_entry macro allows conversion from a + * struct list_elem back to a structure object that contains it. + + * For example, suppose there is a needed for a list of `struct + * foo'. `struct foo' should contain a `struct list_elem' + * member, like so: + + * struct foo { + * struct list_elem elem; + * int bar; + * ...other members... + * }; + + * Then a list of `struct foo' can be be declared and initialized + * like so: + + * struct list foo_list; + + * list_init (&foo_list); + + * Iteration is a typical situation where it is necessary to + * convert from a struct list_elem back to its enclosing + * structure. Here's an example using foo_list: + + * struct list_elem *e; + + * for (e = list_begin (&foo_list); e != list_end (&foo_list); + * e = list_next (e)) { + * struct foo *f = list_entry (e, struct foo, elem); + * ...do something with f... + * } + + * You can find real examples of list usage throughout the + * source; for example, malloc.c, palloc.c, and thread.c in the + * threads directory all use lists. + + * The interface for this list is inspired by the list<> template + * in the C++ STL. If you're familiar with list<>, you should + * find this easy to use. However, it should be emphasized that + * these lists do *no* type checking and can't do much other + * correctness checking. If you screw up, it will bite you. + + * Glossary of list terms: + + * - "front": The first element in a list. Undefined in an + * empty list. Returned by list_front(). + + * - "back": The last element in a list. Undefined in an empty + * list. Returned by list_back(). + + * - "tail": The element figuratively just after the last + * element of a list. Well defined even in an empty list. + * Returned by list_end(). Used as the end sentinel for an + * iteration from front to back. + + * - "beginning": In a non-empty list, the front. In an empty + * list, the tail. Returned by list_begin(). Used as the + * starting point for an iteration from front to back. + + * - "head": The element figuratively just before the first + * element of a list. Well defined even in an empty list. + * Returned by list_rend(). Used as the end sentinel for an + * iteration from back to front. + + * - "reverse beginning": In a non-empty list, the back. In an + * empty list, the head. Returned by list_rbegin(). Used as + * the starting point for an iteration from back to front. + * + * - "interior element": An element that is not the head or + * tail, that is, a real list element. An empty list does + * not have any interior elements.*/ + +#include +#include +#include + +/* List element. */ +struct list_elem { + struct list_elem *prev; /* Previous list element. */ + struct list_elem *next; /* Next list element. */ +}; + +/* List. */ +struct list { + struct list_elem head; /* List head. */ + struct list_elem tail; /* List tail. */ +}; + +/* Converts pointer to list element LIST_ELEM into a pointer to + the structure that LIST_ELEM is embedded inside. Supply the + name of the outer structure STRUCT and the member name MEMBER + of the list element. See the big comment at the top of the + file for an example. */ +#define list_entry(LIST_ELEM, STRUCT, MEMBER) \ + ((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next \ + - offsetof (STRUCT, MEMBER.next))) + +void list_init (struct list *); + +/* List traversal. */ +struct list_elem *list_begin (struct list *); +struct list_elem *list_next (struct list_elem *); +struct list_elem *list_end (struct list *); + +struct list_elem *list_rbegin (struct list *); +struct list_elem *list_prev (struct list_elem *); +struct list_elem *list_rend (struct list *); + +struct list_elem *list_head (struct list *); +struct list_elem *list_tail (struct list *); + +/* List insertion. */ +void list_insert (struct list_elem *, struct list_elem *); +void list_splice (struct list_elem *before, + struct list_elem *first, struct list_elem *last); +void list_push_front (struct list *, struct list_elem *); +void list_push_back (struct list *, struct list_elem *); + +/* List removal. */ +struct list_elem *list_remove (struct list_elem *); +struct list_elem *list_pop_front (struct list *); +struct list_elem *list_pop_back (struct list *); + +/* List elements. */ +struct list_elem *list_front (struct list *); +struct list_elem *list_back (struct list *); + +/* List properties. */ +size_t list_size (struct list *); +bool list_empty (struct list *); + +/* Miscellaneous. */ +void list_reverse (struct list *); + +/* Compares the value of two list elements A and B, given + auxiliary data AUX. Returns true if A is less than B, or + false if A is greater than or equal to B. */ +typedef bool list_less_func (const struct list_elem *a, + const struct list_elem *b, + void *aux); + +/* Operations on lists with ordered elements. */ +void list_sort (struct list *, + list_less_func *, void *aux); +void list_insert_ordered (struct list *, struct list_elem *, + list_less_func *, void *aux); +void list_unique (struct list *, struct list *duplicates, + list_less_func *, void *aux); + +/* Max and min. */ +struct list_elem *list_max (struct list *, list_less_func *, void *aux); +struct list_elem *list_min (struct list *, list_less_func *, void *aux); + +#endif /* lib/kernel/list.h */ diff --git a/include/lib/kernel/stdio.h b/include/lib/kernel/stdio.h new file mode 100644 index 0000000..3e5bae9 --- /dev/null +++ b/include/lib/kernel/stdio.h @@ -0,0 +1,6 @@ +#ifndef __LIB_KERNEL_STDIO_H +#define __LIB_KERNEL_STDIO_H + +void putbuf (const char *, size_t); + +#endif /* lib/kernel/stdio.h */ diff --git a/include/lib/limits.h b/include/lib/limits.h new file mode 100644 index 0000000..c957ec4 --- /dev/null +++ b/include/lib/limits.h @@ -0,0 +1,34 @@ +#ifndef __LIB_LIMITS_H +#define __LIB_LIMITS_H + +#define CHAR_BIT 8 + +#define SCHAR_MAX 127 +#define SCHAR_MIN (-SCHAR_MAX - 1) +#define UCHAR_MAX 255 + +#ifdef __CHAR_UNSIGNED__ +#define CHAR_MIN 0 +#define CHAR_MAX UCHAR_MAX +#else +#define CHAR_MIN SCHAR_MIN +#define CHAR_MAX SCHAR_MAX +#endif + +#define SHRT_MAX 32767 +#define SHRT_MIN (-SHRT_MAX - 1) +#define USHRT_MAX 65535 + +#define INT_MAX 2147483647 +#define INT_MIN (-INT_MAX - 1) +#define UINT_MAX 4294967295U + +#define LONG_MAX 2147483647L +#define LONG_MIN (-LONG_MAX - 1) +#define ULONG_MAX 4294967295UL + +#define LLONG_MAX 9223372036854775807LL +#define LLONG_MIN (-LLONG_MAX - 1) +#define ULLONG_MAX 18446744073709551615ULL + +#endif /* lib/limits.h */ diff --git a/include/lib/random.h b/include/lib/random.h new file mode 100644 index 0000000..0950ae2 --- /dev/null +++ b/include/lib/random.h @@ -0,0 +1,10 @@ +#ifndef __LIB_RANDOM_H +#define __LIB_RANDOM_H + +#include + +void random_init (unsigned seed); +void random_bytes (void *, size_t); +unsigned long random_ulong (void); + +#endif /* lib/random.h */ diff --git a/include/lib/round.h b/include/lib/round.h new file mode 100644 index 0000000..69fd216 --- /dev/null +++ b/include/lib/round.h @@ -0,0 +1,18 @@ +#ifndef __LIB_ROUND_H +#define __LIB_ROUND_H + +/* Yields X rounded up to the nearest multiple of STEP. + * For X >= 0, STEP >= 1 only. */ +#define ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP) * (STEP)) + +/* Yields X divided by STEP, rounded up. + * For X >= 0, STEP >= 1 only. */ +#define DIV_ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP)) + +/* Yields X rounded down to the nearest multiple of STEP. + * For X >= 0, STEP >= 1 only. */ +#define ROUND_DOWN(X, STEP) ((X) / (STEP) * (STEP)) + +/* There is no DIV_ROUND_DOWN. It would be simply X / STEP. */ + +#endif /* lib/round.h */ diff --git a/include/lib/stdarg.h b/include/lib/stdarg.h new file mode 100644 index 0000000..ec3b057 --- /dev/null +++ b/include/lib/stdarg.h @@ -0,0 +1,14 @@ +#ifndef __LIB_STDARG_H +#define __LIB_STDARG_H + +/* GCC has functionality as built-ins, + * so all we need is to use it. */ + +typedef __builtin_va_list va_list; + +#define va_start(LIST, ARG) __builtin_va_start (LIST, ARG) +#define va_end(LIST) __builtin_va_end (LIST) +#define va_arg(LIST, TYPE) __builtin_va_arg (LIST, TYPE) +#define va_copy(DST, SRC) __builtin_va_copy (DST, SRC) + +#endif /* lib/stdarg.h */ diff --git a/include/lib/stdbool.h b/include/lib/stdbool.h new file mode 100644 index 0000000..f173a91 --- /dev/null +++ b/include/lib/stdbool.h @@ -0,0 +1,9 @@ +#ifndef __LIB_STDBOOL_H +#define __LIB_STDBOOL_H + +#define bool _Bool +#define true 1 +#define false 0 +#define __bool_true_false_are_defined 1 + +#endif /* lib/stdbool.h */ diff --git a/include/lib/stddef.h b/include/lib/stddef.h new file mode 100644 index 0000000..da1deeb --- /dev/null +++ b/include/lib/stddef.h @@ -0,0 +1,12 @@ +#ifndef __LIB_STDDEF_H +#define __LIB_STDDEF_H + +#define NULL ((void *) 0) +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *) 0)->MEMBER) + +/* GCC predefines the types we need for ptrdiff_t and size_t, + * so that we don't have to guess. */ +typedef __PTRDIFF_TYPE__ ptrdiff_t; +typedef __SIZE_TYPE__ size_t; + +#endif /* lib/stddef.h */ diff --git a/include/lib/stdint.h b/include/lib/stdint.h new file mode 100644 index 0000000..0b38a39 --- /dev/null +++ b/include/lib/stdint.h @@ -0,0 +1,51 @@ +#ifndef __LIB_STDINT_H +#define __LIB_STDINT_H + +typedef signed char int8_t; +#define INT8_MAX 127 +#define INT8_MIN (-INT8_MAX - 1) + +typedef signed short int int16_t; +#define INT16_MAX 32767 +#define INT16_MIN (-INT16_MAX - 1) + +typedef signed int int32_t; +#define INT32_MAX 2147483647 +#define INT32_MIN (-INT32_MAX - 1) + +typedef signed long long int int64_t; +#define INT64_MAX 9223372036854775807LL +#define INT64_MIN (-INT64_MAX - 1) + +typedef unsigned char uint8_t; +#define UINT8_MAX 255 + +typedef unsigned short int uint16_t; +#define UINT16_MAX 65535 + +typedef unsigned int uint32_t; +#define UINT32_MAX 4294967295U + +typedef unsigned long long int uint64_t; +#define UINT64_MAX 18446744073709551615ULL + +typedef int64_t intptr_t; +#define INTPTR_MIN INT64_MIN +#define INTPTR_MAX INT64_MAX + +typedef uint64_t uintptr_t; +#define UINTPTR_MAX UINT64_MAX + +typedef int64_t intmax_t; +#define INTMAX_MIN INT64_MIN +#define INTMAX_MAX INT64_MAX + +typedef uint64_t uintmax_t; +#define UINTMAX_MAX UINT64_MAX + +#define PTRDIFF_MIN INT32_MIN +#define PTRDIFF_MAX INT32_MAX + +#define SIZE_MAX UINT32_MAX + +#endif /* lib/stdint.h */ diff --git a/include/lib/stdio.h b/include/lib/stdio.h new file mode 100644 index 0000000..f2092ca --- /dev/null +++ b/include/lib/stdio.h @@ -0,0 +1,39 @@ +#ifndef __LIB_STDIO_H +#define __LIB_STDIO_H + +#include +#include +#include +#include +#include + +/* Include lib/user/stdio.h or lib/kernel/stdio.h, as + * appropriate. */ +#include_next + +/* Predefined file handles. */ +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 + +/* Standard functions. */ +int printf (const char *, ...) PRINTF_FORMAT (1, 2); +int snprintf (char *, size_t, const char *, ...) PRINTF_FORMAT (3, 4); +int vprintf (const char *, va_list) PRINTF_FORMAT (1, 0); +int vsnprintf (char *, size_t, const char *, va_list) PRINTF_FORMAT (3, 0); +int putchar (int); +int puts (const char *); + +/* Nonstandard functions. */ +void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii); + +/* Internal functions. */ +void __vprintf (const char *format, va_list args, + void (*output) (char, void *), void *aux); +void __printf (const char *format, + void (*output) (char, void *), void *aux, ...); + +/* Try to be helpful. */ +#define sprintf dont_use_sprintf_use_snprintf +#define vsprintf dont_use_vsprintf_use_vsnprintf + +#endif /* lib/stdio.h */ diff --git a/include/lib/stdlib.h b/include/lib/stdlib.h new file mode 100644 index 0000000..08adc4c --- /dev/null +++ b/include/lib/stdlib.h @@ -0,0 +1,22 @@ +#ifndef __LIB_STDLIB_H +#define __LIB_STDLIB_H + +#include + +/* Standard functions. */ +int atoi (const char *); +void qsort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *)); +void *bsearch (const void *key, const void *array, size_t cnt, + size_t size, int (*compare) (const void *, const void *)); + +/* Nonstandard functions. */ +void sort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux); +void *binary_search (const void *key, const void *array, size_t cnt, + size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux); + +#endif /* lib/stdlib.h */ diff --git a/include/lib/string.h b/include/lib/string.h new file mode 100644 index 0000000..1fff82a --- /dev/null +++ b/include/lib/string.h @@ -0,0 +1,35 @@ +#ifndef __LIB_STRING_H +#define __LIB_STRING_H + +#include + +/* Standard. */ +void *memcpy (void *, const void *, size_t); +void *memmove (void *, const void *, size_t); +char *strncat (char *, const char *, size_t); +int memcmp (const void *, const void *, size_t); +int strcmp (const char *, const char *); +void *memchr (const void *, int, size_t); +char *strchr (const char *, int); +size_t strcspn (const char *, const char *); +char *strpbrk (const char *, const char *); +char *strrchr (const char *, int); +size_t strspn (const char *, const char *); +char *strstr (const char *, const char *); +void *memset (void *, int, size_t); +size_t strlen (const char *); + +/* Extensions. */ +size_t strlcpy (char *, const char *, size_t); +size_t strlcat (char *, const char *, size_t); +char *strtok_r (char *, const char *, char **); +size_t strnlen (const char *, size_t); + +/* Try to be helpful. */ +#define strcpy dont_use_strcpy_use_strlcpy +#define strncpy dont_use_strncpy_use_strlcpy +#define strcat dont_use_strcat_use_strlcat +#define strncat dont_use_strncat_use_strlcat +#define strtok dont_use_strtok_use_strtok_r + +#endif /* lib/string.h */ diff --git a/include/lib/syscall-nr.h b/include/lib/syscall-nr.h new file mode 100644 index 0000000..940511c --- /dev/null +++ b/include/lib/syscall-nr.h @@ -0,0 +1,34 @@ +#ifndef __LIB_SYSCALL_NR_H +#define __LIB_SYSCALL_NR_H + +/* System call numbers. */ +enum { + /* Projects 2 and later. */ + SYS_HALT, /* Halt the operating system. */ + SYS_EXIT, /* Terminate this process. */ + SYS_FORK, /* Clone current process. */ + SYS_EXEC, /* Switch current process. */ + SYS_WAIT, /* Wait for a child process to die. */ + SYS_CREATE, /* Create a file. */ + SYS_REMOVE, /* Delete a file. */ + SYS_OPEN, /* Open a file. */ + SYS_FILESIZE, /* Obtain a file's size. */ + SYS_READ, /* Read from a file. */ + SYS_WRITE, /* Write to a file. */ + SYS_SEEK, /* Change position in a file. */ + SYS_TELL, /* Report current position in a file. */ + SYS_CLOSE, /* Close a file. */ + + /* Project 3 and optionally project 4. */ + SYS_MMAP, /* Map a file into memory. */ + SYS_MUNMAP, /* Remove a memory mapping. */ + + /* Project 4 only. */ + SYS_CHDIR, /* Change the current directory. */ + SYS_MKDIR, /* Create a directory. */ + SYS_READDIR, /* Reads a directory entry. */ + SYS_ISDIR, /* Tests if a fd represents a directory. */ + SYS_INUMBER /* Returns the inode number for a fd. */ +}; + +#endif /* lib/syscall-nr.h */ diff --git a/include/lib/user/stdio.h b/include/lib/user/stdio.h new file mode 100644 index 0000000..b9f3cc6 --- /dev/null +++ b/include/lib/user/stdio.h @@ -0,0 +1,7 @@ +#ifndef __LIB_USER_STDIO_H +#define __LIB_USER_STDIO_H + +int hprintf (int, const char *, ...) PRINTF_FORMAT (2, 3); +int vhprintf (int, const char *, va_list) PRINTF_FORMAT (2, 0); + +#endif /* lib/user/stdio.h */ diff --git a/include/lib/user/syscall.h b/include/lib/user/syscall.h new file mode 100644 index 0000000..e5f19fe --- /dev/null +++ b/include/lib/user/syscall.h @@ -0,0 +1,49 @@ +#ifndef __LIB_USER_SYSCALL_H +#define __LIB_USER_SYSCALL_H + +#include +#include + +/* Process identifier. */ +typedef int pid_t; +#define PID_ERROR ((pid_t) -1) + +/* Map region identifier. */ +typedef int mapid_t; +#define MAP_FAILED ((mapid_t) -1) + +/* Maximum characters in a filename written by readdir(). */ +#define READDIR_MAX_LEN 14 + +/* Typical return values from main() and arguments to exit(). */ +#define EXIT_SUCCESS 0 /* Successful execution. */ +#define EXIT_FAILURE 1 /* Unsuccessful execution. */ + +/* Projects 2 and later. */ +void halt (void) NO_RETURN; +void exit (int status) NO_RETURN; +pid_t fork (void); +int exec (const char *file); +int wait (pid_t); +bool create (const char *file, unsigned initial_size); +bool remove (const char *file); +int open (const char *file); +int filesize (int fd); +int read (int fd, void *buffer, unsigned length); +int write (int fd, const void *buffer, unsigned length); +void seek (int fd, unsigned position); +unsigned tell (int fd); +void close (int fd); + +/* Project 3 and optionally project 4. */ +mapid_t mmap (int fd, void *addr); +void munmap (mapid_t); + +/* Project 4 only. */ +bool chdir (const char *dir); +bool mkdir (const char *dir); +bool readdir (int fd, char name[READDIR_MAX_LEN + 1]); +bool isdir (int fd); +int inumber (int fd); + +#endif /* lib/user/syscall.h */ diff --git a/include/threads/flags.h b/include/threads/flags.h new file mode 100644 index 0000000..4e9a7b1 --- /dev/null +++ b/include/threads/flags.h @@ -0,0 +1,12 @@ +#ifndef THREADS_FLAGS_H +#define THREADS_FLAGS_H + +#define FLAG_MBS (1<<1) +#define FLAG_TF (1<<8) +#define FLAG_IF (1<<9) +#define FLAG_DF (1<<10) +#define FLAG_IOPL (3<<12) +#define FLAG_AC (1<<18) +#define FLAG_NT (1<<14) + +#endif /* threads/flags.h */ diff --git a/include/threads/init.h b/include/threads/init.h new file mode 100644 index 0000000..ce01572 --- /dev/null +++ b/include/threads/init.h @@ -0,0 +1,20 @@ +#ifndef THREADS_INIT_H +#define THREADS_INIT_H + +#include +#include +#include +#include + +/* Physical memory size, in 4 kB pages. */ +extern size_t ram_pages; + +/* Page map level 4 with kernel mappings only. */ +extern uint64_t *base_pml4; + +/* -q: Power off when kernel tasks complete? */ +extern bool power_off_when_done; + +void power_off (void) NO_RETURN; + +#endif /* threads/init.h */ diff --git a/include/threads/interrupt.h b/include/threads/interrupt.h new file mode 100644 index 0000000..50aaebc --- /dev/null +++ b/include/threads/interrupt.h @@ -0,0 +1,78 @@ +#ifndef THREADS_INTERRUPT_H +#define THREADS_INTERRUPT_H + +#include +#include + +/* Interrupts on or off? */ +enum intr_level { + INTR_OFF, /* Interrupts disabled. */ + INTR_ON /* Interrupts enabled. */ +}; + +enum intr_level intr_get_level (void); +enum intr_level intr_set_level (enum intr_level); +enum intr_level intr_enable (void); +enum intr_level intr_disable (void); + +/* Interrupt stack frame. */ +struct gp_registers { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rsi; + uint64_t rdi; + uint64_t rbp; + uint64_t rdx; + uint64_t rcx; + uint64_t rbx; + uint64_t rax; +} __attribute__((packed)); + +struct intr_frame { + /* Pushed by intr_entry in intr-stubs.S. + These are the interrupted task's saved registers. */ + struct gp_registers R; + uint16_t es; + uint16_t __pad1; + uint32_t __pad2; + uint16_t ds; + uint16_t __pad3; + uint32_t __pad4; + /* Pushed by intrNN_stub in intr-stubs.S. */ + uint64_t vec_no; /* Interrupt vector number. */ +/* Sometimes pushed by the CPU, + otherwise for consistency pushed as 0 by intrNN_stub. + The CPU puts it just under `eip', but we move it here. */ + uint64_t error_code; +/* Pushed by the CPU. + These are the interrupted task's saved registers. */ + uintptr_t rip; + uint16_t cs; + uint16_t __pad5; + uint32_t __pad6; + uint64_t eflags; + uintptr_t rsp; + uint16_t ss; + uint16_t __pad7; + uint32_t __pad8; +} __attribute__((packed)); + +typedef void intr_handler_func (struct intr_frame *); + +void intr_init (void); +void intr_register_ext (uint8_t vec, intr_handler_func *, const char *name); +void intr_register_int (uint8_t vec, int dpl, enum intr_level, + intr_handler_func *, const char *name); +bool intr_context (void); +void intr_yield_on_return (void); + +void intr_dump_frame (const struct intr_frame *); +const char *intr_name (uint8_t vec); + +#endif /* threads/interrupt.h */ diff --git a/include/threads/intr-stubs.h b/include/threads/intr-stubs.h new file mode 100644 index 0000000..8d34323 --- /dev/null +++ b/include/threads/intr-stubs.h @@ -0,0 +1,16 @@ +#ifndef THREADS_INTR_STUBS_H +#define THREADS_INTR_STUBS_H + +/* Interrupt stubs. + * + * These are little snippets of code in intr-stubs.S, one for + * each of the 256 possible x86 interrupts. Each one does a + * little bit of stack manipulation, then jumps to intr_entry(). + * See intr-stubs.S for more information. + * + * This array points to each of the interrupt stub entry points + * so that intr_init() can easily find them. */ +typedef void intr_stub_func (void); +extern intr_stub_func *intr_stubs[256]; + +#endif /* threads/intr-stubs.h */ diff --git a/include/threads/io.h b/include/threads/io.h new file mode 100644 index 0000000..e489bb7 --- /dev/null +++ b/include/threads/io.h @@ -0,0 +1,161 @@ +/* This file is derived from source code used in MIT's 6.828 + course. The original copyright notice is reproduced in full + below. */ + +/* + * Copyright (C) 1997 Massachusetts Institute of Technology + * + * This software is being provided by the copyright holders under the + * following license. By obtaining, using and/or copying this software, + * you agree that you have read, understood, and will comply with the + * following terms and conditions: + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose and without fee or royalty is + * hereby granted, provided that the full text of this NOTICE appears on + * ALL copies of the software and documentation or portions thereof, + * including modifications, that you make. + * + * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, + * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR + * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR + * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY + * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT + * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR + * DOCUMENTATION. + * + * The name and trademarks of copyright holders may NOT be used in + * advertising or publicity pertaining to the software without specific, + * written prior permission. Title to copyright in this software and any + * associated documentation will at all times remain with copyright + * holders. See the file AUTHORS which should have accompanied this software + * for a list of all copyright holders. + * + * This file may be derived from previously copyrighted software. This + * copyright applies only to those changes made by the copyright + * holders listed in the AUTHORS file. The rest of this file is covered by + * the copyright notices, if any, listed below. + */ + +#ifndef THREADS_IO_H +#define THREADS_IO_H + +#include +#include + +/* Reads and returns a byte from PORT. */ +static inline uint8_t +inb (uint16_t port) { + /* See [IA32-v2a] "IN". */ + uint8_t data; + asm volatile ("inb %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +/* Reads CNT bytes from PORT, one after another, and stores them + into the buffer starting at ADDR. */ +static inline void +insb (uint16_t port, void *addr, size_t cnt) { + /* See [IA32-v2a] "INS". */ + asm volatile ("cld; repne; insb" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +/* Reads and returns 16 bits from PORT. */ +static inline uint16_t +inw (uint16_t port) { + uint16_t data; + /* See [IA32-v2a] "IN". */ + asm volatile ("inw %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +/* Reads CNT 16-bit (halfword) units from PORT, one after + another, and stores them into the buffer starting at ADDR. */ +static inline void +insw (uint16_t port, void *addr, size_t cnt) { + /* See [IA32-v2a] "INS". */ + asm volatile ("cld; repne; insw" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +/* Reads and returns 32 bits from PORT. */ +static inline uint32_t +inl (uint16_t port) { + /* See [IA32-v2a] "IN". */ + uint32_t data; + asm volatile ("inl %w1,%0" : "=a" (data) : "d" (port)); + return data; +} + +/* Reads CNT 32-bit (word) units from PORT, one after another, + and stores them into the buffer starting at ADDR. */ +static inline void +insl (uint16_t port, void *addr, size_t cnt) { + /* See [IA32-v2a] "INS". */ + asm volatile ("cld; repne; insl" + : "=D" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "memory", "cc"); +} + +/* Writes byte DATA to PORT. */ +static inline void +outb (uint16_t port, uint8_t data) { + /* See [IA32-v2b] "OUT". */ + asm volatile ("outb %0,%w1" : : "a" (data), "d" (port)); +} + +/* Writes to PORT each byte of data in the CNT-byte buffer + starting at ADDR. */ +static inline void +outsb (uint16_t port, const void *addr, size_t cnt) { + /* See [IA32-v2b] "OUTS". */ + asm volatile ("cld; repne; outsb" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +/* Writes the 16-bit DATA to PORT. */ +static inline void +outw (uint16_t port, uint16_t data) { + /* See [IA32-v2b] "OUT". */ + asm volatile ("outw %0,%w1" : : "a" (data), "d" (port)); +} + +/* Writes to PORT each 16-bit unit (halfword) of data in the + CNT-halfword buffer starting at ADDR. */ +static inline void +outsw (uint16_t port, const void *addr, size_t cnt) { + /* See [IA32-v2b] "OUTS". */ + asm volatile ("cld; repne; outsw" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +/* Writes the 32-bit DATA to PORT. */ +static inline void +outl (uint16_t port, uint32_t data) { + /* See [IA32-v2b] "OUT". */ + asm volatile ("outl %0,%w1" : : "a" (data), "d" (port)); +} + +/* Writes to PORT each 32-bit unit (word) of data in the CNT-word + buffer starting at ADDR. */ +static inline void +outsl (uint16_t port, const void *addr, size_t cnt) { + /* See [IA32-v2b] "OUTS". */ + asm volatile ("cld; repne; outsl" + : "=S" (addr), "=c" (cnt) + : "d" (port), "0" (addr), "1" (cnt) + : "cc"); +} + +#endif /* threads/io.h */ diff --git a/include/threads/loader.h b/include/threads/loader.h new file mode 100644 index 0000000..6a41ca9 --- /dev/null +++ b/include/threads/loader.h @@ -0,0 +1,39 @@ +#ifndef THREADS_LOADER_H +#define THREADS_LOADER_H + +/* Constants fixed by the PC BIOS. */ +#define LOADER_BASE 0x7c00 /* Physical address of loader's base. */ +#define LOADER_END 0x7e00 /* Physical address of end of loader. */ + +/* Physical address of kernel base. */ +#define LOADER_KERN_BASE 0x8004000000 + +/* Kernel virtual address at which all physical memory is mapped. */ +#define LOADER_PHYS_BASE 0x200000 + +/* Multiboot infos */ +#define MULTIBOOT_INFO 0x7000 +#define MULTIBOOT_FLAG MULTIBOOT_INFO +#define MULTIBOOT_MMAP_LEN MULTIBOOT_INFO + 44 +#define MULTIBOOT_MMAP_ADDR MULTIBOOT_INFO + 48 + +#define E820_MAP MULTIBOOT_INFO + 52 +#define E820_MAP4 MULTIBOOT_INFO + 56 + +/* Important loader physical addresses. */ +#define LOADER_SIG (LOADER_END - LOADER_SIG_LEN) /* 0xaa55 BIOS signature. */ +#define LOADER_ARGS (LOADER_SIG - LOADER_ARGS_LEN) /* Command-line args. */ +#define LOADER_ARG_CNT (LOADER_ARGS - LOADER_ARG_CNT_LEN) /* Number of args. */ + +/* Sizes of loader data structures. */ +#define LOADER_SIG_LEN 2 +#define LOADER_ARGS_LEN 128 +#define LOADER_ARG_CNT_LEN 4 + +/* GDT selectors defined by loader. + More selectors are defined by userprog/gdt.h. */ +#define SEL_NULL 0x00 /* Null selector. */ +#define SEL_KCSEG 0x08 /* Kernel code selector. */ +#define SEL_KDSEG 0x10 /* Kernel data selector. */ + +#endif /* threads/loader.h */ diff --git a/include/threads/malloc.h b/include/threads/malloc.h new file mode 100644 index 0000000..bc55d36 --- /dev/null +++ b/include/threads/malloc.h @@ -0,0 +1,13 @@ +#ifndef THREADS_MALLOC_H +#define THREADS_MALLOC_H + +#include +#include + +void malloc_init (void); +void *malloc (size_t) __attribute__ ((malloc)); +void *calloc (size_t, size_t) __attribute__ ((malloc)); +void *realloc (void *, size_t); +void free (void *); + +#endif /* threads/malloc.h */ diff --git a/include/threads/mmu.h b/include/threads/mmu.h new file mode 100644 index 0000000..4dd504d --- /dev/null +++ b/include/threads/mmu.h @@ -0,0 +1,32 @@ +#ifndef THREAD_MMU_H +#define THREAD_MMU_H + +#include +#include +#include "threads/pte.h" + +typedef void pte_for_each_func (uint64_t *pte, void *aux); + +uint64_t *pml4e_walk (uint64_t *pml4, const uint64_t va, int create); +uint64_t *pml4_create (void); +void pml4_for_each (uint64_t *, pte_for_each_func *, void *); +void pml4_destroy (uint64_t *pml4); +void pml4_activate (uint64_t *pml4); +void *pml4_get_page (uint64_t *pml4, const void *upage); +bool pml4_set_page (uint64_t *pml4, void *upage, void *kpage, bool rw); +void pml4_clear_page (uint64_t *pml4, void *upage); +bool pml4_is_dirty (uint64_t *pml4, const void *upage); +void pml4_set_dirty (uint64_t *pml4, const void *upage, bool dirty); +bool pml4_is_accessed (uint64_t *pml4, const void *upage); +void pml4_set_accessed (uint64_t *pml4, const void *upage, bool accessed); + +#define is_user_pte(pte) (*(pte) & PTE_U) +#define is_kern_pte(pte) (!is_kern_pte(pte)) + +/* Segment descriptors for x86-64. */ +struct desc_ptr { + uint16_t size; + uint64_t address; +} __attribute__((packed)); + +#endif /* thread/mm.h */ diff --git a/include/threads/palloc.h b/include/threads/palloc.h new file mode 100644 index 0000000..4ed170f --- /dev/null +++ b/include/threads/palloc.h @@ -0,0 +1,23 @@ +#ifndef THREADS_PALLOC_H +#define THREADS_PALLOC_H + +#include +#include + +/* How to allocate pages. */ +enum palloc_flags { + PAL_ASSERT = 001, /* Panic on failure. */ + PAL_ZERO = 002, /* Zero page contents. */ + PAL_USER = 004 /* User page. */ +}; + +/* Maximum number of pages to put in user pool. */ +extern size_t user_page_limit; + +uint64_t palloc_init (void); +void *palloc_get_page (enum palloc_flags); +void *palloc_get_multiple (enum palloc_flags, size_t page_cnt); +void palloc_free_page (void *); +void palloc_free_multiple (void *, size_t page_cnt); + +#endif /* threads/palloc.h */ diff --git a/include/threads/pte.h b/include/threads/pte.h new file mode 100644 index 0000000..2deeb2c --- /dev/null +++ b/include/threads/pte.h @@ -0,0 +1,45 @@ +#ifndef THREADS_PTE_H +#define THREADS_PTE_H + +#include "threads/vaddr.h" + +/* Functions and macros for working with x86 hardware page tables. + * See vaddr.h for more generic functions and macros for virtual addresses. + * + * Virtual addresses are structured as follows: + * 63 48 47 39 38 30 29 21 20 12 11 0 + * +-------------+----------------+----------------+----------------+-------------+------------+ + * | Sign Extend | Page-Map | Page-Directory | Page-directory | Page-Table | Physical | + * | | Level-4 Offset | Pointer | Offset | Offset | Offset | + * +-------------+----------------+----------------+----------------+-------------+------------+ + * | | | | | | + * +------- 9 ------+------- 9 ------+------- 9 ------+----- 9 -----+---- 12 ----+ + * Virtual Address + */ + +#define PML4SHIFT 39UL +#define PDPESHIFT 30UL +#define PDXSHIFT 21UL +#define PTXSHIFT 12UL + +#define PML4(la) ((((uint64_t) (la)) >> PML4SHIFT) & 0x1FF) +#define PDPE(la) ((((uint64_t) (la)) >> PDPESHIFT) & 0x1FF) +#define PDX(la) ((((uint64_t) (la)) >> PDXSHIFT) & 0x1FF) +#define PTX(la) ((((uint64_t) (la)) >> PTXSHIFT) & 0x1FF) +#define PTE_ADDR(pte) ((uint64_t) (pte) & ~0xFFF) + +/* The important flags are listed below. + When a PDE or PTE is not "present", the other flags are + ignored. + A PDE or PTE that is initialized to 0 will be interpreted as + "not present", which is just fine. */ +#define PTE_FLAGS 0x00000000000000fffUL /* Flag bits. */ +#define PTE_ADDR_MASK 0xffffffffffffff000UL /* Address bits. */ +#define PTE_AVL 0x00000e00 /* Bits available for OS use. */ +#define PTE_P 0x1 /* 1=present, 0=not present. */ +#define PTE_W 0x2 /* 1=read/write, 0=read-only. */ +#define PTE_U 0x4 /* 1=user/kernel, 0=kernel only. */ +#define PTE_A 0x20 /* 1=accessed, 0=not acccessed. */ +#define PTE_D 0x40 /* 1=dirty, 0=not dirty (PTEs only). */ + +#endif /* threads/pte.h */ diff --git a/include/threads/synch.h b/include/threads/synch.h new file mode 100644 index 0000000..3d089fd --- /dev/null +++ b/include/threads/synch.h @@ -0,0 +1,48 @@ +#ifndef THREADS_SYNCH_H +#define THREADS_SYNCH_H + +#include +#include + +/* A counting semaphore. */ +struct semaphore { + unsigned value; /* Current value. */ + struct list waiters; /* List of waiting threads. */ +}; + +void sema_init (struct semaphore *, unsigned value); +void sema_down (struct semaphore *); +bool sema_try_down (struct semaphore *); +void sema_up (struct semaphore *); +void sema_self_test (void); + +/* Lock. */ +struct lock { + struct thread *holder; /* Thread holding lock (for debugging). */ + struct semaphore semaphore; /* Binary semaphore controlling access. */ +}; + +void lock_init (struct lock *); +void lock_acquire (struct lock *); +bool lock_try_acquire (struct lock *); +void lock_release (struct lock *); +bool lock_held_by_current_thread (const struct lock *); + +/* Condition variable. */ +struct condition { + struct list waiters; /* List of waiting threads. */ +}; + +void cond_init (struct condition *); +void cond_wait (struct condition *, struct lock *); +void cond_signal (struct condition *, struct lock *); +void cond_broadcast (struct condition *, struct lock *); + +/* Optimization barrier. + * + * The compiler will not reorder operations across an + * optimization barrier. See "Optimization Barriers" in the + * reference guide for more information.*/ +#define barrier() asm volatile ("" : : : "memory") + +#endif /* threads/synch.h */ diff --git a/include/threads/thread.h b/include/threads/thread.h new file mode 100644 index 0000000..e0707dd --- /dev/null +++ b/include/threads/thread.h @@ -0,0 +1,138 @@ +#ifndef THREADS_THREAD_H +#define THREADS_THREAD_H + +#include +#include +#include +#include "threads/interrupt.h" + +/* States in a thread's life cycle. */ +enum thread_status { + THREAD_RUNNING, /* Running thread. */ + THREAD_READY, /* Not running but ready to run. */ + THREAD_BLOCKED, /* Waiting for an event to trigger. */ + THREAD_DYING /* About to be destroyed. */ +}; + +/* Thread identifier type. + You can redefine this to whatever type you like. */ +typedef int tid_t; +#define TID_ERROR ((tid_t) -1) /* Error value for tid_t. */ + +/* Thread priorities. */ +#define PRI_MIN 0 /* Lowest priority. */ +#define PRI_DEFAULT 31 /* Default priority. */ +#define PRI_MAX 63 /* Highest priority. */ + +/* A kernel thread or user process. + * + * Each thread structure is stored in its own 4 kB page. The + * thread structure itself sits at the very bottom of the page + * (at offset 0). The rest of the page is reserved for the + * thread's kernel stack, which grows downward from the top of + * the page (at offset 4 kB). Here's an illustration: + * + * 4 kB +---------------------------------+ + * | kernel stack | + * | | | + * | | | + * | V | + * | grows downward | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * +---------------------------------+ + * | magic | + * | intr_frame | + * | : | + * | : | + * | name | + * | status | + * 0 kB +---------------------------------+ + * + * The upshot of this is twofold: + * + * 1. First, `struct thread' must not be allowed to grow too + * big. If it does, then there will not be enough room for + * the kernel stack. Our base `struct thread' is only a + * few bytes in size. It probably should stay well under 1 + * kB. + * + * 2. Second, kernel stacks must not be allowed to grow too + * large. If a stack overflows, it will corrupt the thread + * state. Thus, kernel functions should not allocate large + * structures or arrays as non-static local variables. Use + * dynamic allocation with malloc() or palloc_get_page() + * instead. + * + * The first symptom of either of these problems will probably be + * an assertion failure in thread_current(), which checks that + * the `magic' member of the running thread's `struct thread' is + * set to THREAD_MAGIC. Stack overflow will normally change this + * value, triggering the assertion. */ +/* The `elem' member has a dual purpose. It can be an element in + * the run queue (thread.c), or it can be an element in a + * semaphore wait list (synch.c). It can be used these two ways + * only because they are mutually exclusive: only a thread in the + * ready state is on the run queue, whereas only a thread in the + * blocked state is on a semaphore wait list. */ +struct thread { + /* Owned by thread.c. */ + tid_t tid; /* Thread identifier. */ + enum thread_status status; /* Thread state. */ + char name[16]; /* Name (for debugging purposes). */ + int priority; /* Priority. */ + + /* Shared between thread.c and synch.c. */ + struct list_elem elem; /* List element. */ + +#ifdef USERPROG + /* Owned by userprog/process.c. */ + uint64_t *pml4; /* Page map level 4 */ +#endif + + /* Owned by thread.c. */ + struct intr_frame tf; /* Information for switching */ + unsigned magic; /* Detects stack overflow. */ +}; + +/* If false (default), use round-robin scheduler. + If true, use multi-level feedback queue scheduler. + Controlled by kernel command-line option "-o mlfqs". */ +extern bool thread_mlfqs; + +void thread_init (void); +void thread_start (void); + +void thread_tick (void); +void thread_print_stats (void); + +typedef void thread_func (void *aux); +tid_t thread_create (const char *name, int priority, thread_func *, void *); + +void thread_block (void); +void thread_unblock (struct thread *); + +struct thread *thread_current (void); +tid_t thread_tid (void); +const char *thread_name (void); + +void thread_exit (void) NO_RETURN; +void thread_yield (void); + +int thread_get_priority (void); +void thread_set_priority (int); + +int thread_get_nice (void); +void thread_set_nice (int); +int thread_get_recent_cpu (void); +int thread_get_load_avg (void); + +void do_iret (struct intr_frame *tf); + +#endif /* threads/thread.h */ diff --git a/include/threads/vaddr.h b/include/threads/vaddr.h new file mode 100644 index 0000000..ac4c624 --- /dev/null +++ b/include/threads/vaddr.h @@ -0,0 +1,59 @@ +#ifndef THREADS_VADDR_H +#define THREADS_VADDR_H + +#include +#include +#include + +#include "threads/loader.h" + +/* Functions and macros for working with virtual addresses. + * + * See pte.h for functions and macros specifically for x86 + * hardware page tables. */ + +#define BITMASK(SHIFT, CNT) (((1ul << (CNT)) - 1) << (SHIFT)) + +/* Page offset (bits 0:12). */ +#define PGSHIFT 0 /* Index of first offset bit. */ +#define PGBITS 12 /* Number of offset bits. */ +#define PGSIZE (1 << PGBITS) /* Bytes in a page. */ +#define PGMASK BITMASK(PGSHIFT, PGBITS) /* Page offset bits (0:12). */ + +/* Offset within a page. */ +#define pg_ofs(va) ((uint64_t) (va) & PGMASK) + +#define pg_no(va) ((uint64_t) (va) >> PGBITS) + +/* Round up to nearest page boundary. */ +#define pg_round_up(va) ((void *) (((uint64_t) (va) + PGSIZE - 1) & ~PGMASK)) + +/* Round down to nearest page boundary. */ +#define pg_round_down(va) (void *) ((uint64_t) (va) & ~PGMASK) + +/* Kernel virtual address start */ +#define KERN_BASE LOADER_KERN_BASE + +/* User stack start */ +#define USER_STACK 0x47480000 + +/* Returns true if VADDR is a user virtual address. */ +#define is_user_vaddr(vaddr) (!is_kernel_vaddr((vaddr))) + +/* Returns true if VADDR is a kernel virtual address. */ +#define is_kernel_vaddr(vaddr) ((uint64_t)(vaddr) >= KERN_BASE) + +// FIXME: add checking +/* Returns kernel virtual address at which physical address PADDR + * is mapped. */ +#define ptov(paddr) ((void *) (((uint64_t) paddr) + KERN_BASE)) + +/* Returns physical address at which kernel virtual address VADDR + * is mapped. */ +#define vtop(vaddr) \ +({ \ + ASSERT(is_kernel_vaddr(vaddr)); \ + ((uint64_t) (vaddr) - (uint64_t) KERN_BASE);\ +}) + +#endif /* threads/vaddr.h */ diff --git a/lib/arithmetic.c b/lib/arithmetic.c new file mode 100644 index 0000000..7f2f191 --- /dev/null +++ b/lib/arithmetic.c @@ -0,0 +1,171 @@ +#include + +/* On x86, division of one 64-bit integer by another cannot be + done with a single instruction or a short sequence. Thus, GCC + implements 64-bit division and remainder operations through + function calls. These functions are normally obtained from + libgcc, which is automatically included by GCC in any link + that it does. + + Some x86-64 machines, however, have a compiler and utilities + that can generate 32-bit x86 code without having any of the + necessary libraries, including libgcc. Thus, we can make + Pintos work on these machines by simply implementing our own + 64-bit division routines, which are the only routines from + libgcc that Pintos requires. + + Completeness is another reason to include these routines. If + Pintos is completely self-contained, then that makes it that + much less mysterious. */ + +/* Uses x86 DIVL instruction to divide 64-bit N by 32-bit D to + yield a 32-bit quotient. Returns the quotient. + Traps with a divide error (#DE) if the quotient does not fit + in 32 bits. */ +static inline uint32_t +divl (uint64_t n, uint32_t d) { + uint32_t n1 = n >> 32; + uint32_t n0 = n; + uint32_t q, r; + + asm ("divl %4" + : "=d" (r), "=a" (q) + : "0" (n1), "1" (n0), "rm" (d)); + + return q; +} + +/* Returns the number of leading zero bits in X, + which must be nonzero. */ +static int +nlz (uint32_t x) { + /* This technique is portable, but there are better ways to do + it on particular systems. With sufficiently new enough GCC, + you can use __builtin_clz() to take advantage of GCC's + knowledge of how to do it. Or you can use the x86 BSR + instruction directly. */ + int n = 0; + if (x <= 0x0000FFFF) { + n += 16; + x <<= 16; + } + if (x <= 0x00FFFFFF) { + n += 8; + x <<= 8; + } + if (x <= 0x0FFFFFFF) { + n += 4; + x <<= 4; + } + if (x <= 0x3FFFFFFF) { + n += 2; + x <<= 2; + } + if (x <= 0x7FFFFFFF) + n++; + return n; +} + +/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the + quotient. */ +static uint64_t +udiv64 (uint64_t n, uint64_t d) { + if ((d >> 32) == 0) { + /* Proof of correctness: + + Let n, d, b, n1, and n0 be defined as in this function. + Let [x] be the "floor" of x. Let T = b[n1/d]. Assume d + nonzero. Then: + [n/d] = [n/d] - T + T + = [n/d - T] + T by (1) below + = [(b*n1 + n0)/d - T] + T by definition of n + = [(b*n1 + n0)/d - dT/d] + T + = [(b(n1 - d[n1/d]) + n0)/d] + T + = [(b[n1 % d] + n0)/d] + T, by definition of % + which is the expression calculated below. + + (1) Note that for any real x, integer i: [x] + i = [x + i]. + + To prevent divl() from trapping, [(b[n1 % d] + n0)/d] must + be less than b. Assume that [n1 % d] and n0 take their + respective maximum values of d - 1 and b - 1: + [(b(d - 1) + (b - 1))/d] < b + <=> [(bd - 1)/d] < b + <=> [b - 1/d] < b + which is a tautology. + + Therefore, this code is correct and will not trap. */ + uint64_t b = 1ULL << 32; + uint32_t n1 = n >> 32; + uint32_t n0 = n; + uint32_t d0 = d; + + return divl (b * (n1 % d0) + n0, d0) + b * (n1 / d0); + } else { + /* Based on the algorithm and proof available from + * http://www.hackersdelight.org/revisions.pdf. */ + if (n < d) + return 0; + else { + uint32_t d1 = d >> 32; + int s = nlz (d1); + uint64_t q = divl (n >> 1, (d << s) >> 32) >> (31 - s); + return n - (q - 1) * d < d ? q - 1 : q; + } + } +} + +/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the + remainder. */ +static uint32_t +umod64 (uint64_t n, uint64_t d) { + return n - d * udiv64 (n, d); +} + +/* Divides signed 64-bit N by signed 64-bit D and returns the + quotient. */ +static int64_t +sdiv64 (int64_t n, int64_t d) { + uint64_t n_abs = n >= 0 ? (uint64_t) n : -(uint64_t) n; + uint64_t d_abs = d >= 0 ? (uint64_t) d : -(uint64_t) d; + uint64_t q_abs = udiv64 (n_abs, d_abs); + return (n < 0) == (d < 0) ? (int64_t) q_abs : -(int64_t) q_abs; +} + +/* Divides signed 64-bit N by signed 64-bit D and returns the + remainder. */ +static int32_t +smod64 (int64_t n, int64_t d) { + return n - d * sdiv64 (n, d); +} + +/* These are the routines that GCC calls. */ + +long long __divdi3 (long long n, long long d); +long long __moddi3 (long long n, long long d); +unsigned long long __udivdi3 (unsigned long long n, unsigned long long d); +unsigned long long __umoddi3 (unsigned long long n, unsigned long long d); + +/* Signed 64-bit division. */ +long long +__divdi3 (long long n, long long d) { + return sdiv64 (n, d); +} + +/* Signed 64-bit remainder. */ +long long +__moddi3 (long long n, long long d) { + return smod64 (n, d); +} + +/* Unsigned 64-bit division. */ +unsigned long long +__udivdi3 (unsigned long long n, unsigned long long d) { + return udiv64 (n, d); +} + +/* Unsigned 64-bit remainder. */ +unsigned long long +__umoddi3 (unsigned long long n, unsigned long long d) { + return umod64 (n, d); +} diff --git a/lib/debug.c b/lib/debug.c new file mode 100644 index 0000000..b886b3a --- /dev/null +++ b/lib/debug.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include + +/* Prints the call stack, that is, a list of addresses, one in + each of the functions we are nested within. gdb or addr2line + may be applied to kernel.o to translate these into file names, + line numbers, and function names. */ +void +debug_backtrace (void) { + static bool explained; + void **frame; + + printf ("Call stack:"); + for (frame = __builtin_frame_address (0); + frame != NULL && frame[0] != NULL; + frame = frame[0]) + printf (" %p", frame[1]); + printf (".\n"); + + if (!explained) { + explained = true; + printf ("The `backtrace' program can make call stacks useful.\n" + "Read \"Backtraces\" in the \"Debugging Tools\" chapter\n" + "of the Pintos documentation for more information.\n"); + } +} diff --git a/lib/kernel/bitmap.c b/lib/kernel/bitmap.c new file mode 100644 index 0000000..cf8c705 --- /dev/null +++ b/lib/kernel/bitmap.c @@ -0,0 +1,338 @@ +#include "bitmap.h" +#include +#include +#include +#include +#include "threads/malloc.h" +#ifdef FILESYS +#include "filesys/file.h" +#endif + +/* Element type. + + This must be an unsigned integer type at least as wide as int. + + Each bit represents one bit in the bitmap. + If bit 0 in an element represents bit K in the bitmap, + then bit 1 in the element represents bit K+1 in the bitmap, + and so on. */ +typedef unsigned long elem_type; + +/* Number of bits in an element. */ +#define ELEM_BITS (sizeof (elem_type) * CHAR_BIT) + +/* From the outside, a bitmap is an array of bits. From the + inside, it's an array of elem_type (defined above) that + simulates an array of bits. */ +struct bitmap { + size_t bit_cnt; /* Number of bits. */ + elem_type *bits; /* Elements that represent bits. */ +}; + +/* Returns the index of the element that contains the bit + numbered BIT_IDX. */ +static inline size_t +elem_idx (size_t bit_idx) { + return bit_idx / ELEM_BITS; +} + +/* Returns an elem_type where only the bit corresponding to + BIT_IDX is turned on. */ +static inline elem_type +bit_mask (size_t bit_idx) { + return (elem_type) 1 << (bit_idx % ELEM_BITS); +} + +/* Returns the number of elements required for BIT_CNT bits. */ +static inline size_t +elem_cnt (size_t bit_cnt) { + return DIV_ROUND_UP (bit_cnt, ELEM_BITS); +} + +/* Returns the number of bytes required for BIT_CNT bits. */ +static inline size_t +byte_cnt (size_t bit_cnt) { + return sizeof (elem_type) * elem_cnt (bit_cnt); +} + +/* Returns a bit mask in which the bits actually used in the last + element of B's bits are set to 1 and the rest are set to 0. */ +static inline elem_type +last_mask (const struct bitmap *b) { + int last_bits = b->bit_cnt % ELEM_BITS; + return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1; +} + +/* Creation and destruction. */ + +/* Initializes B to be a bitmap of BIT_CNT bits + and sets all of its bits to false. + Returns true if success, false if memory allocation + failed. */ +struct bitmap * +bitmap_create (size_t bit_cnt) { + struct bitmap *b = malloc (sizeof *b); + if (b != NULL) { + b->bit_cnt = bit_cnt; + b->bits = malloc (byte_cnt (bit_cnt)); + if (b->bits != NULL || bit_cnt == 0) { + bitmap_set_all (b, false); + return b; + } + free (b); + } + return NULL; +} + +/* Creates and returns a bitmap with BIT_CNT bits in the + BLOCK_SIZE bytes of storage preallocated at BLOCK. + BLOCK_SIZE must be at least bitmap_needed_bytes(BIT_CNT). */ +struct bitmap * +bitmap_create_in_buf (size_t bit_cnt, void *block, size_t block_size UNUSED) { + struct bitmap *b = block; + + ASSERT (block_size >= bitmap_buf_size (bit_cnt)); + + b->bit_cnt = bit_cnt; + b->bits = (elem_type *) (b + 1); + bitmap_set_all (b, false); + return b; +} + +/* Returns the number of bytes required to accomodate a bitmap + with BIT_CNT bits (for use with bitmap_create_in_buf()). */ +size_t +bitmap_buf_size (size_t bit_cnt) { + return sizeof (struct bitmap) + byte_cnt (bit_cnt); +} + +/* Destroys bitmap B, freeing its storage. + Not for use on bitmaps created by + bitmap_create_preallocated(). */ +void +bitmap_destroy (struct bitmap *b) { + if (b != NULL) { + free (b->bits); + free (b); + } +} + +/* Bitmap size. */ + +/* Returns the number of bits in B. */ +size_t +bitmap_size (const struct bitmap *b) { + return b->bit_cnt; +} + +/* Setting and testing single bits. */ + +/* Atomically sets the bit numbered IDX in B to VALUE. */ +void +bitmap_set (struct bitmap *b, size_t idx, bool value) { + ASSERT (b != NULL); + ASSERT (idx < b->bit_cnt); + if (value) + bitmap_mark (b, idx); + else + bitmap_reset (b, idx); +} + +/* Atomically sets the bit numbered BIT_IDX in B to true. */ +void +bitmap_mark (struct bitmap *b, size_t bit_idx) { + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] |= mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the OR instruction in [IA32-v2b]. */ + asm ("lock orq %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc"); +} + +/* Atomically sets the bit numbered BIT_IDX in B to false. */ +void +bitmap_reset (struct bitmap *b, size_t bit_idx) { + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] &= ~mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the AND instruction in [IA32-v2a]. */ + asm ("lock andq %1, %0" : "=m" (b->bits[idx]) : "r" (~mask) : "cc"); +} + +/* Atomically toggles the bit numbered IDX in B; + that is, if it is true, makes it false, + and if it is false, makes it true. */ +void +bitmap_flip (struct bitmap *b, size_t bit_idx) { + size_t idx = elem_idx (bit_idx); + elem_type mask = bit_mask (bit_idx); + + /* This is equivalent to `b->bits[idx] ^= mask' except that it + is guaranteed to be atomic on a uniprocessor machine. See + the description of the XOR instruction in [IA32-v2b]. */ + asm ("lock xorq %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc"); +} + +/* Returns the value of the bit numbered IDX in B. */ +bool +bitmap_test (const struct bitmap *b, size_t idx) { + ASSERT (b != NULL); + ASSERT (idx < b->bit_cnt); + return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0; +} + +/* Setting and testing multiple bits. */ + +/* Sets all bits in B to VALUE. */ +void +bitmap_set_all (struct bitmap *b, bool value) { + ASSERT (b != NULL); + + bitmap_set_multiple (b, 0, bitmap_size (b), value); +} + +/* Sets the CNT bits starting at START in B to VALUE. */ +void +bitmap_set_multiple (struct bitmap *b, size_t start, size_t cnt, bool value) { + size_t i; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + for (i = 0; i < cnt; i++) + bitmap_set (b, start + i, value); +} + +/* Returns the number of bits in B between START and START + CNT, + exclusive, that are set to VALUE. */ +size_t +bitmap_count (const struct bitmap *b, size_t start, size_t cnt, bool value) { + size_t i, value_cnt; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + value_cnt = 0; + for (i = 0; i < cnt; i++) + if (bitmap_test (b, start + i) == value) + value_cnt++; + return value_cnt; +} + +/* Returns true if any bits in B between START and START + CNT, + exclusive, are set to VALUE, and false otherwise. */ +bool +bitmap_contains (const struct bitmap *b, size_t start, size_t cnt, bool value) { + size_t i; + + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + ASSERT (start + cnt <= b->bit_cnt); + + for (i = 0; i < cnt; i++) + if (bitmap_test (b, start + i) == value) + return true; + return false; +} + +/* Returns true if any bits in B between START and START + CNT, + exclusive, are set to true, and false otherwise.*/ +bool +bitmap_any (const struct bitmap *b, size_t start, size_t cnt) { + return bitmap_contains (b, start, cnt, true); +} + +/* Returns true if no bits in B between START and START + CNT, + exclusive, are set to true, and false otherwise.*/ +bool +bitmap_none (const struct bitmap *b, size_t start, size_t cnt) { + return !bitmap_contains (b, start, cnt, true); +} + +/* Returns true if every bit in B between START and START + CNT, + exclusive, is set to true, and false otherwise. */ +bool +bitmap_all (const struct bitmap *b, size_t start, size_t cnt) { + return !bitmap_contains (b, start, cnt, false); +} + +/* Finding set or unset bits. */ + +/* Finds and returns the starting index of the first group of CNT + consecutive bits in B at or after START that are all set to + VALUE. + If there is no such group, returns BITMAP_ERROR. */ +size_t +bitmap_scan (const struct bitmap *b, size_t start, size_t cnt, bool value) { + ASSERT (b != NULL); + ASSERT (start <= b->bit_cnt); + + if (cnt <= b->bit_cnt) { + size_t last = b->bit_cnt - cnt; + size_t i; + for (i = start; i <= last; i++) + if (!bitmap_contains (b, i, cnt, !value)) + return i; + } + return BITMAP_ERROR; +} + +/* Finds the first group of CNT consecutive bits in B at or after + START that are all set to VALUE, flips them all to !VALUE, + and returns the index of the first bit in the group. + If there is no such group, returns BITMAP_ERROR. + If CNT is zero, returns 0. + Bits are set atomically, but testing bits is not atomic with + setting them. */ +size_t +bitmap_scan_and_flip (struct bitmap *b, size_t start, size_t cnt, bool value) { + size_t idx = bitmap_scan (b, start, cnt, value); + if (idx != BITMAP_ERROR) + bitmap_set_multiple (b, idx, cnt, !value); + return idx; +} + +/* File input and output. */ + +#ifdef FILESYS +/* Returns the number of bytes needed to store B in a file. */ +size_t +bitmap_file_size (const struct bitmap *b) { + return byte_cnt (b->bit_cnt); +} + +/* Reads B from FILE. Returns true if successful, false + otherwise. */ +bool +bitmap_read (struct bitmap *b, struct file *file) { + bool success = true; + if (b->bit_cnt > 0) { + off_t size = byte_cnt (b->bit_cnt); + success = file_read_at (file, b->bits, size, 0) == size; + b->bits[elem_cnt (b->bit_cnt) - 1] &= last_mask (b); + } + return success; +} + +/* Writes B to FILE. Return true if successful, false + otherwise. */ +bool +bitmap_write (const struct bitmap *b, struct file *file) { + off_t size = byte_cnt (b->bit_cnt); + return file_write_at (file, b->bits, size, 0) == size; +} +#endif /* FILESYS */ + +/* Debugging. */ + +/* Dumps the contents of B to the console as hexadecimal. */ +void +bitmap_dump (const struct bitmap *b) { + hex_dump (0, b->bits, byte_cnt (b->bit_cnt), false); +} + diff --git a/lib/kernel/console.c b/lib/kernel/console.c new file mode 100644 index 0000000..93a25cd --- /dev/null +++ b/lib/kernel/console.c @@ -0,0 +1,177 @@ +#include +#include +#include +#include "devices/serial.h" +#include "devices/vga.h" +#include "threads/init.h" +#include "threads/interrupt.h" +#include "threads/synch.h" + +static void vprintf_helper (char, void *); +static void putchar_have_lock (uint8_t c); + +/* The console lock. + Both the vga and serial layers do their own locking, so it's + safe to call them at any time. + But this lock is useful to prevent simultaneous printf() calls + from mixing their output, which looks confusing. */ +static struct lock console_lock; + +/* True in ordinary circumstances: we want to use the console + lock to avoid mixing output between threads, as explained + above. + + False in early boot before the point that locks are functional + or the console lock has been initialized, or after a kernel + panics. In the former case, taking the lock would cause an + assertion failure, which in turn would cause a panic, turning + it into the latter case. In the latter case, if it is a buggy + lock_acquire() implementation that caused the panic, we'll + likely just recurse. */ +static bool use_console_lock; + +/* It's possible, if you add enough debug output to Pintos, to + try to recursively grab console_lock from a single thread. As + a real example, I added a printf() call to palloc_free(). + Here's a real backtrace that resulted: + + lock_console() + vprintf() + printf() - palloc() tries to grab the lock again + palloc_free() + schedule_tail() - another thread dying as we switch threads + schedule() + thread_yield() + intr_handler() - timer interrupt + intr_set_level() + serial_putc() + putchar_have_lock() + putbuf() + sys_write() - one process writing to the console + syscall_handler() + intr_handler() + + This kind of thing is very difficult to debug, so we avoid the + problem by simulating a recursive lock with a depth + counter. */ +static int console_lock_depth; + +/* Number of characters written to console. */ +static int64_t write_cnt; + +/* Enable console locking. */ +void +console_init (void) { + lock_init (&console_lock); + use_console_lock = true; +} + +/* Notifies the console that a kernel panic is underway, + which warns it to avoid trying to take the console lock from + now on. */ +void +console_panic (void) { + use_console_lock = false; +} + +/* Prints console statistics. */ +void +console_print_stats (void) { + printf ("Console: %lld characters output\n", write_cnt); +} + +/* Acquires the console lock. */ + static void +acquire_console (void) { + if (!intr_context () && use_console_lock) { + if (lock_held_by_current_thread (&console_lock)) + console_lock_depth++; + else + lock_acquire (&console_lock); + } +} + +/* Releases the console lock. */ +static void +release_console (void) { + if (!intr_context () && use_console_lock) { + if (console_lock_depth > 0) + console_lock_depth--; + else + lock_release (&console_lock); + } +} + +/* Returns true if the current thread has the console lock, + false otherwise. */ +static bool +console_locked_by_current_thread (void) { + return (intr_context () + || !use_console_lock + || lock_held_by_current_thread (&console_lock)); +} + +/* The standard vprintf() function, + which is like printf() but uses a va_list. + Writes its output to both vga display and serial port. */ +int +vprintf (const char *format, va_list args) { + int char_cnt = 0; + + acquire_console (); + __vprintf (format, args, vprintf_helper, &char_cnt); + release_console (); + + return char_cnt; +} + +/* Writes string S to the console, followed by a new-line + character. */ +int +puts (const char *s) { + acquire_console (); + while (*s != '\0') + putchar_have_lock (*s++); + putchar_have_lock ('\n'); + release_console (); + + return 0; +} + +/* Writes the N characters in BUFFER to the console. */ +void +putbuf (const char *buffer, size_t n) { + acquire_console (); + while (n-- > 0) + putchar_have_lock (*buffer++); + release_console (); +} + +/* Writes C to the vga display and serial port. */ +int +putchar (int c) { + acquire_console (); + putchar_have_lock (c); + release_console (); + + return c; +} + +/* Helper function for vprintf(). */ +static void +vprintf_helper (char c, void *char_cnt_) { + int *char_cnt = char_cnt_; + (*char_cnt)++; + putchar_have_lock (c); +} + +/* Writes C to the vga display and serial port. + The caller has already acquired the console lock if + appropriate. */ +static void +putchar_have_lock (uint8_t c) { + ASSERT (console_locked_by_current_thread ()); + write_cnt++; + serial_putc (c); + vga_putc (c); +} diff --git a/lib/kernel/debug.c b/lib/kernel/debug.c new file mode 100644 index 0000000..6c3e956 --- /dev/null +++ b/lib/kernel/debug.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include "threads/init.h" +#include "threads/interrupt.h" +#include "devices/serial.h" + +/* Halts the OS, printing the source file name, line number, and + function name, plus a user-specific message. */ +void +debug_panic (const char *file, int line, const char *function, + const char *message, ...) { + static int level; + va_list args; + + intr_disable (); + console_panic (); + + level++; + if (level == 1) { + printf ("Kernel PANIC at %s:%d in %s(): ", file, line, function); + + va_start (args, message); + vprintf (message, args); + printf ("\n"); + va_end (args); + + debug_backtrace (); + } else if (level == 2) + printf ("Kernel PANIC recursion at %s:%d in %s().\n", + file, line, function); + else { + /* Don't print anything: that's probably why we recursed. */ + } + + serial_flush (); + if (power_off_when_done) + power_off (); + for (;;); +} diff --git a/lib/kernel/hash.c b/lib/kernel/hash.c new file mode 100644 index 0000000..0bd250f --- /dev/null +++ b/lib/kernel/hash.c @@ -0,0 +1,394 @@ +/* Hash table. + + This data structure is thoroughly documented in the Tour of + Pintos for Project 3. + + See hash.h for basic information. */ + +#include "hash.h" +#include "../debug.h" +#include "threads/malloc.h" + +#define list_elem_to_hash_elem(LIST_ELEM) \ + list_entry(LIST_ELEM, struct hash_elem, list_elem) + +static struct list *find_bucket (struct hash *, struct hash_elem *); +static struct hash_elem *find_elem (struct hash *, struct list *, + struct hash_elem *); +static void insert_elem (struct hash *, struct list *, struct hash_elem *); +static void remove_elem (struct hash *, struct hash_elem *); +static void rehash (struct hash *); + +/* Initializes hash table H to compute hash values using HASH and + compare hash elements using LESS, given auxiliary data AUX. */ +bool +hash_init (struct hash *h, + hash_hash_func *hash, hash_less_func *less, void *aux) { + h->elem_cnt = 0; + h->bucket_cnt = 4; + h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt); + h->hash = hash; + h->less = less; + h->aux = aux; + + if (h->buckets != NULL) { + hash_clear (h, NULL); + return true; + } else + return false; +} + +/* Removes all the elements from H. + + If DESTRUCTOR is non-null, then it is called for each element + in the hash. DESTRUCTOR may, if appropriate, deallocate the + memory used by the hash element. However, modifying hash + table H while hash_clear() is running, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), yields undefined behavior, + whether done in DESTRUCTOR or elsewhere. */ +void +hash_clear (struct hash *h, hash_action_func *destructor) { + size_t i; + + for (i = 0; i < h->bucket_cnt; i++) { + struct list *bucket = &h->buckets[i]; + + if (destructor != NULL) + while (!list_empty (bucket)) { + struct list_elem *list_elem = list_pop_front (bucket); + struct hash_elem *hash_elem = list_elem_to_hash_elem (list_elem); + destructor (hash_elem, h->aux); + } + + list_init (bucket); + } + + h->elem_cnt = 0; +} + +/* Destroys hash table H. + + If DESTRUCTOR is non-null, then it is first called for each + element in the hash. DESTRUCTOR may, if appropriate, + deallocate the memory used by the hash element. However, + modifying hash table H while hash_clear() is running, using + any of the functions hash_clear(), hash_destroy(), + hash_insert(), hash_replace(), or hash_delete(), yields + undefined behavior, whether done in DESTRUCTOR or + elsewhere. */ +void +hash_destroy (struct hash *h, hash_action_func *destructor) { + if (destructor != NULL) + hash_clear (h, destructor); + free (h->buckets); +} + +/* Inserts NEW into hash table H and returns a null pointer, if + no equal element is already in the table. + If an equal element is already in the table, returns it + without inserting NEW. */ +struct hash_elem * +hash_insert (struct hash *h, struct hash_elem *new) { + struct list *bucket = find_bucket (h, new); + struct hash_elem *old = find_elem (h, bucket, new); + + if (old == NULL) + insert_elem (h, bucket, new); + + rehash (h); + + return old; +} + +/* Inserts NEW into hash table H, replacing any equal element + already in the table, which is returned. */ +struct hash_elem * +hash_replace (struct hash *h, struct hash_elem *new) { + struct list *bucket = find_bucket (h, new); + struct hash_elem *old = find_elem (h, bucket, new); + + if (old != NULL) + remove_elem (h, old); + insert_elem (h, bucket, new); + + rehash (h); + + return old; +} + +/* Finds and returns an element equal to E in hash table H, or a + null pointer if no equal element exists in the table. */ +struct hash_elem * +hash_find (struct hash *h, struct hash_elem *e) { + return find_elem (h, find_bucket (h, e), e); +} + +/* Finds, removes, and returns an element equal to E in hash + table H. Returns a null pointer if no equal element existed + in the table. + + If the elements of the hash table are dynamically allocated, + or own resources that are, then it is the caller's + responsibility to deallocate them. */ +struct hash_elem * +hash_delete (struct hash *h, struct hash_elem *e) { + struct hash_elem *found = find_elem (h, find_bucket (h, e), e); + if (found != NULL) { + remove_elem (h, found); + rehash (h); + } + return found; +} + +/* Calls ACTION for each element in hash table H in arbitrary + order. + Modifying hash table H while hash_apply() is running, using + any of the functions hash_clear(), hash_destroy(), + hash_insert(), hash_replace(), or hash_delete(), yields + undefined behavior, whether done from ACTION or elsewhere. */ +void +hash_apply (struct hash *h, hash_action_func *action) { + size_t i; + + ASSERT (action != NULL); + + for (i = 0; i < h->bucket_cnt; i++) { + struct list *bucket = &h->buckets[i]; + struct list_elem *elem, *next; + + for (elem = list_begin (bucket); elem != list_end (bucket); elem = next) { + next = list_next (elem); + action (list_elem_to_hash_elem (elem), h->aux); + } + } +} + +/* Initializes I for iterating hash table H. + + Iteration idiom: + + struct hash_iterator i; + + hash_first (&i, h); + while (hash_next (&i)) + { + struct foo *f = hash_entry (hash_cur (&i), struct foo, elem); + ...do something with f... + } + + Modifying hash table H during iteration, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), invalidates all + iterators. */ +void +hash_first (struct hash_iterator *i, struct hash *h) { + ASSERT (i != NULL); + ASSERT (h != NULL); + + i->hash = h; + i->bucket = i->hash->buckets; + i->elem = list_elem_to_hash_elem (list_head (i->bucket)); +} + +/* Advances I to the next element in the hash table and returns + it. Returns a null pointer if no elements are left. Elements + are returned in arbitrary order. + + Modifying a hash table H during iteration, using any of the + functions hash_clear(), hash_destroy(), hash_insert(), + hash_replace(), or hash_delete(), invalidates all + iterators. */ +struct hash_elem * +hash_next (struct hash_iterator *i) { + ASSERT (i != NULL); + + i->elem = list_elem_to_hash_elem (list_next (&i->elem->list_elem)); + while (i->elem == list_elem_to_hash_elem (list_end (i->bucket))) { + if (++i->bucket >= i->hash->buckets + i->hash->bucket_cnt) { + i->elem = NULL; + break; + } + i->elem = list_elem_to_hash_elem (list_begin (i->bucket)); + } + + return i->elem; +} + +/* Returns the current element in the hash table iteration, or a + null pointer at the end of the table. Undefined behavior + after calling hash_first() but before hash_next(). */ +struct hash_elem * +hash_cur (struct hash_iterator *i) { + return i->elem; +} + +/* Returns the number of elements in H. */ +size_t +hash_size (struct hash *h) { + return h->elem_cnt; +} + +/* Returns true if H contains no elements, false otherwise. */ +bool +hash_empty (struct hash *h) { + return h->elem_cnt == 0; +} + +/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */ +#define FNV_32_PRIME 16777619u +#define FNV_32_BASIS 2166136261u + +/* Returns a hash of the SIZE bytes in BUF. */ +unsigned +hash_bytes (const void *buf_, size_t size) { + /* Fowler-Noll-Vo 32-bit hash, for bytes. */ + const unsigned char *buf = buf_; + unsigned hash; + + ASSERT (buf != NULL); + + hash = FNV_32_BASIS; + while (size-- > 0) + hash = (hash * FNV_32_PRIME) ^ *buf++; + + return hash; +} + +/* Returns a hash of string S. */ +unsigned +hash_string (const char *s_) { + const unsigned char *s = (const unsigned char *) s_; + unsigned hash; + + ASSERT (s != NULL); + + hash = FNV_32_BASIS; + while (*s != '\0') + hash = (hash * FNV_32_PRIME) ^ *s++; + + return hash; +} + +/* Returns a hash of integer I. */ +unsigned +hash_int (int i) { + return hash_bytes (&i, sizeof i); +} + +/* Returns the bucket in H that E belongs in. */ +static struct list * +find_bucket (struct hash *h, struct hash_elem *e) { + size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1); + return &h->buckets[bucket_idx]; +} + +/* Searches BUCKET in H for a hash element equal to E. Returns + it if found or a null pointer otherwise. */ +static struct hash_elem * +find_elem (struct hash *h, struct list *bucket, struct hash_elem *e) { + struct list_elem *i; + + for (i = list_begin (bucket); i != list_end (bucket); i = list_next (i)) { + struct hash_elem *hi = list_elem_to_hash_elem (i); + if (!h->less (hi, e, h->aux) && !h->less (e, hi, h->aux)) + return hi; + } + return NULL; +} + +/* Returns X with its lowest-order bit set to 1 turned off. */ +static inline size_t +turn_off_least_1bit (size_t x) { + return x & (x - 1); +} + +/* Returns true if X is a power of 2, otherwise false. */ +static inline size_t +is_power_of_2 (size_t x) { + return x != 0 && turn_off_least_1bit (x) == 0; +} + +/* Element per bucket ratios. */ +#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */ +#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */ +#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */ + +/* Changes the number of buckets in hash table H to match the + ideal. This function can fail because of an out-of-memory + condition, but that'll just make hash accesses less efficient; + we can still continue. */ +static void +rehash (struct hash *h) { + size_t old_bucket_cnt, new_bucket_cnt; + struct list *new_buckets, *old_buckets; + size_t i; + + ASSERT (h != NULL); + + /* Save old bucket info for later use. */ + old_buckets = h->buckets; + old_bucket_cnt = h->bucket_cnt; + + /* Calculate the number of buckets to use now. + We want one bucket for about every BEST_ELEMS_PER_BUCKET. + We must have at least four buckets, and the number of + buckets must be a power of 2. */ + new_bucket_cnt = h->elem_cnt / BEST_ELEMS_PER_BUCKET; + if (new_bucket_cnt < 4) + new_bucket_cnt = 4; + while (!is_power_of_2 (new_bucket_cnt)) + new_bucket_cnt = turn_off_least_1bit (new_bucket_cnt); + + /* Don't do anything if the bucket count wouldn't change. */ + if (new_bucket_cnt == old_bucket_cnt) + return; + + /* Allocate new buckets and initialize them as empty. */ + new_buckets = malloc (sizeof *new_buckets * new_bucket_cnt); + if (new_buckets == NULL) { + /* Allocation failed. This means that use of the hash table will + be less efficient. However, it is still usable, so + there's no reason for it to be an error. */ + return; + } + for (i = 0; i < new_bucket_cnt; i++) + list_init (&new_buckets[i]); + + /* Install new bucket info. */ + h->buckets = new_buckets; + h->bucket_cnt = new_bucket_cnt; + + /* Move each old element into the appropriate new bucket. */ + for (i = 0; i < old_bucket_cnt; i++) { + struct list *old_bucket; + struct list_elem *elem, *next; + + old_bucket = &old_buckets[i]; + for (elem = list_begin (old_bucket); + elem != list_end (old_bucket); elem = next) { + struct list *new_bucket + = find_bucket (h, list_elem_to_hash_elem (elem)); + next = list_next (elem); + list_remove (elem); + list_push_front (new_bucket, elem); + } + } + + free (old_buckets); +} + +/* Inserts E into BUCKET (in hash table H). */ +static void +insert_elem (struct hash *h, struct list *bucket, struct hash_elem *e) { + h->elem_cnt++; + list_push_front (bucket, &e->list_elem); +} + +/* Removes E from hash table H. */ +static void +remove_elem (struct hash *h, struct hash_elem *e) { + h->elem_cnt--; + list_remove (&e->list_elem); +} + diff --git a/lib/kernel/list.c b/lib/kernel/list.c new file mode 100644 index 0000000..e8ff3b9 --- /dev/null +++ b/lib/kernel/list.c @@ -0,0 +1,489 @@ +#include "list.h" +#include "../debug.h" + +/* Our doubly linked lists have two header elements: the "head" + just before the first element and the "tail" just after the + last element. The `prev' link of the front header is null, as + is the `next' link of the back header. Their other two links + point toward each other via the interior elements of the list. + + An empty list looks like this: + + +------+ +------+ + <---| head |<--->| tail |---> + +------+ +------+ + + A list with two elements in it looks like this: + + +------+ +-------+ +-------+ +------+ + <---| head |<--->| 1 |<--->| 2 |<--->| tail |<---> + +------+ +-------+ +-------+ +------+ + + The symmetry of this arrangement eliminates lots of special + cases in list processing. For example, take a look at + list_remove(): it takes only two pointer assignments and no + conditionals. That's a lot simpler than the code would be + without header elements. + + (Because only one of the pointers in each header element is used, + we could in fact combine them into a single header element + without sacrificing this simplicity. But using two separate + elements allows us to do a little bit of checking on some + operations, which can be valuable.) */ + +static bool is_sorted (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) UNUSED; + +/* Returns true if ELEM is a head, false otherwise. */ +static inline bool +is_head (struct list_elem *elem) { + return elem != NULL && elem->prev == NULL && elem->next != NULL; +} + +/* Returns true if ELEM is an interior element, + false otherwise. */ +static inline bool +is_interior (struct list_elem *elem) { + return elem != NULL && elem->prev != NULL && elem->next != NULL; +} + +/* Returns true if ELEM is a tail, false otherwise. */ +static inline bool +is_tail (struct list_elem *elem) { + return elem != NULL && elem->prev != NULL && elem->next == NULL; +} + +/* Initializes LIST as an empty list. */ +void +list_init (struct list *list) { + ASSERT (list != NULL); + list->head.prev = NULL; + list->head.next = &list->tail; + list->tail.prev = &list->head; + list->tail.next = NULL; +} + +/* Returns the beginning of LIST. */ +struct list_elem * +list_begin (struct list *list) { + ASSERT (list != NULL); + return list->head.next; +} + +/* Returns the element after ELEM in its list. If ELEM is the + last element in its list, returns the list tail. Results are + undefined if ELEM is itself a list tail. */ +struct list_elem * +list_next (struct list_elem *elem) { + ASSERT (is_head (elem) || is_interior (elem)); + return elem->next; +} + +/* Returns LIST's tail. + + list_end() is often used in iterating through a list from + front to back. See the big comment at the top of list.h for + an example. */ +struct list_elem * +list_end (struct list *list) { + ASSERT (list != NULL); + return &list->tail; +} + +/* Returns the LIST's reverse beginning, for iterating through + LIST in reverse order, from back to front. */ +struct list_elem * +list_rbegin (struct list *list) { + ASSERT (list != NULL); + return list->tail.prev; +} + +/* Returns the element before ELEM in its list. If ELEM is the + first element in its list, returns the list head. Results are + undefined if ELEM is itself a list head. */ +struct list_elem * +list_prev (struct list_elem *elem) { + ASSERT (is_interior (elem) || is_tail (elem)); + return elem->prev; +} + +/* Returns LIST's head. + + list_rend() is often used in iterating through a list in + reverse order, from back to front. Here's typical usage, + following the example from the top of list.h: + + for (e = list_rbegin (&foo_list); e != list_rend (&foo_list); + e = list_prev (e)) + { + struct foo *f = list_entry (e, struct foo, elem); + ...do something with f... + } + */ +struct list_elem * +list_rend (struct list *list) { + ASSERT (list != NULL); + return &list->head; +} + +/* Return's LIST's head. + + list_head() can be used for an alternate style of iterating + through a list, e.g.: + + e = list_head (&list); + while ((e = list_next (e)) != list_end (&list)) + { + ... + } + */ +struct list_elem * +list_head (struct list *list) { + ASSERT (list != NULL); + return &list->head; +} + +/* Return's LIST's tail. */ +struct list_elem * +list_tail (struct list *list) { + ASSERT (list != NULL); + return &list->tail; +} + +/* Inserts ELEM just before BEFORE, which may be either an + interior element or a tail. The latter case is equivalent to + list_push_back(). */ +void +list_insert (struct list_elem *before, struct list_elem *elem) { + ASSERT (is_interior (before) || is_tail (before)); + ASSERT (elem != NULL); + + elem->prev = before->prev; + elem->next = before; + before->prev->next = elem; + before->prev = elem; +} + +/* Removes elements FIRST though LAST (exclusive) from their + current list, then inserts them just before BEFORE, which may + be either an interior element or a tail. */ +void +list_splice (struct list_elem *before, + struct list_elem *first, struct list_elem *last) { + ASSERT (is_interior (before) || is_tail (before)); + if (first == last) + return; + last = list_prev (last); + + ASSERT (is_interior (first)); + ASSERT (is_interior (last)); + + /* Cleanly remove FIRST...LAST from its current list. */ + first->prev->next = last->next; + last->next->prev = first->prev; + + /* Splice FIRST...LAST into new list. */ + first->prev = before->prev; + last->next = before; + before->prev->next = first; + before->prev = last; +} + +/* Inserts ELEM at the beginning of LIST, so that it becomes the + front in LIST. */ +void +list_push_front (struct list *list, struct list_elem *elem) { + list_insert (list_begin (list), elem); +} + +/* Inserts ELEM at the end of LIST, so that it becomes the + back in LIST. */ +void +list_push_back (struct list *list, struct list_elem *elem) { + list_insert (list_end (list), elem); +} + +/* Removes ELEM from its list and returns the element that + followed it. Undefined behavior if ELEM is not in a list. + + It's not safe to treat ELEM as an element in a list after + removing it. In particular, using list_next() or list_prev() + on ELEM after removal yields undefined behavior. This means + that a naive loop to remove the elements in a list will fail: + + ** DON'T DO THIS ** + for (e = list_begin (&list); e != list_end (&list); e = list_next (e)) + { + ...do something with e... + list_remove (e); + } + ** DON'T DO THIS ** + + Here is one correct way to iterate and remove elements from a +list: + +for (e = list_begin (&list); e != list_end (&list); e = list_remove (e)) +{ +...do something with e... +} + +If you need to free() elements of the list then you need to be +more conservative. Here's an alternate strategy that works +even in that case: + +while (!list_empty (&list)) +{ +struct list_elem *e = list_pop_front (&list); +...do something with e... +} +*/ +struct list_elem * +list_remove (struct list_elem *elem) { + ASSERT (is_interior (elem)); + elem->prev->next = elem->next; + elem->next->prev = elem->prev; + return elem->next; +} + +/* Removes the front element from LIST and returns it. + Undefined behavior if LIST is empty before removal. */ +struct list_elem * +list_pop_front (struct list *list) { + struct list_elem *front = list_front (list); + list_remove (front); + return front; +} + +/* Removes the back element from LIST and returns it. + Undefined behavior if LIST is empty before removal. */ +struct list_elem * +list_pop_back (struct list *list) { + struct list_elem *back = list_back (list); + list_remove (back); + return back; +} + +/* Returns the front element in LIST. + Undefined behavior if LIST is empty. */ +struct list_elem * +list_front (struct list *list) { + ASSERT (!list_empty (list)); + return list->head.next; +} + +/* Returns the back element in LIST. + Undefined behavior if LIST is empty. */ +struct list_elem * +list_back (struct list *list) { + ASSERT (!list_empty (list)); + return list->tail.prev; +} + +/* Returns the number of elements in LIST. + Runs in O(n) in the number of elements. */ +size_t +list_size (struct list *list) { + struct list_elem *e; + size_t cnt = 0; + + for (e = list_begin (list); e != list_end (list); e = list_next (e)) + cnt++; + return cnt; +} + +/* Returns true if LIST is empty, false otherwise. */ +bool +list_empty (struct list *list) { + return list_begin (list) == list_end (list); +} + +/* Swaps the `struct list_elem *'s that A and B point to. */ +static void +swap (struct list_elem **a, struct list_elem **b) { + struct list_elem *t = *a; + *a = *b; + *b = t; +} + +/* Reverses the order of LIST. */ +void +list_reverse (struct list *list) { + if (!list_empty (list)) { + struct list_elem *e; + + for (e = list_begin (list); e != list_end (list); e = e->prev) + swap (&e->prev, &e->next); + swap (&list->head.next, &list->tail.prev); + swap (&list->head.next->prev, &list->tail.prev->next); + } +} + +/* Returns true only if the list elements A through B (exclusive) + are in order according to LESS given auxiliary data AUX. */ +static bool +is_sorted (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) { + if (a != b) + while ((a = list_next (a)) != b) + if (less (a, list_prev (a), aux)) + return false; + return true; +} + +/* Finds a run, starting at A and ending not after B, of list + elements that are in nondecreasing order according to LESS + given auxiliary data AUX. Returns the (exclusive) end of the + run. + A through B (exclusive) must form a non-empty range. */ +static struct list_elem * +find_end_of_run (struct list_elem *a, struct list_elem *b, + list_less_func *less, void *aux) { + ASSERT (a != NULL); + ASSERT (b != NULL); + ASSERT (less != NULL); + ASSERT (a != b); + + do { + a = list_next (a); + } while (a != b && !less (a, list_prev (a), aux)); + return a; +} + +/* Merges A0 through A1B0 (exclusive) with A1B0 through B1 + (exclusive) to form a combined range also ending at B1 + (exclusive). Both input ranges must be nonempty and sorted in + nondecreasing order according to LESS given auxiliary data + AUX. The output range will be sorted the same way. */ +static void +inplace_merge (struct list_elem *a0, struct list_elem *a1b0, + struct list_elem *b1, + list_less_func *less, void *aux) { + ASSERT (a0 != NULL); + ASSERT (a1b0 != NULL); + ASSERT (b1 != NULL); + ASSERT (less != NULL); + ASSERT (is_sorted (a0, a1b0, less, aux)); + ASSERT (is_sorted (a1b0, b1, less, aux)); + + while (a0 != a1b0 && a1b0 != b1) + if (!less (a1b0, a0, aux)) + a0 = list_next (a0); + else { + a1b0 = list_next (a1b0); + list_splice (a0, list_prev (a1b0), a1b0); + } +} + +/* Sorts LIST according to LESS given auxiliary data AUX, using a + natural iterative merge sort that runs in O(n lg n) time and + O(1) space in the number of elements in LIST. */ +void +list_sort (struct list *list, list_less_func *less, void *aux) { + size_t output_run_cnt; /* Number of runs output in current pass. */ + + ASSERT (list != NULL); + ASSERT (less != NULL); + + /* Pass over the list repeatedly, merging adjacent runs of + nondecreasing elements, until only one run is left. */ + do { + struct list_elem *a0; /* Start of first run. */ + struct list_elem *a1b0; /* End of first run, start of second. */ + struct list_elem *b1; /* End of second run. */ + + output_run_cnt = 0; + for (a0 = list_begin (list); a0 != list_end (list); a0 = b1) { + /* Each iteration produces one output run. */ + output_run_cnt++; + + /* Locate two adjacent runs of nondecreasing elements + A0...A1B0 and A1B0...B1. */ + a1b0 = find_end_of_run (a0, list_end (list), less, aux); + if (a1b0 == list_end (list)) + break; + b1 = find_end_of_run (a1b0, list_end (list), less, aux); + + /* Merge the runs. */ + inplace_merge (a0, a1b0, b1, less, aux); + } + } + while (output_run_cnt > 1); + + ASSERT (is_sorted (list_begin (list), list_end (list), less, aux)); +} + +/* Inserts ELEM in the proper position in LIST, which must be + sorted according to LESS given auxiliary data AUX. + Runs in O(n) average case in the number of elements in LIST. */ +void +list_insert_ordered (struct list *list, struct list_elem *elem, + list_less_func *less, void *aux) { + struct list_elem *e; + + ASSERT (list != NULL); + ASSERT (elem != NULL); + ASSERT (less != NULL); + + for (e = list_begin (list); e != list_end (list); e = list_next (e)) + if (less (elem, e, aux)) + break; + return list_insert (e, elem); +} + +/* Iterates through LIST and removes all but the first in each + set of adjacent elements that are equal according to LESS + given auxiliary data AUX. If DUPLICATES is non-null, then the + elements from LIST are appended to DUPLICATES. */ +void +list_unique (struct list *list, struct list *duplicates, + list_less_func *less, void *aux) { + struct list_elem *elem, *next; + + ASSERT (list != NULL); + ASSERT (less != NULL); + if (list_empty (list)) + return; + + elem = list_begin (list); + while ((next = list_next (elem)) != list_end (list)) + if (!less (elem, next, aux) && !less (next, elem, aux)) { + list_remove (next); + if (duplicates != NULL) + list_push_back (duplicates, next); + } else + elem = next; +} + +/* Returns the element in LIST with the largest value according + to LESS given auxiliary data AUX. If there is more than one + maximum, returns the one that appears earlier in the list. If + the list is empty, returns its tail. */ +struct list_elem * +list_max (struct list *list, list_less_func *less, void *aux) { + struct list_elem *max = list_begin (list); + if (max != list_end (list)) { + struct list_elem *e; + + for (e = list_next (max); e != list_end (list); e = list_next (e)) + if (less (max, e, aux)) + max = e; + } + return max; +} + +/* Returns the element in LIST with the smallest value according + to LESS given auxiliary data AUX. If there is more than one + minimum, returns the one that appears earlier in the list. If + the list is empty, returns its tail. */ +struct list_elem * +list_min (struct list *list, list_less_func *less, void *aux) { + struct list_elem *min = list_begin (list); + if (min != list_end (list)) { + struct list_elem *e; + + for (e = list_next (min); e != list_end (list); e = list_next (e)) + if (less (e, min, aux)) + min = e; + } + return min; +} diff --git a/lib/kernel/targets.mk b/lib/kernel/targets.mk new file mode 100644 index 0000000..405ec72 --- /dev/null +++ b/lib/kernel/targets.mk @@ -0,0 +1,5 @@ +lib/kernel_SRC = lib/kernel/debug.c # Debug helpers. +lib/kernel_SRC += lib/kernel/list.c # Doubly-linked lists. +lib/kernel_SRC += lib/kernel/bitmap.c # Bitmaps. +lib/kernel_SRC += lib/kernel/hash.c # Hash tables. +lib/kernel_SRC += lib/kernel/console.c # printf(), putchar(). diff --git a/lib/random.c b/lib/random.c new file mode 100644 index 0000000..c8a015e --- /dev/null +++ b/lib/random.c @@ -0,0 +1,77 @@ +#include "random.h" +#include +#include +#include "debug.h" + +/* RC4-based pseudo-random number generator (PRNG). + + RC4 is a stream cipher. We're not using it here for its + cryptographic properties, but because it is easy to implement + and its output is plenty random for non-cryptographic + purposes. + + See http://en.wikipedia.org/wiki/RC4_(cipher) for information + on RC4.*/ + +/* RC4 state. */ +static uint8_t s[256]; /* S[]. */ +static uint8_t s_i, s_j; /* i, j. */ + +/* Already initialized? */ +static bool inited; + +/* Swaps the bytes pointed to by A and B. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) { + uint8_t t = *a; + *a = *b; + *b = t; +} + +/* Initializes or reinitializes the PRNG with the given SEED. */ +void +random_init (unsigned seed) { + uint8_t *seedp = (uint8_t *) &seed; + int i; + uint8_t j; + + for (i = 0; i < 256; i++) + s[i] = i; + for (i = j = 0; i < 256; i++) { + j += s[i] + seedp[i % sizeof seed]; + swap_byte (s + i, s + j); + } + + s_i = s_j = 0; + inited = true; +} + +/* Writes SIZE random bytes into BUF. */ +void +random_bytes (void *buf_, size_t size) { + uint8_t *buf; + + if (!inited) + random_init (0); + + for (buf = buf_; size-- > 0; buf++) { + uint8_t s_k; + + s_i++; + s_j += s[s_i]; + swap_byte (s + s_i, s + s_j); + + s_k = s[s_i] + s[s_j]; + *buf = s[s_k]; + } +} + +/* Returns a pseudo-random unsigned long. + Use random_ulong() % n to obtain a random number in the range + 0...n (exclusive). */ +unsigned long +random_ulong (void) { + unsigned long ul; + random_bytes (&ul, sizeof ul); + return ul; +} diff --git a/lib/stdio.c b/lib/stdio.c new file mode 100644 index 0000000..8c7bbfe --- /dev/null +++ b/lib/stdio.c @@ -0,0 +1,594 @@ +#include +#include +#include +#include +#include +#include + +/* Auxiliary data for vsnprintf_helper(). */ +struct vsnprintf_aux { + char *p; /* Current output position. */ + int length; /* Length of output string. */ + int max_length; /* Max length of output string. */ +}; + +static void vsnprintf_helper (char, void *); + +/* Like vprintf(), except that output is stored into BUFFER, + which must have space for BUF_SIZE characters. Writes at most + BUF_SIZE - 1 characters to BUFFER, followed by a null + terminator. BUFFER will always be null-terminated unless + BUF_SIZE is zero. Returns the number of characters that would + have been written to BUFFER, not including a null terminator, + had there been enough room. */ +int +vsnprintf (char *buffer, size_t buf_size, const char *format, va_list args) { + /* Set up aux data for vsnprintf_helper(). */ + struct vsnprintf_aux aux; + aux.p = buffer; + aux.length = 0; + aux.max_length = buf_size > 0 ? buf_size - 1 : 0; + + /* Do most of the work. */ + __vprintf (format, args, vsnprintf_helper, &aux); + + /* Add null terminator. */ + if (buf_size > 0) + *aux.p = '\0'; + + return aux.length; +} + +/* Helper function for vsnprintf(). */ +static void +vsnprintf_helper (char ch, void *aux_) { + struct vsnprintf_aux *aux = aux_; + + if (aux->length++ < aux->max_length) + *aux->p++ = ch; +} + +/* Like printf(), except that output is stored into BUFFER, + which must have space for BUF_SIZE characters. Writes at most + BUF_SIZE - 1 characters to BUFFER, followed by a null + terminator. BUFFER will always be null-terminated unless + BUF_SIZE is zero. Returns the number of characters that would + have been written to BUFFER, not including a null terminator, + had there been enough room. */ +int +snprintf (char *buffer, size_t buf_size, const char *format, ...) { + va_list args; + int retval; + + va_start (args, format); + retval = vsnprintf (buffer, buf_size, format, args); + va_end (args); + + return retval; +} + +/* Writes formatted output to the console. + In the kernel, the console is both the video display and first + serial port. + In userspace, the console is file descriptor 1. */ +int +printf (const char *format, ...) { + va_list args; + int retval; + + va_start (args, format); + retval = vprintf (format, args); + va_end (args); + + return retval; +} + +/* printf() formatting internals. */ + +/* A printf() conversion. */ +struct printf_conversion { + /* Flags. */ + enum { + MINUS = 1 << 0, /* '-' */ + PLUS = 1 << 1, /* '+' */ + SPACE = 1 << 2, /* ' ' */ + POUND = 1 << 3, /* '#' */ + ZERO = 1 << 4, /* '0' */ + GROUP = 1 << 5 /* '\'' */ + } flags; + + /* Minimum field width. */ + int width; + + /* Numeric precision. + -1 indicates no precision was specified. */ + int precision; + + /* Type of argument to format. */ + enum { + CHAR = 1, /* hh */ + SHORT = 2, /* h */ + INT = 3, /* (none) */ + INTMAX = 4, /* j */ + LONG = 5, /* l */ + LONGLONG = 6, /* ll */ + PTRDIFFT = 7, /* t */ + SIZET = 8 /* z */ + } type; +}; + +struct integer_base { + int base; /* Base. */ + const char *digits; /* Collection of digits. */ + int x; /* `x' character to use, for base 16 only. */ + int group; /* Number of digits to group with ' flag. */ +}; + +static const struct integer_base base_d = {10, "0123456789", 0, 3}; +static const struct integer_base base_o = {8, "01234567", 0, 3}; +static const struct integer_base base_x = {16, "0123456789abcdef", 'x', 4}; +static const struct integer_base base_X = {16, "0123456789ABCDEF", 'X', 4}; + +static const char *parse_conversion (const char *format, + struct printf_conversion *, + va_list *); +static void format_integer (uintmax_t value, bool is_signed, bool negative, + const struct integer_base *, + const struct printf_conversion *, + void (*output) (char, void *), void *aux); +static void output_dup (char ch, size_t cnt, + void (*output) (char, void *), void *aux); +static void format_string (const char *string, int length, + struct printf_conversion *, + void (*output) (char, void *), void *aux); + +void +__vprintf (const char *format, va_list args, + void (*output) (char, void *), void *aux) { + for (; *format != '\0'; format++) { + struct printf_conversion c; + + /* Literally copy non-conversions to output. */ + if (*format != '%') { + output (*format, aux); + continue; + } + format++; + + /* %% => %. */ + if (*format == '%') { + output ('%', aux); + continue; + } + + /* Parse conversion specifiers. */ + format = parse_conversion (format, &c, &args); + + /* Do conversion. */ + switch (*format) { + case 'd': + case 'i': + { + /* Signed integer conversions. */ + intmax_t value; + + switch (c.type) { + case CHAR: + value = (signed char) va_arg (args, int); + break; + case SHORT: + value = (short) va_arg (args, int); + break; + case INT: + value = va_arg (args, int); + break; + case INTMAX: + value = va_arg (args, intmax_t); + break; + case LONG: + value = va_arg (args, long); + break; + case LONGLONG: + value = va_arg (args, long long); + break; + case PTRDIFFT: + value = va_arg (args, ptrdiff_t); + break; + case SIZET: + value = va_arg (args, size_t); + if (value > SIZE_MAX / 2) + value = value - SIZE_MAX - 1; + break; + default: + NOT_REACHED (); + } + + format_integer (value < 0 ? -value : value, + true, value < 0, &base_d, &c, output, aux); + } + break; + + case 'o': + case 'u': + case 'x': + case 'X': + { + /* Unsigned integer conversions. */ + uintmax_t value; + const struct integer_base *b; + + switch (c.type) { + case CHAR: + value = (unsigned char) va_arg (args, unsigned); + break; + case SHORT: + value = (unsigned short) va_arg (args, unsigned); + break; + case INT: + value = va_arg (args, unsigned); + break; + case INTMAX: + value = va_arg (args, uintmax_t); + break; + case LONG: + value = va_arg (args, unsigned long); + break; + case LONGLONG: + value = va_arg (args, unsigned long long); + break; + case PTRDIFFT: + value = va_arg (args, ptrdiff_t); +#if UINTMAX_MAX != PTRDIFF_MAX + value &= ((uintmax_t) PTRDIFF_MAX << 1) | 1; +#endif + break; + case SIZET: + value = va_arg (args, size_t); + break; + default: + NOT_REACHED (); + } + + switch (*format) { + case 'o': b = &base_o; break; + case 'u': b = &base_d; break; + case 'x': b = &base_x; break; + case 'X': b = &base_X; break; + default: NOT_REACHED (); + } + + format_integer (value, false, false, b, &c, output, aux); + } + break; + + case 'c': + { + /* Treat character as single-character string. */ + char ch = va_arg (args, int); + format_string (&ch, 1, &c, output, aux); + } + break; + + case 's': + { + /* String conversion. */ + const char *s = va_arg (args, char *); + if (s == NULL) + s = "(null)"; + + /* Limit string length according to precision. +Note: if c.precision == -1 then strnlen() will get +SIZE_MAX for MAXLEN, which is just what we want. */ + format_string (s, strnlen (s, c.precision), &c, output, aux); + } + break; + + case 'p': + { + /* Pointer conversion. + Format pointers as %#x. */ + void *p = va_arg (args, void *); + + c.flags = POUND; + format_integer ((uintptr_t) p, false, false, + &base_x, &c, output, aux); + } + break; + + case 'f': + case 'e': + case 'E': + case 'g': + case 'G': + case 'n': + /* We don't support floating-point arithmetic, + and %n can be part of a security hole. */ + __printf ("<>", output, aux, *format); + break; + + default: + __printf ("<>", output, aux, *format); + break; + } + } +} + +/* Parses conversion option characters starting at FORMAT and + initializes C appropriately. Returns the character in FORMAT + that indicates the conversion (e.g. the `d' in `%d'). Uses + *ARGS for `*' field widths and precisions. */ +static const char * +parse_conversion (const char *format, struct printf_conversion *c, + va_list *args) { + /* Parse flag characters. */ + c->flags = 0; + for (;;) { + switch (*format++) { + case '-': + c->flags |= MINUS; + break; + case '+': + c->flags |= PLUS; + break; + case ' ': + c->flags |= SPACE; + break; + case '#': + c->flags |= POUND; + break; + case '0': + c->flags |= ZERO; + break; + case '\'': + c->flags |= GROUP; + break; + default: + format--; + goto not_a_flag; + } + } +not_a_flag: + if (c->flags & MINUS) + c->flags &= ~ZERO; + if (c->flags & PLUS) + c->flags &= ~SPACE; + + /* Parse field width. */ + c->width = 0; + if (*format == '*') { + format++; + c->width = va_arg (*args, int); + } else { + for (; isdigit (*format); format++) + c->width = c->width * 10 + *format - '0'; + } + if (c->width < 0) { + c->width = -c->width; + c->flags |= MINUS; + } + + /* Parse precision. */ + c->precision = -1; + if (*format == '.') { + format++; + if (*format == '*') { + format++; + c->precision = va_arg (*args, int); + } else { + c->precision = 0; + for (; isdigit (*format); format++) + c->precision = c->precision * 10 + *format - '0'; + } + if (c->precision < 0) + c->precision = -1; + } + if (c->precision >= 0) + c->flags &= ~ZERO; + + /* Parse type. */ + c->type = INT; + switch (*format++) { + case 'h': + if (*format == 'h') { + format++; + c->type = CHAR; + } + else + c->type = SHORT; + break; + + case 'j': + c->type = INTMAX; + break; + + case 'l': + if (*format == 'l') { + format++; + c->type = LONGLONG; + } + else + c->type = LONG; + break; + + case 't': + c->type = PTRDIFFT; + break; + + case 'z': + c->type = SIZET; + break; + + default: + format--; + break; + } + + return format; +} + +/* Performs an integer conversion, writing output to OUTPUT with + auxiliary data AUX. The integer converted has absolute value + VALUE. If IS_SIGNED is true, does a signed conversion with + NEGATIVE indicating a negative value; otherwise does an + unsigned conversion and ignores NEGATIVE. The output is done + according to the provided base B. Details of the conversion + are in C. */ +static void +format_integer (uintmax_t value, bool is_signed, bool negative, + const struct integer_base *b, + const struct printf_conversion *c, + void (*output) (char, void *), void *aux) { + char buf[64], *cp; /* Buffer and current position. */ + int x; /* `x' character to use or 0 if none. */ + int sign; /* Sign character or 0 if none. */ + int precision; /* Rendered precision. */ + int pad_cnt; /* # of pad characters to fill field width. */ + int digit_cnt; /* # of digits output so far. */ + + /* Determine sign character, if any. + An unsigned conversion will never have a sign character, + even if one of the flags requests one. */ + sign = 0; + if (is_signed) { + if (c->flags & PLUS) + sign = negative ? '-' : '+'; + else if (c->flags & SPACE) + sign = negative ? '-' : ' '; + else if (negative) + sign = '-'; + } + + /* Determine whether to include `0x' or `0X'. + It will only be included with a hexadecimal conversion of a + nonzero value with the # flag. */ + x = (c->flags & POUND) && value ? b->x : 0; + + /* Accumulate digits into buffer. + This algorithm produces digits in reverse order, so later we + will output the buffer's content in reverse. */ + cp = buf; + digit_cnt = 0; + while (value > 0) { + if ((c->flags & GROUP) && digit_cnt > 0 && digit_cnt % b->group == 0) + *cp++ = ','; + *cp++ = b->digits[value % b->base]; + value /= b->base; + digit_cnt++; + } + + /* Append enough zeros to match precision. + If requested precision is 0, then a value of zero is + rendered as a null string, otherwise as "0". + If the # flag is used with base 8, the result must always + begin with a zero. */ + precision = c->precision < 0 ? 1 : c->precision; + while (cp - buf < precision && cp < buf + sizeof buf - 1) + *cp++ = '0'; + if ((c->flags & POUND) && b->base == 8 && (cp == buf || cp[-1] != '0')) + *cp++ = '0'; + + /* Calculate number of pad characters to fill field width. */ + pad_cnt = c->width - (cp - buf) - (x ? 2 : 0) - (sign != 0); + if (pad_cnt < 0) + pad_cnt = 0; + + /* Do output. */ + if ((c->flags & (MINUS | ZERO)) == 0) + output_dup (' ', pad_cnt, output, aux); + if (sign) + output (sign, aux); + if (x) { + output ('0', aux); + output (x, aux); + } + if (c->flags & ZERO) + output_dup ('0', pad_cnt, output, aux); + while (cp > buf) + output (*--cp, aux); + if (c->flags & MINUS) + output_dup (' ', pad_cnt, output, aux); +} + +/* Writes CH to OUTPUT with auxiliary data AUX, CNT times. */ +static void +output_dup (char ch, size_t cnt, void (*output) (char, void *), void *aux) { + while (cnt-- > 0) + output (ch, aux); +} + +/* Formats the LENGTH characters starting at STRING according to + the conversion specified in C. Writes output to OUTPUT with + auxiliary data AUX. */ +static void +format_string (const char *string, int length, + struct printf_conversion *c, + void (*output) (char, void *), void *aux) { + int i; + if (c->width > length && (c->flags & MINUS) == 0) + output_dup (' ', c->width - length, output, aux); + for (i = 0; i < length; i++) + output (string[i], aux); + if (c->width > length && (c->flags & MINUS) != 0) + output_dup (' ', c->width - length, output, aux); +} + +/* Wrapper for __vprintf() that converts varargs into a + va_list. */ +void +__printf (const char *format, + void (*output) (char, void *), void *aux, ...) { + va_list args; + + va_start (args, aux); + __vprintf (format, args, output, aux); + va_end (args); +} + +/* Dumps the SIZE bytes in BUF to the console as hex bytes + arranged 16 per line. Numeric offsets are also included, + starting at OFS for the first byte in BUF. If ASCII is true + then the corresponding ASCII characters are also rendered + alongside. */ +void +hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii) { + const uint8_t *buf = buf_; + const size_t per_line = 16; /* Maximum bytes per line. */ + + while (size > 0) { + size_t start, end, n; + size_t i; + + /* Number of bytes on this line. */ + start = ofs % per_line; + end = per_line; + if (end - start > size) + end = start + size; + n = end - start; + + /* Print line. */ + printf ("%016llx ", (uintmax_t) ROUND_DOWN (ofs, per_line)); + for (i = 0; i < start; i++) + printf (" "); + for (; i < end; i++) + printf ("%02hhx%c", + buf[i - start], i == per_line / 2 - 1? '-' : ' '); + if (ascii) { + for (; i < per_line; i++) + printf (" "); + printf ("|"); + for (i = 0; i < start; i++) + printf (" "); + for (; i < end; i++) + printf ("%c", + isprint (buf[i - start]) ? buf[i - start] : '.'); + for (; i < per_line; i++) + printf (" "); + printf ("|"); + } + printf ("\n"); + + ofs += n; + buf += n; + size -= n; + } +} diff --git a/lib/stdlib.c b/lib/stdlib.c new file mode 100644 index 0000000..84c7f61 --- /dev/null +++ b/lib/stdlib.c @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include + +/* Converts a string representation of a signed decimal integer + in S into an `int', which is returned. */ +int +atoi (const char *s) +{ + bool negative; + int value; + + ASSERT (s != NULL); + + /* Skip white space. */ + while (isspace ((unsigned char) *s)) + s++; + + /* Parse sign. */ + negative = false; + if (*s == '+') + s++; + else if (*s == '-') + { + negative = true; + s++; + } + + /* Parse digits. We always initially parse the value as + negative, and then make it positive later, because the + negative range of an int is bigger than the positive range + on a 2's complement system. */ + for (value = 0; isdigit (*s); s++) + value = value * 10 - (*s - '0'); + if (!negative) + value = -value; + + return value; +} + +/* Compares A and B by calling the AUX function. */ +static int +compare_thunk (const void *a, const void *b, void *aux) +{ + int (**compare) (const void *, const void *) = aux; + return (*compare) (a, b); +} + +/* Sorts ARRAY, which contains CNT elements of SIZE bytes each, + using COMPARE. When COMPARE is passed a pair of elements A + and B, respectively, it must return a strcmp()-type result, + i.e. less than zero if A < B, zero if A == B, greater than + zero if A > B. Runs in O(n lg n) time and O(1) space in + CNT. */ +void +qsort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *)) +{ + sort (array, cnt, size, compare_thunk, &compare); +} + +/* Swaps elements with 1-based indexes A_IDX and B_IDX in ARRAY + with elements of SIZE bytes each. */ +static void +do_swap (unsigned char *array, size_t a_idx, size_t b_idx, size_t size) +{ + unsigned char *a = array + (a_idx - 1) * size; + unsigned char *b = array + (b_idx - 1) * size; + size_t i; + + for (i = 0; i < size; i++) + { + unsigned char t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +/* Compares elements with 1-based indexes A_IDX and B_IDX in + ARRAY with elements of SIZE bytes each, using COMPARE to + compare elements, passing AUX as auxiliary data, and returns a + strcmp()-type result. */ +static int +do_compare (unsigned char *array, size_t a_idx, size_t b_idx, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + return compare (array + (a_idx - 1) * size, array + (b_idx - 1) * size, aux); +} + +/* "Float down" the element with 1-based index I in ARRAY of CNT + elements of SIZE bytes each, using COMPARE to compare + elements, passing AUX as auxiliary data. */ +static void +heapify (unsigned char *array, size_t i, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + for (;;) + { + /* Set `max' to the index of the largest element among I + and its children (if any). */ + size_t left = 2 * i; + size_t right = 2 * i + 1; + size_t max = i; + if (left <= cnt && do_compare (array, left, max, size, compare, aux) > 0) + max = left; + if (right <= cnt + && do_compare (array, right, max, size, compare, aux) > 0) + max = right; + + /* If the maximum value is already in element I, we're + done. */ + if (max == i) + break; + + /* Swap and continue down the heap. */ + do_swap (array, i, max, size); + i = max; + } +} + +/* Sorts ARRAY, which contains CNT elements of SIZE bytes each, + using COMPARE to compare elements, passing AUX as auxiliary + data. When COMPARE is passed a pair of elements A and B, + respectively, it must return a strcmp()-type result, i.e. less + than zero if A < B, zero if A == B, greater than zero if A > + B. Runs in O(n lg n) time and O(1) space in CNT. */ +void +sort (void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + size_t i; + + ASSERT (array != NULL || cnt == 0); + ASSERT (compare != NULL); + ASSERT (size > 0); + + /* Build a heap. */ + for (i = cnt / 2; i > 0; i--) + heapify (array, i, cnt, size, compare, aux); + + /* Sort the heap. */ + for (i = cnt; i > 1; i--) + { + do_swap (array, 1, i, size); + heapify (array, 1, i - 1, size, compare, aux); + } +} + +/* Searches ARRAY, which contains CNT elements of SIZE bytes + each, for the given KEY. Returns a match is found, otherwise + a null pointer. If there are multiple matches, returns an + arbitrary one of them. + + ARRAY must be sorted in order according to COMPARE. + + Uses COMPARE to compare elements. When COMPARE is passed a + pair of elements A and B, respectively, it must return a + strcmp()-type result, i.e. less than zero if A < B, zero if A + == B, greater than zero if A > B. */ +void * +bsearch (const void *key, const void *array, size_t cnt, + size_t size, int (*compare) (const void *, const void *)) +{ + return binary_search (key, array, cnt, size, compare_thunk, &compare); +} + +/* Searches ARRAY, which contains CNT elements of SIZE bytes + each, for the given KEY. Returns a match is found, otherwise + a null pointer. If there are multiple matches, returns an + arbitrary one of them. + + ARRAY must be sorted in order according to COMPARE. + + Uses COMPARE to compare elements, passing AUX as auxiliary + data. When COMPARE is passed a pair of elements A and B, + respectively, it must return a strcmp()-type result, i.e. less + than zero if A < B, zero if A == B, greater than zero if A > + B. */ +void * +binary_search (const void *key, const void *array, size_t cnt, size_t size, + int (*compare) (const void *, const void *, void *aux), + void *aux) +{ + const unsigned char *first = array; + const unsigned char *last = array + size * cnt; + + while (first < last) + { + size_t range = (last - first) / size; + const unsigned char *middle = first + (range / 2) * size; + int cmp = compare (key, middle, aux); + + if (cmp < 0) + last = middle; + else if (cmp > 0) + first = middle + size; + else + return (void *) middle; + } + + return NULL; +} + diff --git a/lib/string.c b/lib/string.c new file mode 100644 index 0000000..2058cc9 --- /dev/null +++ b/lib/string.c @@ -0,0 +1,347 @@ +#include +#include + +/* Copies SIZE bytes from SRC to DST, which must not overlap. + Returns DST. */ +void * +memcpy (void *dst_, const void *src_, size_t size) { + unsigned char *dst = dst_; + const unsigned char *src = src_; + + ASSERT (dst != NULL || size == 0); + ASSERT (src != NULL || size == 0); + + while (size-- > 0) + *dst++ = *src++; + + return dst_; +} + +/* Copies SIZE bytes from SRC to DST, which are allowed to + overlap. Returns DST. */ +void * +memmove (void *dst_, const void *src_, size_t size) { + unsigned char *dst = dst_; + const unsigned char *src = src_; + + ASSERT (dst != NULL || size == 0); + ASSERT (src != NULL || size == 0); + + if (dst < src) { + while (size-- > 0) + *dst++ = *src++; + } else { + dst += size; + src += size; + while (size-- > 0) + *--dst = *--src; + } + + return dst; +} + +/* Find the first differing byte in the two blocks of SIZE bytes + at A and B. Returns a positive value if the byte in A is + greater, a negative value if the byte in B is greater, or zero + if blocks A and B are equal. */ +int +memcmp (const void *a_, const void *b_, size_t size) { + const unsigned char *a = a_; + const unsigned char *b = b_; + + ASSERT (a != NULL || size == 0); + ASSERT (b != NULL || size == 0); + + for (; size-- > 0; a++, b++) + if (*a != *b) + return *a > *b ? +1 : -1; + return 0; +} + +/* Finds the first differing characters in strings A and B. + Returns a positive value if the character in A (as an unsigned + char) is greater, a negative value if the character in B (as + an unsigned char) is greater, or zero if strings A and B are + equal. */ +int +strcmp (const char *a_, const char *b_) { + const unsigned char *a = (const unsigned char *) a_; + const unsigned char *b = (const unsigned char *) b_; + + ASSERT (a != NULL); + ASSERT (b != NULL); + + while (*a != '\0' && *a == *b) { + a++; + b++; + } + + return *a < *b ? -1 : *a > *b; +} + +/* Returns a pointer to the first occurrence of CH in the first + SIZE bytes starting at BLOCK. Returns a null pointer if CH + does not occur in BLOCK. */ +void * +memchr (const void *block_, int ch_, size_t size) { + const unsigned char *block = block_; + unsigned char ch = ch_; + + ASSERT (block != NULL || size == 0); + + for (; size-- > 0; block++) + if (*block == ch) + return (void *) block; + + return NULL; +} + +/* Finds and returns the first occurrence of C in STRING, or a + null pointer if C does not appear in STRING. If C == '\0' + then returns a pointer to the null terminator at the end of + STRING. */ +char * +strchr (const char *string, int c_) { + char c = c_; + + ASSERT (string); + + for (;;) + if (*string == c) + return (char *) string; + else if (*string == '\0') + return NULL; + else + string++; +} + +/* Returns the length of the initial substring of STRING that + consists of characters that are not in STOP. */ +size_t +strcspn (const char *string, const char *stop) { + size_t length; + + for (length = 0; string[length] != '\0'; length++) + if (strchr (stop, string[length]) != NULL) + break; + return length; +} + +/* Returns a pointer to the first character in STRING that is + also in STOP. If no character in STRING is in STOP, returns a + null pointer. */ +char * +strpbrk (const char *string, const char *stop) { + for (; *string != '\0'; string++) + if (strchr (stop, *string) != NULL) + return (char *) string; + return NULL; +} + +/* Returns a pointer to the last occurrence of C in STRING. + Returns a null pointer if C does not occur in STRING. */ +char * +strrchr (const char *string, int c_) { + char c = c_; + const char *p = NULL; + + for (; *string != '\0'; string++) + if (*string == c) + p = string; + return (char *) p; +} + +/* Returns the length of the initial substring of STRING that + consists of characters in SKIP. */ +size_t +strspn (const char *string, const char *skip) { + size_t length; + + for (length = 0; string[length] != '\0'; length++) + if (strchr (skip, string[length]) == NULL) + break; + return length; +} + +/* Returns a pointer to the first occurrence of NEEDLE within + HAYSTACK. Returns a null pointer if NEEDLE does not exist + within HAYSTACK. */ +char * +strstr (const char *haystack, const char *needle) { + size_t haystack_len = strlen (haystack); + size_t needle_len = strlen (needle); + + if (haystack_len >= needle_len) { + size_t i; + + for (i = 0; i <= haystack_len - needle_len; i++) + if (!memcmp (haystack + i, needle, needle_len)) + return (char *) haystack + i; + } + + return NULL; +} + +/* Breaks a string into tokens separated by DELIMITERS. The + first time this function is called, S should be the string to + tokenize, and in subsequent calls it must be a null pointer. + SAVE_PTR is the address of a `char *' variable used to keep + track of the tokenizer's position. The return value each time + is the next token in the string, or a null pointer if no + tokens remain. + + This function treats multiple adjacent delimiters as a single + delimiter. The returned tokens will never be length 0. + DELIMITERS may change from one call to the next within a + single string. + + strtok_r() modifies the string S, changing delimiters to null + bytes. Thus, S must be a modifiable string. String literals, + in particular, are *not* modifiable in C, even though for + backward compatibility they are not `const'. + + Example usage: + + char s[] = " String to tokenize. "; + char *token, *save_ptr; + + for (token = strtok_r (s, " ", &save_ptr); token != NULL; + token = strtok_r (NULL, " ", &save_ptr)) + printf ("'%s'\n", token); + +outputs: + +'String' +'to' +'tokenize.' +*/ +char * +strtok_r (char *s, const char *delimiters, char **save_ptr) { + char *token; + + ASSERT (delimiters != NULL); + ASSERT (save_ptr != NULL); + + /* If S is nonnull, start from it. + If S is null, start from saved position. */ + if (s == NULL) + s = *save_ptr; + ASSERT (s != NULL); + + /* Skip any DELIMITERS at our current position. */ + while (strchr (delimiters, *s) != NULL) { + /* strchr() will always return nonnull if we're searching + for a null byte, because every string contains a null + byte (at the end). */ + if (*s == '\0') { + *save_ptr = s; + return NULL; + } + + s++; + } + + /* Skip any non-DELIMITERS up to the end of the string. */ + token = s; + while (strchr (delimiters, *s) == NULL) + s++; + if (*s != '\0') { + *s = '\0'; + *save_ptr = s + 1; + } else + *save_ptr = s; + return token; +} + +/* Sets the SIZE bytes in DST to VALUE. */ +void * +memset (void *dst_, int value, size_t size) { + unsigned char *dst = dst_; + + ASSERT (dst != NULL || size == 0); + + while (size-- > 0) + *dst++ = value; + + return dst_; +} + +/* Returns the length of STRING. */ +size_t +strlen (const char *string) { + const char *p; + + ASSERT (string); + + for (p = string; *p != '\0'; p++) + continue; + return p - string; +} + +/* If STRING is less than MAXLEN characters in length, returns + its actual length. Otherwise, returns MAXLEN. */ +size_t +strnlen (const char *string, size_t maxlen) { + size_t length; + + for (length = 0; string[length] != '\0' && length < maxlen; length++) + continue; + return length; +} + +/* Copies string SRC to DST. If SRC is longer than SIZE - 1 + characters, only SIZE - 1 characters are copied. A null + terminator is always written to DST, unless SIZE is 0. + Returns the length of SRC, not including the null terminator. + + strlcpy() is not in the standard C library, but it is an + increasingly popular extension. See +http://www.courtesan.com/todd/papers/strlcpy.html for +information on strlcpy(). */ +size_t +strlcpy (char *dst, const char *src, size_t size) { + size_t src_len; + + ASSERT (dst != NULL); + ASSERT (src != NULL); + + src_len = strlen (src); + if (size > 0) { + size_t dst_len = size - 1; + if (src_len < dst_len) + dst_len = src_len; + memcpy (dst, src, dst_len); + dst[dst_len] = '\0'; + } + return src_len; +} + +/* Concatenates string SRC to DST. The concatenated string is + limited to SIZE - 1 characters. A null terminator is always + written to DST, unless SIZE is 0. Returns the length that the + concatenated string would have assuming that there was + sufficient space, not including a null terminator. + + strlcat() is not in the standard C library, but it is an + increasingly popular extension. See +http://www.courtesan.com/todd/papers/strlcpy.html for +information on strlcpy(). */ +size_t +strlcat (char *dst, const char *src, size_t size) { + size_t src_len, dst_len; + + ASSERT (dst != NULL); + ASSERT (src != NULL); + + src_len = strlen (src); + dst_len = strlen (dst); + if (size > 0 && dst_len < size) { + size_t copy_cnt = size - dst_len - 1; + if (src_len < copy_cnt) + copy_cnt = src_len; + memcpy (dst + dst_len, src, copy_cnt); + dst[dst_len + copy_cnt] = '\0'; + } + return src_len + dst_len; +} + diff --git a/lib/targets.mk b/lib/targets.mk new file mode 100644 index 0000000..8fd5680 --- /dev/null +++ b/lib/targets.mk @@ -0,0 +1,6 @@ +lib_SRC = lib/debug.c # Debug helpers. +lib_SRC += lib/random.c # Pseudo-random numbers. +lib_SRC += lib/stdio.c # I/O library. +lib_SRC += lib/stdlib.c # Utility functions. +lib_SRC += lib/string.c # String functions. +lib_SRC += lib/arithmetic.c diff --git a/lib/user/console.c b/lib/user/console.c new file mode 100644 index 0000000..ecb770a --- /dev/null +++ b/lib/user/console.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include + +/* The standard vprintf() function, + which is like printf() but uses a va_list. */ +int +vprintf (const char *format, va_list args) { + return vhprintf (STDOUT_FILENO, format, args); +} + +/* Like printf(), but writes output to the given HANDLE. */ +int +hprintf (int handle, const char *format, ...) { + va_list args; + int retval; + + va_start (args, format); + retval = vhprintf (handle, format, args); + va_end (args); + + return retval; +} + +/* Writes string S to the console, followed by a new-line + character. */ +int +puts (const char *s) { + write (STDOUT_FILENO, s, strlen (s)); + putchar ('\n'); + + return 0; +} + +/* Writes C to the console. */ +int +putchar (int c) { + char c2 = c; + write (STDOUT_FILENO, &c2, 1); + return c; +} + +/* Auxiliary data for vhprintf_helper(). */ +struct vhprintf_aux { + char buf[64]; /* Character buffer. */ + char *p; /* Current position in buffer. */ + int char_cnt; /* Total characters written so far. */ + int handle; /* Output file handle. */ +}; + +static void add_char (char, void *); +static void flush (struct vhprintf_aux *); + +/* Formats the printf() format specification FORMAT with + arguments given in ARGS and writes the output to the given + HANDLE. */ +int +vhprintf (int handle, const char *format, va_list args) { + struct vhprintf_aux aux; + aux.p = aux.buf; + aux.char_cnt = 0; + aux.handle = handle; + __vprintf (format, args, add_char, &aux); + flush (&aux); + return aux.char_cnt; +} + +/* Adds C to the buffer in AUX, flushing it if the buffer fills + up. */ +static void +add_char (char c, void *aux_) { + struct vhprintf_aux *aux = aux_; + *aux->p++ = c; + if (aux->p >= aux->buf + sizeof aux->buf) + flush (aux); + aux->char_cnt++; +} + +/* Flushes the buffer in AUX. */ +static void +flush (struct vhprintf_aux *aux) { + if (aux->p > aux->buf) + write (aux->handle, aux->buf, aux->p - aux->buf); + aux->p = aux->buf; +} diff --git a/lib/user/debug.c b/lib/user/debug.c new file mode 100644 index 0000000..e3aea48 --- /dev/null +++ b/lib/user/debug.c @@ -0,0 +1,24 @@ +#include +#include +#include +#include +#include + +/* Aborts the user program, printing the source file name, line + number, and function name, plus a user-specific message. */ +void +debug_panic (const char *file, int line, const char *function, + const char *message, ...) { + va_list args; + + printf ("User process ABORT at %s:%d in %s(): ", file, line, function); + + va_start (args, message); + vprintf (message, args); + printf ("\n"); + va_end (args); + + debug_backtrace (); + + exit (1); +} diff --git a/lib/user/entry.c b/lib/user/entry.c new file mode 100644 index 0000000..46f2d59 --- /dev/null +++ b/lib/user/entry.c @@ -0,0 +1,9 @@ +#include + +int main (int, char *[]); +void _start (int argc, char *argv[]); + +void +_start (int argc, char *argv[]) { + exit (main (argc, argv)); +} diff --git a/lib/user/syscall.c b/lib/user/syscall.c new file mode 100644 index 0000000..6124078 --- /dev/null +++ b/lib/user/syscall.c @@ -0,0 +1,132 @@ +#include +#include +#include "../syscall-nr.h" + +__attribute__((always_inline)) + static __inline int64_t syscall(int num, uint64_t a1, uint64_t a2, + uint64_t a3, uint64_t a4, uint64_t a5) { + int64_t ret; + __asm __volatile("syscall\n" : "=a" (ret) : "a" (num), + "d" (a1), "c" (a2), "b" (a3), "D" (a4), + "S" (a5) : "cc", "memory"); + return ret; + } + +/* Invokes syscall NUMBER, passing no arguments, and returns the + return value as an `int'. */ +#define syscall0(NUMBER) (syscall((NUMBER), 0, 0, 0, 0, 0)) + +/* Invokes syscall NUMBER, passing argument ARG0, and returns the + return value as an `int'. */ +#define syscall1(NUMBER, ARG0) (syscall((NUMBER), (ARG0), 0, 0, 0, 0)) +/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and + returns the return value as an `int'. */ +#define syscall2(NUMBER, ARG0, ARG1) (syscall((NUMBER), (ARG0), (ARG1), 0, 0, 0)) +#define syscall3(NUMBER, ARG0, ARG1, ARG2) (syscall((NUMBER), (ARG0), (ARG1), (ARG2), 0, 0)) + +void +halt (void) { + syscall0 (SYS_HALT); + NOT_REACHED (); +} + +void +exit (int status) { + syscall1 (SYS_EXIT, status); + NOT_REACHED (); +} + +pid_t +fork (void) { + return (pid_t) syscall0 (SYS_FORK); +} + +int +exec (const char *file) { + return (pid_t) syscall1 (SYS_EXEC, file); +} + +int +wait (pid_t pid) { + return syscall1 (SYS_WAIT, pid); +} + +bool +create (const char *file, unsigned initial_size) { + return syscall2 (SYS_CREATE, file, initial_size); +} + +bool +remove (const char *file) { + return syscall1 (SYS_REMOVE, file); +} + +int +open (const char *file) { + return syscall1 (SYS_OPEN, file); +} + +int +filesize (int fd) { + return syscall1 (SYS_FILESIZE, fd); +} + +int +read (int fd, void *buffer, unsigned size) { + return syscall3 (SYS_READ, fd, buffer, size); +} + +int +write (int fd, const void *buffer, unsigned size) { + return syscall3 (SYS_WRITE, fd, buffer, size); +} + +void +seek (int fd, unsigned position) { + syscall2 (SYS_SEEK, fd, position); +} + +unsigned +tell (int fd) { + return syscall1 (SYS_TELL, fd); +} + +void +close (int fd) { + syscall1 (SYS_CLOSE, fd); +} + +mapid_t +mmap (int fd, void *addr) { + return syscall2 (SYS_MMAP, fd, addr); +} + +void +munmap (mapid_t mapid) { + syscall1 (SYS_MUNMAP, mapid); +} + +bool +chdir (const char *dir) { + return syscall1 (SYS_CHDIR, dir); +} + +bool +mkdir (const char *dir) { + return syscall1 (SYS_MKDIR, dir); +} + +bool +readdir (int fd, char name[READDIR_MAX_LEN + 1]) { + return syscall2 (SYS_READDIR, fd, name); +} + +bool +isdir (int fd) { + return syscall1 (SYS_ISDIR, fd); +} + +int +inumber (int fd) { + return syscall1 (SYS_INUMBER, fd); +} diff --git a/lib/user/user.lds b/lib/user/user.lds new file mode 100644 index 0000000..d950a43 --- /dev/null +++ b/lib/user/user.lds @@ -0,0 +1,60 @@ +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) + +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + __executable_start = 0x0400000 + SIZEOF_HEADERS; + . = 0x0400000 + SIZEOF_HEADERS; + .text : AT(0x400000 + SIZEOF_HEADERS) { + *(.text) + *(.note.gnu.build-id) + } = 0x90 + .rodata : { *(.rodata) } + + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = ALIGN (0x1000) - ((0x1000 - .) & (0x1000 - 1)); + . = DATA_SEGMENT_ALIGN (0x1000, 0x1000); + + .data : { *(.data) } + .bss : { *(.bss) } + + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1 */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2 */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2 */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /DISCARD/ : { *(.note.GNU-stack) } + /DISCARD/ : { *(.eh_frame) } +} diff --git a/tests/Algorithm/Diff.pm b/tests/Algorithm/Diff.pm new file mode 100644 index 0000000..904c530 --- /dev/null +++ b/tests/Algorithm/Diff.pm @@ -0,0 +1,1713 @@ +package Algorithm::Diff; +# Skip to first "=head" line for documentation. +use strict; + +use integer; # see below in _replaceNextLargerWith() for mod to make + # if you don't use this +use vars qw( $VERSION @EXPORT_OK ); +$VERSION = 1.19_01; +# ^ ^^ ^^-- Incremented at will +# | \+----- Incremented for non-trivial changes to features +# \-------- Incremented for fundamental changes +require Exporter; +*import = \&Exporter::import; +@EXPORT_OK = qw( + prepare LCS LCDidx LCS_length + diff sdiff compact_diff + traverse_sequences traverse_balanced +); + +# McIlroy-Hunt diff algorithm +# Adapted from the Smalltalk code of Mario I. Wolczko, +# by Ned Konz, perl@bike-nomad.com +# Updates by Tye McQueen, http://perlmonks.org/?node=tye + +# Create a hash that maps each element of $aCollection to the set of +# positions it occupies in $aCollection, restricted to the elements +# within the range of indexes specified by $start and $end. +# The fourth parameter is a subroutine reference that will be called to +# generate a string to use as a key. +# Additional parameters, if any, will be passed to this subroutine. +# +# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); + +sub _withPositionsOfInInterval +{ + my $aCollection = shift; # array ref + my $start = shift; + my $end = shift; + my $keyGen = shift; + my %d; + my $index; + for ( $index = $start ; $index <= $end ; $index++ ) + { + my $element = $aCollection->[$index]; + my $key = &$keyGen( $element, @_ ); + if ( exists( $d{$key} ) ) + { + unshift ( @{ $d{$key} }, $index ); + } + else + { + $d{$key} = [$index]; + } + } + return wantarray ? %d : \%d; +} + +# Find the place at which aValue would normally be inserted into the +# array. If that place is already occupied by aValue, do nothing, and +# return undef. If the place does not exist (i.e., it is off the end of +# the array), add it to the end, otherwise replace the element at that +# point with aValue. It is assumed that the array's values are numeric. +# This is where the bulk (75%) of the time is spent in this module, so +# try to make it fast! + +sub _replaceNextLargerWith +{ + my ( $array, $aValue, $high ) = @_; + $high ||= $#$array; + + # off the end? + if ( $high == -1 || $aValue > $array->[-1] ) + { + push ( @$array, $aValue ); + return $high + 1; + } + + # binary search for insertion point... + my $low = 0; + my $index; + my $found; + while ( $low <= $high ) + { + $index = ( $high + $low ) / 2; + + # $index = int(( $high + $low ) / 2); # without 'use integer' + $found = $array->[$index]; + + if ( $aValue == $found ) + { + return undef; + } + elsif ( $aValue > $found ) + { + $low = $index + 1; + } + else + { + $high = $index - 1; + } + } + + # now insertion point is in $low. + $array->[$low] = $aValue; # overwrite next larger + return $low; +} + +# This method computes the longest common subsequence in $a and $b. + +# Result is array or ref, whose contents is such that +# $a->[ $i ] == $b->[ $result[ $i ] ] +# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. + +# An additional argument may be passed; this is a hash or key generating +# function that should return a string that uniquely identifies the given +# element. It should be the case that if the key is the same, the elements +# will compare the same. If this parameter is undef or missing, the key +# will be the element as a string. + +# By default, comparisons will use "eq" and elements will be turned into keys +# using the default stringizing operator '""'. + +# Additional parameters, if any, will be passed to the key generation +# routine. + +sub _longestCommonSubsequence +{ + my $a = shift; # array ref or hash ref + my $b = shift; # array ref or hash ref + my $counting = shift; # scalar + my $keyGen = shift; # code ref + my $compare; # code ref + + if ( ref($a) eq 'HASH' ) + { # prepared hash must be in $b + my $tmp = $b; + $b = $a; + $a = $tmp; + } + + # Check for bogus (non-ref) argument values + if ( !ref($a) || !ref($b) ) + { + my @callerInfo = caller(1); + die 'error: must pass array or hash references to ' . $callerInfo[3]; + } + + # set up code refs + # Note that these are optimized. + if ( !defined($keyGen) ) # optimize for strings + { + $keyGen = sub { $_[0] }; + $compare = sub { my ( $a, $b ) = @_; $a eq $b }; + } + else + { + $compare = sub { + my $a = shift; + my $b = shift; + &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); + }; + } + + my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); + my ( $prunedCount, $bMatches ) = ( 0, {} ); + + if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? + { + $bMatches = $b; + } + else + { + my ( $bStart, $bFinish ) = ( 0, $#$b ); + + # First we prune off any common elements at the beginning + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) + { + $matchVector->[ $aStart++ ] = $bStart++; + $prunedCount++; + } + + # now the end + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) + { + $matchVector->[ $aFinish-- ] = $bFinish--; + $prunedCount++; + } + + # Now compute the equivalence classes of positions of elements + $bMatches = + _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); + } + my $thresh = []; + my $links = []; + + my ( $i, $ai, $j, $k ); + for ( $i = $aStart ; $i <= $aFinish ; $i++ ) + { + $ai = &$keyGen( $a->[$i], @_ ); + if ( exists( $bMatches->{$ai} ) ) + { + $k = 0; + for $j ( @{ $bMatches->{$ai} } ) + { + + # optimization: most of the time this will be true + if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) + { + $thresh->[$k] = $j; + } + else + { + $k = _replaceNextLargerWith( $thresh, $j, $k ); + } + + # oddly, it's faster to always test this (CPU cache?). + if ( defined($k) ) + { + $links->[$k] = + [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; + } + } + } + } + + if (@$thresh) + { + return $prunedCount + @$thresh if $counting; + for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) + { + $matchVector->[ $link->[1] ] = $link->[2]; + } + } + elsif ($counting) + { + return $prunedCount; + } + + return wantarray ? @$matchVector : $matchVector; +} + +sub traverse_sequences +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $finishedACallback = $callbacks->{'A_FINISHED'}; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $finishedBCallback = $callbacks->{'B_FINISHED'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in @$matchVector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai; + + for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) + { + my $bLine = $matchVector->[$ai]; + if ( defined($bLine) ) # matched + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; + &$matchCallback( $ai, $bi++, @_ ); + } + else + { + &$discardACallback( $ai, $bi, @_ ); + } + } + + # The last entry (if any) processed was a match. + # $ai and $bi point just past the last matching lines in their sequences. + + while ( $ai <= $lastA or $bi <= $lastB ) + { + + # last A? + if ( $ai == $lastA + 1 and $bi <= $lastB ) + { + if ( defined($finishedACallback) ) + { + &$finishedACallback( $lastA, @_ ); + $finishedACallback = undef; + } + else + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; + } + } + + # last B? + if ( $bi == $lastB + 1 and $ai <= $lastA ) + { + if ( defined($finishedBCallback) ) + { + &$finishedBCallback( $lastB, @_ ); + $finishedBCallback = undef; + } + else + { + &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; + } + } + + &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; + &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; + } + + return 1; +} + +sub traverse_balanced +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $changeCallback = $callbacks->{'CHANGE'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in match vector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai = 0; + my $ma = -1; + my $mb; + + while (1) + { + + # Find next match indices $ma and $mb + do { + $ma++; + } while( + $ma <= $#$matchVector + && !defined $matchVector->[$ma] + ); + + last if $ma > $#$matchVector; # end of matchVector? + $mb = $matchVector->[$ma]; + + # Proceed with discard a/b or change events until + # next match + while ( $ai < $ma || $bi < $mb ) + { + + if ( $ai < $ma && $bi < $mb ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai < $ma ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi < $mb + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + # Match + &$matchCallback( $ai++, $bi++, @_ ); + } + + while ( $ai <= $lastA || $bi <= $lastB ) + { + if ( $ai <= $lastA && $bi <= $lastB ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai <= $lastA ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi <= $lastB + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + return 1; +} + +sub prepare +{ + my $a = shift; # array ref + my $keyGen = shift; # code ref + + # set up code ref + $keyGen = sub { $_[0] } unless defined($keyGen); + + return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); +} + +sub LCS +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); + my @retval; + my $i; + for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) + { + if ( defined( $matchVector->[$i] ) ) + { + push ( @retval, $a->[$i] ); + } + } + return wantarray ? @retval : \@retval; +} + +sub LCS_length +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + return _longestCommonSubsequence( $a, $b, 1, @_ ); +} + +sub LCSidx +{ + my $a= shift @_; + my $b= shift @_; + my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); + my @am= grep defined $match->[$_], 0..$#$match; + my @bm= @{$match}[@am]; + return \@am, \@bm; +} + +sub compact_diff +{ + my $a= shift @_; + my $b= shift @_; + my( $am, $bm )= LCSidx( $a, $b, @_ ); + my @cdiff; + my( $ai, $bi )= ( 0, 0 ); + push @cdiff, $ai, $bi; + while( 1 ) { + while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { + shift @$am; + shift @$bm; + ++$ai, ++$bi; + } + push @cdiff, $ai, $bi; + last if ! @$am; + $ai = $am->[0]; + $bi = $bm->[0]; + push @cdiff, $ai, $bi; + } + push @cdiff, 0+@$a, 0+@$b + if $ai < @$a || $bi < @$b; + return wantarray ? @cdiff : \@cdiff; +} + +sub diff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $hunk = []; + my $discard = sub { + push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; + }; + my $add = sub { + push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; + }; + my $match = sub { + push @$retval, $hunk + if 0 < @$hunk; + $hunk = [] + }; + traverse_sequences( $a, $b, + { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); + &$match(); + return wantarray ? @$retval : $retval; +} + +sub sdiff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; + my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; + my $change = sub { + push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + my $match = sub { + push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + traverse_balanced( + $a, + $b, + { + MATCH => $match, + DISCARD_A => $discard, + DISCARD_B => $add, + CHANGE => $change, + }, + @_ + ); + return wantarray ? @$retval : $retval; +} + +######################################## +my $Root= __PACKAGE__; +package Algorithm::Diff::_impl; +use strict; + +sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices + # 1 # $me->[1]: Ref to first sequence + # 2 # $me->[2]: Ref to second sequence +sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos +sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items +sub _Base() { 5 } # $me->[_Base]: Added to range's min and max +sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected +sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position +sub _Min() { -2 } # Added to _Off to get min instead of max+1 + +sub Die +{ + require Carp; + Carp::confess( @_ ); +} + +sub _ChkPos +{ + my( $me )= @_; + return if $me->[_Pos]; + my $meth= ( caller(1) )[3]; + Die( "Called $meth on 'reset' object" ); +} + +sub _ChkSeq +{ + my( $me, $seq )= @_; + return $seq + $me->[_Off] + if 1 == $seq || 2 == $seq; + my $meth= ( caller(1) )[3]; + Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); +} + +sub getObjPkg +{ + my( $us )= @_; + return ref $us if ref $us; + return $us . "::_obj"; +} + +sub new +{ + my( $us, $seq1, $seq2, $opts ) = @_; + my @args; + for( $opts->{keyGen} ) { + push @args, $_ if $_; + } + for( $opts->{keyGenArgs} ) { + push @args, @$_ if $_; + } + my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); + my $same= 1; + if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { + $same= 0; + splice @$cdif, 0, 2; + } + my @obj= ( $cdif, $seq1, $seq2 ); + $obj[_End] = (1+@$cdif)/2; + $obj[_Same] = $same; + $obj[_Base] = 0; + my $me = bless \@obj, $us->getObjPkg(); + $me->Reset( 0 ); + return $me; +} + +sub Reset +{ + my( $me, $pos )= @_; + $pos= int( $pos || 0 ); + $pos += $me->[_End] + if $pos < 0; + $pos= 0 + if $pos < 0 || $me->[_End] <= $pos; + $me->[_Pos]= $pos || !1; + $me->[_Off]= 2*$pos - 1; + return $me; +} + +sub Base +{ + my( $me, $base )= @_; + my $oldBase= $me->[_Base]; + $me->[_Base]= 0+$base if defined $base; + return $oldBase; +} + +sub Copy +{ + my( $me, $pos, $base )= @_; + my @obj= @$me; + my $you= bless \@obj, ref($me); + $you->Reset( $pos ) if defined $pos; + $you->Base( $base ); + return $you; +} + +sub Next { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + if( $steps ) { + my $pos= $me->[_Pos]; + my $new= $pos + $steps; + $new= 0 if $pos && $new < 0; + $me->Reset( $new ) + } + return $me->[_Pos]; +} + +sub Prev { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + my $pos= $me->Next(-$steps); + $pos -= $me->[_End] if $pos; + return $pos; +} + +sub Diff { + my( $me )= @_; + $me->_ChkPos(); + return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); + my $ret= 0; + my $off= $me->[_Off]; + for my $seq ( 1, 2 ) { + $ret |= $seq + if $me->[_Idx][ $off + $seq + _Min ] + < $me->[_Idx][ $off + $seq ]; + } + return $ret; +} + +sub Min { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off + _Min ]; +} + +sub Max { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off ] -1; +} + +sub Range { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + $base= $me->[_Base] if !defined $base; + return ( $base + $me->[_Idx][ $off + _Min ] ) + .. ( $base + $me->[_Idx][ $off ] - 1 ); +} + +sub Items { + my( $me, $seq )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + return + @{$me->[$seq]}[ + $me->[_Idx][ $off + _Min ] + .. ( $me->[_Idx][ $off ] - 1 ) + ]; +} + +sub Same { + my( $me )= @_; + $me->_ChkPos(); + return wantarray ? () : 0 + if $me->[_Same] != ( 1 & $me->[_Pos] ); + return $me->Items(1); +} + +my %getName; +BEGIN { + %getName= ( + same => \&Same, + diff => \&Diff, + base => \&Base, + min => \&Min, + max => \&Max, + range=> \&Range, + items=> \&Items, # same thing + ); +} + +sub Get +{ + my $me= shift @_; + $me->_ChkPos(); + my @value; + for my $arg ( @_ ) { + for my $word ( split ' ', $arg ) { + my $meth; + if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ + || not $meth= $getName{ lc $2 } + ) { + Die( $Root, ", Get: Invalid request ($word)" ); + } + my( $base, $name, $seq )= ( $1, $2, $3 ); + push @value, scalar( + 4 == length($name) + ? $meth->( $me ) + : $meth->( $me, $seq, $base ) + ); + } + } + if( wantarray ) { + return @value; + } elsif( 1 == @value ) { + return $value[0]; + } + Die( 0+@value, " values requested from ", + $Root, "'s Get in scalar context" ); +} + + +my $Obj= getObjPkg($Root); +no strict 'refs'; + +for my $meth ( qw( new getObjPkg ) ) { + *{$Root."::".$meth} = \&{$meth}; + *{$Obj ."::".$meth} = \&{$meth}; +} +for my $meth ( qw( + Next Prev Reset Copy Base Diff + Same Items Range Min Max Get + _ChkPos _ChkSeq +) ) { + *{$Obj."::".$meth} = \&{$meth}; +} + +1; +__END__ + +=head1 NAME + +Algorithm::Diff - Compute `intelligent' differences between two files / lists + +=head1 SYNOPSIS + + require Algorithm::Diff; + + # This example produces traditional 'diff' output: + + my $diff = Algorithm::Diff->new( \@seq1, \@seq2 ); + + $diff->Base( 1 ); # Return line numbers, not indices + while( $diff->Next() ) { + next if $diff->Same(); + my $sep = ''; + if( ! $diff->Items(2) ) { + sprintf "%d,%dd%d\n", + $diff->Get(qw( Min1 Max1 Max2 )); + } elsif( ! $diff->Items(1) ) { + sprint "%da%d,%d\n", + $diff->Get(qw( Max1 Min2 Max2 )); + } else { + $sep = "---\n"; + sprintf "%d,%dc%d,%d\n", + $diff->Get(qw( Min1 Max1 Min2 Max2 )); + } + print "< $_" for $diff->Items(1); + print $sep; + print "> $_" for $diff->Items(2); + } + + + # Alternate interfaces: + + use Algorithm::Diff qw( + LCS LCS_length LCSidx + diff sdiff compact_diff + traverse_sequences traverse_balanced ); + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + $count = LCS_length( \@seq1, \@seq2 ); + + ( $seq1idxref, $seq2idxref ) = LCSidx( \@seq1, \@seq2 ); + + + # Complicated interfaces: + + @diffs = diff( \@seq1, \@seq2 ); + + @sdiffs = sdiff( \@seq1, \@seq2 ); + + @cdiffs = compact_diff( \@seq1, \@seq2 ); + + traverse_sequences( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + }, + \&key_generator, + @extra_args, + ); + + traverse_balanced( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + CHANGE => \&callback4, + }, + \&key_generator, + @extra_args, + ); + + +=head1 INTRODUCTION + +(by Mark-Jason Dominus) + +I once read an article written by the authors of C; they said +that they worked very hard on the algorithm until they found the +right one. + +I think what they ended up using (and I hope someone will correct me, +because I am not very confident about this) was the `longest common +subsequence' method. In the LCS problem, you have two sequences of +items: + + a b c d f g h j q z + + a b c d e f g i j k r x y z + +and you want to find the longest sequence of items that is present in +both original sequences in the same order. That is, you want to find +a new sequence I which can be obtained from the first sequence by +deleting some items, and from the secend sequence by deleting other +items. You also want I to be as long as possible. In this case I +is + + a b c d f g j z + +From there it's only a small step to get diff-like output: + + e h i k q r x y + + - + + - + + + + +This module solves the LCS problem. It also includes a canned function +to generate C-like output. + +It might seem from the example above that the LCS of two sequences is +always pretty obvious, but that's not always the case, especially when +the two sequences have many repeated elements. For example, consider + + a x b y c z p d q + a b c a x b y c z + +A naive approach might start by matching up the C and C that +appear at the beginning of each sequence, like this: + + a x b y c z p d q + a b c a b y c z + +This finds the common subsequence C. But actually, the LCS +is C: + + a x b y c z p d q + a b c a x b y c z + +or + + a x b y c z p d q + a b c a x b y c z + +=head1 USAGE + +(See also the README file and several example +scripts include with this module.) + +This module now provides an object-oriented interface that uses less +memory and is easier to use than most of the previous procedural +interfaces. It also still provides several exportable functions. We'll +deal with these in ascending order of difficulty: C, +C, C, OO interface, C, C, C, +C, and C. + +=head2 C + +Given references to two lists of items, LCS returns an array containing +their longest common subsequence. In scalar context, it returns a +reference to such a list. + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + + @lcs = LCS( \@seq1, \@seq2, \&keyGen, @args ); + $lcsref = LCS( \@seq1, \@seq2, \&keyGen, @args ); + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + +This is just like C except it only returns the length of the +longest common subsequence. This provides a performance gain of about +9% compared to C. + +=head2 C + +Like C except it returns references to two arrays. The first array +contains the indices into @seq1 where the LCS items are located. The +second array contains the indices into @seq2 where the LCS items are located. + +Therefore, the following three lists will contain the same values: + + my( $idx1, $idx2 ) = LCSidx( \@seq1, \@seq2 ); + my @list1 = @seq1[ @$idx1 ]; + my @list2 = @seq2[ @$idx2 ]; + my @list3 = LCS( \@seq1, \@seq2 ); + +=head2 C + + $diff = Algorithm::Diffs->new( \@seq1, \@seq2 ); + $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts ); + +C computes the smallest set of additions and deletions necessary +to turn the first sequence into the second and compactly records them +in the object. + +You use the object to iterate over I, where each hunk represents +a contiguous section of items which should be added, deleted, replaced, +or left unchanged. + +=over 4 + +The following summary of all of the methods looks a lot like Perl code +but some of the symbols have different meanings: + + [ ] Encloses optional arguments + : Is followed by the default value for an optional argument + | Separates alternate return results + +Method summary: + + $obj = Algorithm::Diff->new( \@seq1, \@seq2, [ \%opts ] ); + $pos = $obj->Next( [ $count : 1 ] ); + $revPos = $obj->Prev( [ $count : 1 ] ); + $obj = $obj->Reset( [ $pos : 0 ] ); + $copy = $obj->Copy( [ $pos, [ $newBase ] ] ); + $oldBase = $obj->Base( [ $newBase ] ); + +Note that all of the following methods C if used on an object that +is "reset" (not currently pointing at any hunk). + + $bits = $obj->Diff( ); + @items|$cnt = $obj->Same( ); + @items|$cnt = $obj->Items( $seqNum ); + @idxs |$cnt = $obj->Range( $seqNum, [ $base ] ); + $minIdx = $obj->Min( $seqNum, [ $base ] ); + $maxIdx = $obj->Max( $seqNum, [ $base ] ); + @values = $obj->Get( @names ); + +Passing in C for an optional argument is always treated the same +as if no argument were passed in. + +=item C + + $pos = $diff->Next(); # Move forward 1 hunk + $pos = $diff->Next( 2 ); # Move forward 2 hunks + $pos = $diff->Next(-5); # Move backward 5 hunks + +C moves the object to point at the next hunk. The object starts +out "reset", which means it isn't pointing at any hunk. If the object +is reset, then C moves to the first hunk. + +C returns a true value iff the move didn't go past the last hunk. +So C will return true iff the object is not reset. + +Actually, C returns the object's new position, which is a number +between 1 and the number of hunks (inclusive), or returns a false value. + +=item C + +C is almost identical to C; it moves to the $Nth +previous hunk. On a 'reset' object, C [and C] move +to the last hunk. + +The position returned by C is relative to the I of the +hunks; -1 for the last hunk, -2 for the second-to-last, etc. + +=item C + + $diff->Reset(); # Reset the object's position + $diff->Reset($pos); # Move to the specified hunk + $diff->Reset(1); # Move to the first hunk + $diff->Reset(-1); # Move to the last hunk + +C returns the object, so, for example, you could use +C<< $diff->Reset()->Next(-1) >> to get the number of hunks. + +=item C + + $copy = $diff->Copy( $newPos, $newBase ); + +C returns a copy of the object. The copy and the orignal object +share most of their data, so making copies takes very little memory. +The copy maintains its own position (separate from the original), which +is the main purpose of copies. It also maintains its own base. + +By default, the copy's position starts out the same as the original +object's position. But C takes an optional first argument to set the +new position, so the following three snippets are equivalent: + + $copy = $diff->Copy($pos); + + $copy = $diff->Copy(); + $copy->Reset($pos); + + $copy = $diff->Copy()->Reset($pos); + +C takes an optional second argument to set the base for +the copy. If you wish to change the base of the copy but leave +the position the same as in the original, here are two +equivalent ways: + + $copy = $diff->Copy(); + $copy->Base( 0 ); + + $copy = $diff->Copy(undef,0); + +Here are two equivalent way to get a "reset" copy: + + $copy = $diff->Copy(0); + + $copy = $diff->Copy()->Reset(); + +=item C + + $bits = $obj->Diff(); + +C returns a true value iff the current hunk contains items that are +different between the two sequences. It actually returns one of the +follow 4 values: + +=over 4 + +=item 3 + +C<3==(1|2)>. This hunk contains items from @seq1 and the items +from @seq2 that should replace them. Both sequence 1 and 2 +contain changed items so both the 1 and 2 bits are set. + +=item 2 + +This hunk only contains items from @seq2 that should be inserted (not +items from @seq1). Only sequence 2 contains changed items so only the 2 +bit is set. + +=item 1 + +This hunk only contains items from @seq1 that should be deleted (not +items from @seq2). Only sequence 1 contains changed items so only the 1 +bit is set. + +=item 0 + +This means that the items in this hunk are the same in both sequences. +Neither sequence 1 nor 2 contain changed items so neither the 1 nor the +2 bits are set. + +=back + +=item C + +C returns a true value iff the current hunk contains items that +are the same in both sequences. It actually returns the list of items +if they are the same or an emty list if they aren't. In a scalar +context, it returns the size of the list. + +=item C + + $count = $diff->Items(2); + @items = $diff->Items($seqNum); + +C returns the (number of) items from the specified sequence that +are part of the current hunk. + +If the current hunk contains only insertions, then +C<< $diff->Items(1) >> will return an empty list (0 in a scalar conext). +If the current hunk contains only deletions, then C<< $diff->Items(2) >> +will return an empty list (0 in a scalar conext). + +If the hunk contains replacements, then both C<< $diff->Items(1) >> and +C<< $diff->Items(2) >> will return different, non-empty lists. + +Otherwise, the hunk contains identical items and all of the following +will return the same lists: + + @items = $diff->Items(1); + @items = $diff->Items(2); + @items = $diff->Same(); + +=item C + + $count = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum, $base ); + +C is like C except that it returns a list of I to +the items rather than the items themselves. By default, the index of +the first item (in each sequence) is 0 but this can be changed by +calling the C method. So, by default, the following two snippets +return the same lists: + + @list = $diff->Items(2); + @list = @seq2[ $diff->Range(2) ]; + +You can also specify the base to use as the second argument. So the +following two snippets I return the same lists: + + @list = $diff->Items(1); + @list = @seq1[ $diff->Range(1,0) ]; + +=item C + + $curBase = $diff->Base(); + $oldBase = $diff->Base($newBase); + +C sets and/or returns the current base (usually 0 or 1) that is +used when you request range information. The base defaults to 0 so +that range information is returned as array indices. You can set the +base to 1 if you want to report traditional line numbers instead. + +=item C + + $min1 = $diff->Min(1); + $min = $diff->Min( $seqNum, $base ); + +C returns the first value that C would return (given the +same arguments) or returns C if C would return an empty +list. + +=item C + +C returns the last value that C would return or C. + +=item C + + ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 )); + @values = $diff->Get(qw( 0min2 1max2 range2 same base )); + +C returns one or more scalar values. You pass in a list of the +names of the values you want returned. Each name must match one of the +following regexes: + + /^(-?\d+)?(min|max)[12]$/i + /^(range[12]|same|diff|base)$/i + +The 1 or 2 after a name says which sequence you want the information +for (and where allowed, it is required). The optional number before +"min" or "max" is the base to use. So the following equalities hold: + + $diff->Get('min1') == $diff->Min(1) + $diff->Get('0min2') == $diff->Min(2,0) + +Using C in a scalar context when you've passed in more than one +name is a fatal error (C is called). + +=back + +=head2 C + +Given a reference to a list of items, C returns a reference +to a hash which can be used when comparing this sequence to other +sequences with C or C. + + $prep = prepare( \@seq1 ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $prep, $seq[$i] ); + # do something useful with @lcs + } + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + + $prep = prepare( \@seq1, \&keyGen ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $seq[$i], $prep, \&keyGen ); + # do something useful with @lcs + } + +Using C provides a performance gain of about 50% when calling LCS +many times compared with not preparing. + +=head2 C + + @diffs = diff( \@seq1, \@seq2 ); + $diffs_ref = diff( \@seq1, \@seq2 ); + +C computes the smallest set of additions and deletions necessary +to turn the first sequence into the second, and returns a description +of these changes. The description is a list of I; each hunk +represents a contiguous section of items which should be added, +deleted, or replaced. (Hunks containing unchanged items are not +included.) + +The return value of C is a list of hunks, or, in scalar context, a +reference to such a list. If there are no differences, the list will be +empty. + +Here is an example. Calling C for the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +would produce the following list: + + ( + [ [ '-', 0, 'a' ] ], + + [ [ '+', 2, 'd' ] ], + + [ [ '-', 4, 'h' ], + [ '+', 4, 'f' ] ], + + [ [ '+', 6, 'k' ] ], + + [ [ '-', 8, 'n' ], + [ '-', 9, 'p' ], + [ '+', 9, 'r' ], + [ '+', 10, 's' ], + [ '+', 11, 't' ] ], + ) + +There are five hunks here. The first hunk says that the C at +position 0 of the first sequence should be deleted (C<->). The second +hunk says that the C at position 2 of the second sequence should +be inserted (C<+>). The third hunk says that the C at position 4 +of the first sequence should be removed and replaced with the C +from position 4 of the second sequence. And so on. + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + + @sdiffs = sdiff( \@seq1, \@seq2 ); + $sdiffs_ref = sdiff( \@seq1, \@seq2 ); + +C computes all necessary components to show two sequences +and their minimized differences side by side, just like the +Unix-utility I does: + + same same + before | after + old < - + - > new + +It returns a list of array refs, each pointing to an array of +display instructions. In scalar context it returns a reference +to such a list. If there are no differences, the list will have one +entry per item, each indicating that the item was unchanged. + +Display instructions consist of three elements: A modifier indicator +(C<+>: Element added, C<->: Element removed, C: Element unmodified, +C: Element changed) and the value of the old and new elements, to +be displayed side-by-side. + +An C of the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +results in + + ( [ '-', 'a', '' ], + [ 'u', 'b', 'b' ], + [ 'u', 'c', 'c' ], + [ '+', '', 'd' ], + [ 'u', 'e', 'e' ], + [ 'c', 'h', 'f' ], + [ 'u', 'j', 'j' ], + [ '+', '', 'k' ], + [ 'u', 'l', 'l' ], + [ 'u', 'm', 'm' ], + [ 'c', 'n', 'r' ], + [ 'c', 'p', 's' ], + [ '+', '', 't' ], + ) + +C may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C + +C is much like C except it returns a much more +compact description consisting of just one flat list of indices. An +example helps explain the format: + + my @a = qw( a b c e h j l m n p ); + my @b = qw( b c d e f j k l m r s t ); + @cdiff = compact_diff( \@a, \@b ); + # Returns: + # @a @b @a @b + # start start values values + ( 0, 0, # = + 0, 0, # a ! + 1, 0, # b c = b c + 3, 2, # ! d + 3, 3, # e = e + 4, 4, # f ! h + 5, 5, # j = j + 6, 6, # ! k + 6, 7, # l m = l m + 8, 9, # n p ! r s t + 10, 12, # + ); + +The 0th, 2nd, 4th, etc. entries are all indices into @seq1 (@a in the +above example) indicating where a hunk begins. The 1st, 3rd, 5th, etc. +entries are all indices into @seq2 (@b in the above example) indicating +where the same hunk begins. + +So each pair of indices (except the last pair) describes where a hunk +begins (in each sequence). Since each hunk must end at the item just +before the item that starts the next hunk, the next pair of indices can +be used to determine where the hunk ends. + +So, the first 4 entries (0..3) describe the first hunk. Entries 0 and 1 +describe where the first hunk begins (and so are always both 0). +Entries 2 and 3 describe where the next hunk begins, so subtracting 1 +from each tells us where the first hunk ends. That is, the first hunk +contains items C<$diff[0]> through C<$diff[2] - 1> of the first sequence +and contains items C<$diff[1]> through C<$diff[3] - 1> of the second +sequence. + +In other words, the first hunk consists of the following two lists of items: + + # 1st pair 2nd pair + # of indices of indices + @list1 = @a[ $cdiff[0] .. $cdiff[2]-1 ]; + @list2 = @b[ $cdiff[1] .. $cdiff[3]-1 ]; + # Hunk start Hunk end + +Note that the hunks will always alternate between those that are part of +the LCS (those that contain unchanged items) and those that contain +changes. This means that all we need to be told is whether the first +hunk is a 'same' or 'diff' hunk and we can determine which of the other +hunks contain 'same' items or 'diff' items. + +By convention, we always make the first hunk contain unchanged items. +So the 1st, 3rd, 5th, etc. hunks (all odd-numbered hunks if you start +counting from 1) all contain unchanged items. And the 2nd, 4th, 6th, +etc. hunks (all even-numbered hunks if you start counting from 1) all +contain changed items. + +Since @a and @b don't begin with the same value, the first hunk in our +example is empty (otherwise we'd violate the above convention). Note +that the first 4 index values in our example are all zero. Plug these +values into our previous code block and we get: + + @hunk1a = @a[ 0 .. 0-1 ]; + @hunk1b = @b[ 0 .. 0-1 ]; + +And C<0..-1> returns the empty list. + +Move down one pair of indices (2..5) and we get the offset ranges for +the second hunk, which contains changed items. + +Since C<@diff[2..5]> contains (0,0,1,0) in our example, the second hunk +consists of these two lists of items: + + @hunk2a = @a[ $cdiff[2] .. $cdiff[4]-1 ]; + @hunk2b = @b[ $cdiff[3] .. $cdiff[5]-1 ]; + # or + @hunk2a = @a[ 0 .. 1-1 ]; + @hunk2b = @b[ 0 .. 0-1 ]; + # or + @hunk2a = @a[ 0 .. 0 ]; + @hunk2b = @b[ 0 .. -1 ]; + # or + @hunk2a = ( 'a' ); + @hunk2b = ( ); + +That is, we would delete item 0 ('a') from @a. + +Since C<@diff[4..7]> contains (1,0,3,2) in our example, the third hunk +consists of these two lists of items: + + @hunk3a = @a[ $cdiff[4] .. $cdiff[6]-1 ]; + @hunk3a = @b[ $cdiff[5] .. $cdiff[7]-1 ]; + # or + @hunk3a = @a[ 1 .. 3-1 ]; + @hunk3a = @b[ 0 .. 2-1 ]; + # or + @hunk3a = @a[ 1 .. 2 ]; + @hunk3a = @b[ 0 .. 1 ]; + # or + @hunk3a = qw( b c ); + @hunk3a = qw( b c ); + +Note that this third hunk contains unchanged items as our convention demands. + +You can continue this process until you reach the last two indices, +which will always be the number of items in each sequence. This is +required so that subtracting one from each will give you the indices to +the last items in each sequence. + +=head2 C + +C used to be the most general facility provided by +this module (the new OO interface is more powerful and much easier to +use). + +Imagine that there are two arrows. Arrow A points to an element of +sequence A, and arrow B points to an element of the sequence B. +Initially, the arrows point to the first elements of the respective +sequences. C will advance the arrows through the +sequences one element at a time, calling an appropriate user-specified +callback function before each advance. It willadvance the arrows in +such a way that if there are equal elements C<$A[$i]> and C<$B[$j]> +which are equal and which are part of the LCS, there will be some moment +during the execution of C when arrow A is pointing +to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens, +C will call the C callback function and then +it will advance both arrows. + +Otherwise, one of the arrows is pointing to an element of its sequence +that is not part of the LCS. C will advance that +arrow and will call the C or the C callback, +depending on which arrow it advanced. If both arrows point to elements +that are not part of the LCS, then C will advance +one of them and call the appropriate callback, but it is not specified +which it will call. + +The arguments to C are the two sequences to +traverse, and a hash which specifies the callback functions, like this: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + } + ); + +Callbacks for MATCH, DISCARD_A, and DISCARD_B are invoked with at least +the indices of the two arrows as their arguments. They are not expected +to return any values. If a callback is omitted from the table, it is +not called. + +Callbacks for A_FINISHED and B_FINISHED are invoked with at least the +corresponding index in A or B. + +If arrow A reaches the end of its sequence, before arrow B does, +C will call the C callback when it +advances arrow B, if there is such a function; if not it will call +C instead. Similarly if arrow B finishes first. +C returns when both arrows are at the ends of their +respective sequences. It returns true on success and false on failure. +At present there is no way to fail. + +C may be passed an optional fourth parameter; this +is a CODE reference to a key generation function. See L. + +Additional parameters, if any, will be passed to the key generation function. + +If you want to pass additional parameters to your callbacks, but don't +need a custom key generation function, you can get the default by +passing undef: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + }, + undef, # default key-gen + $myArgument1, + $myArgument2, + $myArgument3, + ); + +C does not have a useful return value; you are +expected to plug in the appropriate behavior with the callback +functions. + +=head2 C + +C is an alternative to C. It +uses a different algorithm to iterate through the entries in the +computed LCS. Instead of sticking to one side and showing element changes +as insertions and deletions only, it will jump back and forth between +the two sequences and report I occurring as deletions on one +side followed immediatly by an insertion on the other side. + +In addition to the C, C, and C callbacks +supported by C, C supports +a C callback indicating that one element got C by another: + + traverse_balanced( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + CHANGE => $callback_4, + } + ); + +If no C callback is specified, C +will map C events to C and C actions, +therefore resulting in a similar behaviour as C +with different order of events. + +C might be a bit slower than C, +noticable only while processing huge amounts of data. + +The C function of this module +is implemented as call to C. + +C does not have a useful return value; you are expected to +plug in the appropriate behavior with the callback functions. + +=head1 KEY GENERATION FUNCTIONS + +Most of the functions accept an optional extra parameter. This is a +CODE reference to a key generating (hashing) function that should return +a string that uniquely identifies a given element. It should be the +case that if two elements are to be considered equal, their keys should +be the same (and the other way around). If no key generation function +is provided, the key will be the element as a string. + +By default, comparisons will use "eq" and elements will be turned into keys +using the default stringizing operator '""'. + +Where this is important is when you're comparing something other than +strings. If it is the case that you have multiple different objects +that should be considered to be equal, you should supply a key +generation function. Otherwise, you have to make sure that your arrays +contain unique references. + +For instance, consider this example: + + package Person; + + sub new + { + my $package = shift; + return bless { name => '', ssn => '', @_ }, $package; + } + + sub clone + { + my $old = shift; + my $new = bless { %$old }, ref($old); + } + + sub hash + { + return shift()->{'ssn'}; + } + + my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' ); + my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' ); + my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' ); + my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' ); + my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' ); + +If you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4, $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +everything would work out OK (each of the objects would be converted +into a string like "Person=HASH(0x82425b0)" for comparison). + +But if you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +$person4 and $person4->clone() (which have the same name and SSN) +would be seen as different objects. If you wanted them to be considered +equivalent, you would have to pass in a key generation function: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2, \&Person::hash ); + +This would use the 'ssn' field in each Person as a comparison key, and +so would consider $person4 and $person4->clone() as equal. + +You may also pass additional parameters to the key generation function +if you wish. + +=head1 ERROR CHECKING + +If you pass these routines a non-reference and they expect a reference, +they will die with a message. + +=head1 AUTHOR + +This version released by Tye McQueen (http://perlmonks.org/?node=tye). + +=head1 LICENSE + +Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved. +Parts by Tye McQueen. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl. + +=head1 MAILING LIST + +Mark-Jason still maintains a mailing list. To join a low-volume mailing +list for announcements related to diff and Algorithm::Diff, send an +empty mail message to mjd-perl-diff-request@plover.com. + +=head1 CREDITS + +Versions through 0.59 (and much of this documentation) were written by: + +Mark-Jason Dominus, mjd-perl-diff@plover.com + +This version borrows some documentation and routine names from +Mark-Jason's, but Diff.pm's code was completely replaced. + +This code was adapted from the Smalltalk code of Mario Wolczko +, which is available at +ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st + +C and C were written by Mike Schilli +. + +The algorithm is that described in +I, +CACM, vol.20, no.5, pp.350-353, May 1977, with a few +minor improvements to improve the speed. + +Much work was done by Ned Konz (perl@bike-nomad.com). + +The OO interface and some other changes are by Tye McQueen. + +=cut diff --git a/tests/Make.tests b/tests/Make.tests new file mode 100644 index 0000000..57ffb76 --- /dev/null +++ b/tests/Make.tests @@ -0,0 +1,76 @@ +# -*- makefile -*- + +include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS)) + +PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS)) +TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS)) +EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES)) + +OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES)) +ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES)) +RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES)) + +ifdef PROGS +include ../../Makefile.userprog +endif + +TIMEOUT = 60 + +clean:: + rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) + +grade:: results + $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@ + +check:: results + @cat $< + @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \ + FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \ + if [ $$FAILURES = 0 ]; then \ + echo "All $$COUNT tests passed."; \ + else \ + echo "$$FAILURES of $$COUNT tests failed."; \ + exit 1; \ + fi + +results: $(RESULTS) + @for d in $(TESTS) $(EXTRA_GRADES); do \ + if echo PASS | cmp -s $$d.result -; then \ + echo "pass $$d"; \ + else \ + echo "FAIL $$d"; \ + fi; \ + done > $@ + +outputs:: $(OUTPUTS) + +$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog))) +$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES))) +$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test))) + +# Prevent an environment variable VERBOSE from surprising us. +VERBOSE = + +TESTCMD = pintos -v -k -T $(TIMEOUT) +TESTCMD += $(SIMULATOR) +TESTCMD += $(PINTOSOPTS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += --fs-disk=$(FSDISK) +TESTCMD += $(foreach file,$(PUTFILES),-p $(file):$(notdir $(file))) +endif +ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) +TESTCMD += --swap-disk=4 +endif +TESTCMD += -- -q +TESTCMD += $(KERNELFLAGS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += -f +endif +TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F)) +TESTCMD += < /dev/null +TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).output +%.output: os.dsk + $(TESTCMD) + +%.result: %.ck %.output + perl -I$(SRCDIR) $< $* $@ diff --git a/tests/arc4.c b/tests/arc4.c new file mode 100644 index 0000000..b033cc6 --- /dev/null +++ b/tests/arc4.c @@ -0,0 +1,53 @@ +#include +#include "tests/arc4.h" + +/* Swap bytes. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) +{ + uint8_t t = *a; + *a = *b; + *b = t; +} + +void +arc4_init (struct arc4 *arc4, const void *key_, size_t size) +{ + const uint8_t *key = key_; + size_t key_idx; + uint8_t *s; + int i, j; + + s = arc4->s; + arc4->i = arc4->j = 0; + for (i = 0; i < 256; i++) + s[i] = i; + for (key_idx = 0, i = j = 0; i < 256; i++) + { + j = (j + s[i] + key[key_idx]) & 255; + swap_byte (s + i, s + j); + if (++key_idx >= size) + key_idx = 0; + } +} + +void +arc4_crypt (struct arc4 *arc4, void *buf_, size_t size) +{ + uint8_t *buf = buf_; + uint8_t *s; + uint8_t i, j; + + s = arc4->s; + i = arc4->i; + j = arc4->j; + while (size-- > 0) + { + i += 1; + j += s[i]; + swap_byte (s + i, s + j); + *buf++ ^= s[(s[i] + s[j]) & 255]; + } + arc4->i = i; + arc4->j = j; +} diff --git a/tests/arc4.h b/tests/arc4.h new file mode 100644 index 0000000..61c533a --- /dev/null +++ b/tests/arc4.h @@ -0,0 +1,17 @@ +#ifndef TESTS_ARC4_H +#define TESTS_ARC4_H + +#include +#include + +/* Alleged RC4 algorithm encryption state. */ +struct arc4 + { + uint8_t s[256]; + uint8_t i, j; + }; + +void arc4_init (struct arc4 *, const void *, size_t); +void arc4_crypt (struct arc4 *, void *, size_t); + +#endif /* tests/arc4.h */ diff --git a/tests/arc4.pm b/tests/arc4.pm new file mode 100644 index 0000000..df19216 --- /dev/null +++ b/tests/arc4.pm @@ -0,0 +1,29 @@ +use strict; +use warnings; + +sub arc4_init { + my ($key) = @_; + my (@s) = 0...255; + my ($j) = 0; + for my $i (0...255) { + $j = ($j + $s[$i] + ord (substr ($key, $i % length ($key), 1))) & 0xff; + @s[$i, $j] = @s[$j, $i]; + } + return (0, 0, @s); +} + +sub arc4_crypt { + my ($arc4, $buf) = @_; + my ($i, $j, @s) = @$arc4; + my ($out) = ""; + for my $c (split (//, $buf)) { + $i = ($i + 1) & 0xff; + $j = ($j + $s[$i]) & 0xff; + @s[$i, $j] = @s[$j, $i]; + $out .= chr (ord ($c) ^ $s[($s[$i] + $s[$j]) & 0xff]); + } + @$arc4 = ($i, $j, @s); + return $out; +} + +1; diff --git a/tests/cksum.c b/tests/cksum.c new file mode 100644 index 0000000..92a2995 --- /dev/null +++ b/tests/cksum.c @@ -0,0 +1,92 @@ +/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */ + +#include +#include "tests/cksum.h" + +static unsigned long crctab[] = { + 0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* This is the algorithm used by the Posix `cksum' utility. */ +unsigned long +cksum (const void *b_, size_t n) +{ + const unsigned char *b = b_; + uint32_t s = 0; + size_t i; + for (i = n; i > 0; --i) + { + unsigned char c = *b++; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + while (n != 0) + { + unsigned char c = n; + n >>= 8; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + return ~s; +} + +#ifdef STANDALONE_TEST +#include +int +main (void) +{ + char buf[65536]; + int n = fread (buf, 1, sizeof buf, stdin); + printf ("%lu\n", cksum (buf, n)); + return 0; +} +#endif diff --git a/tests/cksum.h b/tests/cksum.h new file mode 100644 index 0000000..23a1fe9 --- /dev/null +++ b/tests/cksum.h @@ -0,0 +1,8 @@ +#ifndef TESTS_CKSUM_H +#define TESTS_CKSUM_H + +#include + +unsigned long cksum(const void *, size_t); + +#endif /* tests/cksum.h */ diff --git a/tests/cksum.pm b/tests/cksum.pm new file mode 100644 index 0000000..73be5f2 --- /dev/null +++ b/tests/cksum.pm @@ -0,0 +1,87 @@ +# From the `cksum' entry in SUSv3. + +use strict; +use warnings; + +my (@crctab) = + (0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4); + +sub cksum { + my ($b) = @_; + my ($n) = length ($b); + my ($s) = 0; + for my $i (0...$n - 1) { + my ($c) = ord (substr ($b, $i, 1)); + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + while ($n != 0) { + my ($c) = $n & 0xff; + $n >>= 8; + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + return ~$s & 0xffff_ffff; +} + +sub cksum_file { + my ($file) = @_; + open (FILE, '<', $file) or die "$file: open: $!\n"; + my ($data); + sysread (FILE, $data, -s FILE) == -s FILE or die "$file: read: $!\n"; + close (FILE); + return cksum ($data); +} + +1; diff --git a/tests/internal/list.c b/tests/internal/list.c new file mode 100644 index 0000000..836c69e --- /dev/null +++ b/tests/internal/list.c @@ -0,0 +1,174 @@ +/* Test program for lib/kernel/list.c. + + Attempts to test the list functionality that is not + sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include "threads/test.h" + +/* Maximum number of elements in a linked list that we will + test. */ +#define MAX_SIZE 64 + +/* A linked list element. */ +struct value + { + struct list_elem elem; /* List element. */ + int value; /* Item value. */ + }; + +static void shuffle (struct value[], size_t); +static bool value_less (const struct list_elem *, const struct list_elem *, + void *); +static void verify_list_fwd (struct list *, int size); +static void verify_list_bkwd (struct list *, int size); + +/* Test the linked list implementation. */ +void +test (void) +{ + int size; + + printf ("testing various size lists:"); + for (size = 0; size < MAX_SIZE; size++) + { + int repeat; + + printf (" %d", size); + for (repeat = 0; repeat < 10; repeat++) + { + static struct value values[MAX_SIZE * 4]; + struct list list; + struct list_elem *e; + int i, ofs; + + /* Put values 0...SIZE in random order in VALUES. */ + for (i = 0; i < size; i++) + values[i].value = i; + shuffle (values, size); + + /* Assemble list. */ + list_init (&list); + for (i = 0; i < size; i++) + list_push_back (&list, &values[i].elem); + + /* Verify correct minimum and maximum elements. */ + e = list_min (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == 0 + : e == list_begin (&list)); + e = list_max (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == size - 1 + : e == list_begin (&list)); + + /* Sort and verify list. */ + list_sort (&list, value_less, NULL); + verify_list_fwd (&list, size); + + /* Reverse and verify list. */ + list_reverse (&list); + verify_list_bkwd (&list, size); + + /* Shuffle, insert using list_insert_ordered(), + and verify ordering. */ + shuffle (values, size); + list_init (&list); + for (i = 0; i < size; i++) + list_insert_ordered (&list, &values[i].elem, + value_less, NULL); + verify_list_fwd (&list, size); + + /* Duplicate some items, uniquify, and verify. */ + ofs = size; + for (e = list_begin (&list); e != list_end (&list); + e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + int copies = random_ulong () % 4; + while (copies-- > 0) + { + values[ofs].value = v->value; + list_insert (e, &values[ofs++].elem); + } + } + ASSERT ((size_t) ofs < sizeof values / sizeof *values); + list_unique (&list, NULL, value_less, NULL); + verify_list_fwd (&list, size); + } + } + + printf (" done\n"); + printf ("list: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (struct value *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + struct value t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns true if value A is less than value B, false + otherwise. */ +static bool +value_less (const struct list_elem *a_, const struct list_elem *b_, + void *aux UNUSED) +{ + const struct value *a = list_entry (a_, struct value, elem); + const struct value *b = list_entry (b_, struct value, elem); + + return a->value < b->value; +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in forward order. */ +static void +verify_list_fwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_begin (list); + i < size && e != list_end (list); + i++, e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_end (list)); +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in reverse order. */ +static void +verify_list_bkwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_rbegin (list); + i < size && e != list_rend (list); + i++, e = list_prev (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_rend (list)); +} diff --git a/tests/internal/stdio.c b/tests/internal/stdio.c new file mode 100644 index 0000000..fb60cda --- /dev/null +++ b/tests/internal/stdio.c @@ -0,0 +1,208 @@ +/* Test program for printf() in lib/stdio.c. + + Attempts to test printf() functionality that is not + sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include +#include "threads/test.h" + +/* Number of failures so far. */ +static int failure_cnt; + +static void +checkf (const char *expect, const char *format, ...) +{ + char output[128]; + va_list args; + + printf ("\"%s\" -> \"%s\": ", format, expect); + + va_start (args, format); + vsnprintf (output, sizeof output, format, args); + va_end (args); + + if (strcmp (expect, output)) + { + printf ("\nFAIL: actual output \"%s\"\n", output); + failure_cnt++; + } + else + printf ("okay\n"); +} + +/* Test printf() implementation. */ +void +test (void) +{ + printf ("Testing formats:"); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("1", "%'d", 1); + checkf ("12", "%'d", 12); + checkf ("123", "%'d", 123); + checkf ("1,234", "%'d", 1234); + checkf ("12,345", "%'d", 12345); + checkf ("123,456", "%'ld", 123456L); + checkf ("1,234,567", "%'ld", 1234567L); + checkf ("12,345,678", "%'ld", 12345678L); + checkf ("123,456,789", "%'ld", 123456789L); + checkf ("1,234,567,890", "%'ld", 1234567890L); + checkf ("12,345,678,901", "%'lld", 12345678901LL); + checkf ("123,456,789,012", "%'lld", 123456789012LL); + checkf ("1,234,567,890,123", "%'lld", 1234567890123LL); + checkf ("12,345,678,901,234", "%'lld", 12345678901234LL); + checkf ("123,456,789,012,345", "%'lld", 123456789012345LL); + checkf ("1,234,567,890,123,456", "%'lld", 1234567890123456LL); + checkf ("12,345,678,901,234,567", "%'lld", 12345678901234567LL); + checkf ("123,456,789,012,345,678", "%'lld", 123456789012345678LL); + checkf ("1,234,567,890,123,456,789", "%'lld", 1234567890123456789LL); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("-1", "%'d", -1); + checkf ("-12", "%'d", -12); + checkf ("-123", "%'d", -123); + checkf ("-1,234", "%'d", -1234); + checkf ("-12,345", "%'d", -12345); + checkf ("-123,456", "%'ld", -123456L); + checkf ("-1,234,567", "%'ld", -1234567L); + checkf ("-12,345,678", "%'ld", -12345678L); + checkf ("-123,456,789", "%'ld", -123456789L); + checkf ("-1,234,567,890", "%'ld", -1234567890L); + checkf ("-12,345,678,901", "%'lld", -12345678901LL); + checkf ("-123,456,789,012", "%'lld", -123456789012LL); + checkf ("-1,234,567,890,123", "%'lld", -1234567890123LL); + checkf ("-12,345,678,901,234", "%'lld", -12345678901234LL); + checkf ("-123,456,789,012,345", "%'lld", -123456789012345LL); + checkf ("-1,234,567,890,123,456", "%'lld", -1234567890123456LL); + checkf ("-12,345,678,901,234,567", "%'lld", -12345678901234567LL); + checkf ("-123,456,789,012,345,678", "%'lld", -123456789012345678LL); + checkf ("-1,234,567,890,123,456,789", "%'lld", -1234567890123456789LL); + + /* Check signed integer conversions. */ + checkf (" 0", "%5d", 0); + checkf ("0 ", "%-5d", 0); + checkf (" +0", "%+5d", 0); + checkf ("+0 ", "%+-5d", 0); + checkf (" 0", "% 5d", 0); + checkf ("00000", "%05d", 0); + checkf (" ", "%5.0d", 0); + checkf (" 00", "%5.2d", 0); + checkf ("0", "%d", 0); + + checkf (" 1", "%5d", 1); + checkf ("1 ", "%-5d", 1); + checkf (" +1", "%+5d", 1); + checkf ("+1 ", "%+-5d", 1); + checkf (" 1", "% 5d", 1); + checkf ("00001", "%05d", 1); + checkf (" 1", "%5.0d", 1); + checkf (" 01", "%5.2d", 1); + checkf ("1", "%d", 1); + + checkf (" -1", "%5d", -1); + checkf ("-1 ", "%-5d", -1); + checkf (" -1", "%+5d", -1); + checkf ("-1 ", "%+-5d", -1); + checkf (" -1", "% 5d", -1); + checkf ("-0001", "%05d", -1); + checkf (" -1", "%5.0d", -1); + checkf (" -01", "%5.2d", -1); + checkf ("-1", "%d", -1); + + checkf ("12345", "%5d", 12345); + checkf ("12345", "%-5d", 12345); + checkf ("+12345", "%+5d", 12345); + checkf ("+12345", "%+-5d", 12345); + checkf (" 12345", "% 5d", 12345); + checkf ("12345", "%05d", 12345); + checkf ("12345", "%5.0d", 12345); + checkf ("12345", "%5.2d", 12345); + checkf ("12345", "%d", 12345); + + checkf ("123456", "%5d", 123456); + checkf ("123456", "%-5d", 123456); + checkf ("+123456", "%+5d", 123456); + checkf ("+123456", "%+-5d", 123456); + checkf (" 123456", "% 5d", 123456); + checkf ("123456", "%05d", 123456); + checkf ("123456", "%5.0d", 123456); + checkf ("123456", "%5.2d", 123456); + checkf ("123456", "%d", 123456); + + /* Check unsigned integer conversions. */ + checkf (" 0", "%5u", 0); + checkf (" 0", "%5o", 0); + checkf (" 0", "%5x", 0); + checkf (" 0", "%5X", 0); + checkf (" 0", "%#5o", 0); + checkf (" 0", "%#5x", 0); + checkf (" 0", "%#5X", 0); + checkf (" 00000000", "%#10.8x", 0); + + checkf (" 1", "%5u", 1); + checkf (" 1", "%5o", 1); + checkf (" 1", "%5x", 1); + checkf (" 1", "%5X", 1); + checkf (" 01", "%#5o", 1); + checkf (" 0x1", "%#5x", 1); + checkf (" 0X1", "%#5X", 1); + checkf ("0x00000001", "%#10.8x", 1); + + checkf ("123456", "%5u", 123456); + checkf ("361100", "%5o", 123456); + checkf ("1e240", "%5x", 123456); + checkf ("1E240", "%5X", 123456); + checkf ("0361100", "%#5o", 123456); + checkf ("0x1e240", "%#5x", 123456); + checkf ("0X1E240", "%#5X", 123456); + checkf ("0x0001e240", "%#10.8x", 123456); + + /* Character and string conversions. */ + checkf ("foobar", "%c%c%c%c%c%c", 'f', 'o', 'o', 'b', 'a', 'r'); + checkf (" left-right ", "%6s%s%-7s", "left", "-", "right"); + checkf ("trim", "%.4s", "trimoff"); + checkf ("%%", "%%%%"); + + /* From Cristian Cadar's automatic test case generator. */ + checkf (" abcdefgh", "%9s", "abcdefgh"); + checkf ("36657730000", "%- o", (unsigned) 036657730000); + checkf ("4139757568", "%- u", (unsigned) 4139757568UL); + checkf ("f6bfb000", "%- x", (unsigned) 0xf6bfb000); + checkf ("36657730000", "%-to", (ptrdiff_t) 036657730000); + checkf ("4139757568", "%-tu", (ptrdiff_t) 4139757568UL); + checkf ("-155209728", "%-zi", (size_t) -155209728); + checkf ("-155209728", "%-zd", (size_t) -155209728); + checkf ("036657730000", "%+#o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "%+#x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "% zi", (size_t) -155209728); + checkf ("-155209728", "% zd", (size_t) -155209728); + checkf ("4139757568", "% tu", (ptrdiff_t) 4139757568UL); + checkf ("036657730000", "% #o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "% #x", (unsigned) 0xf6bfb000); + checkf ("0xf6bfb000", "%# x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "%#zd", (size_t) -155209728); + checkf ("-155209728", "%0zi", (size_t) -155209728); + checkf ("4,139,757,568", "%'tu", (ptrdiff_t) 4139757568UL); + checkf ("-155,209,728", "%-'d", -155209728); + checkf ("-155209728", "%.zi", (size_t) -155209728); + checkf ("-155209728", "%zi", (size_t) -155209728); + checkf ("-155209728", "%zd", (size_t) -155209728); + checkf ("-155209728", "%+zi", (size_t) -155209728); + + if (failure_cnt == 0) + printf ("\nstdio: PASS\n"); + else + printf ("\nstdio: FAIL: %d tests failed\n", failure_cnt); +} diff --git a/tests/internal/stdlib.c b/tests/internal/stdlib.c new file mode 100644 index 0000000..ad0f0f9 --- /dev/null +++ b/tests/internal/stdlib.c @@ -0,0 +1,114 @@ +/* Test program for sorting and searching in lib/stdlib.c. + + Attempts to test the sorting and searching functionality that + is not sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include +#include +#include +#include +#include +#include "threads/test.h" + +/* Maximum number of elements in an array that we will test. */ +#define MAX_CNT 4096 + +static void shuffle (int[], size_t); +static int compare_ints (const void *, const void *); +static void verify_order (const int[], size_t); +static void verify_bsearch (const int[], size_t); + +/* Test sorting and searching implementations. */ +void +test (void) +{ + int cnt; + + printf ("testing various size arrays:"); + for (cnt = 0; cnt < MAX_CNT; cnt = cnt * 4 / 3 + 1) + { + int repeat; + + printf (" %zu", cnt); + for (repeat = 0; repeat < 10; repeat++) + { + static int values[MAX_CNT]; + int i; + + /* Put values 0...CNT in random order in VALUES. */ + for (i = 0; i < cnt; i++) + values[i] = i; + shuffle (values, cnt); + + /* Sort VALUES, then verify ordering. */ + qsort (values, cnt, sizeof *values, compare_ints); + verify_order (values, cnt); + verify_bsearch (values, cnt); + } + } + + printf (" done\n"); + printf ("stdlib: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (int *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + int t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns 1 if *A is greater than *B, + 0 if *A equals *B, + -1 if *A is less than *B. */ +static int +compare_ints (const void *a_, const void *b_) +{ + const int *a = a_; + const int *b = b_; + + return *a < *b ? -1 : *a > *b; +} + +/* Verifies that ARRAY contains the CNT ints 0...CNT-1. */ +static void +verify_order (const int *array, size_t cnt) +{ + int i; + + for (i = 0; (size_t) i < cnt; i++) + ASSERT (array[i] == i); +} + +/* Checks that bsearch() works properly in ARRAY. ARRAY must + contain the values 0...CNT-1. */ +static void +verify_bsearch (const int *array, size_t cnt) +{ + int not_in_array[] = {0, -1, INT_MAX, MAX_CNT, MAX_CNT + 1, MAX_CNT * 2}; + int i; + + /* Check that all the values in the array are found properly. */ + for (i = 0; (size_t) i < cnt; i++) + ASSERT (bsearch (&i, array, cnt, sizeof *array, compare_ints) + == array + i); + + /* Check that some values not in the array are not found. */ + not_in_array[0] = cnt; + for (i = 0; (size_t) i < sizeof not_in_array / sizeof *not_in_array; i++) + ASSERT (bsearch (¬_in_array[i], array, cnt, sizeof *array, compare_ints) + == NULL); +} diff --git a/tests/lib.c b/tests/lib.c new file mode 100644 index 0000000..ee36505 --- /dev/null +++ b/tests/lib.c @@ -0,0 +1,196 @@ +#include "tests/lib.h" +#include +#include +#include +#include +#include + +const char *test_name; +bool quiet = false; + +static void +vmsg (const char *format, va_list args, const char *suffix) +{ + /* We go to some trouble to stuff the entire message into a + single buffer and output it in a single system call, because + that'll (typically) ensure that it gets sent to the console + atomically. Otherwise kernel messages like "foo: exit(0)" + can end up being interleaved if we're unlucky. */ + static char buf[1024]; + + snprintf (buf, sizeof buf, "(%s) ", test_name); + vsnprintf (buf + strlen (buf), sizeof buf - strlen (buf), format, args); + strlcpy (buf + strlen (buf), suffix, sizeof buf - strlen (buf)); + write (STDOUT_FILENO, buf, strlen (buf)); +} + +void +msg (const char *format, ...) +{ + va_list args; + + if (quiet) + return; + va_start (args, format); + vmsg (format, args, "\n"); + va_end (args); +} + +void +fail (const char *format, ...) +{ + va_list args; + + va_start (args, format); + vmsg (format, args, ": FAILED\n"); + va_end (args); + + exit (1); +} + +static void +swap (void *a_, void *b_, size_t size) +{ + uint8_t *a = a_; + uint8_t *b = b_; + size_t i; + + for (i = 0; i < size; i++) + { + uint8_t t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +void +shuffle (void *buf_, size_t cnt, size_t size) +{ + char *buf = buf_; + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + swap (buf + i * size, buf + j * size, size); + } +} + +void +exec_children (const char *child_name, pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + char cmd_line[128]; + snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i); + CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR, + "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line); + } +} + +void +wait_children (pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + int status = wait (pids[i]); + CHECK (status == (int) i, + "wait for child %zu of %zu returned %d (expected %zu)", + i + 1, child_cnt, status, i); + } +} + +void +check_file_handle (int fd, + const char *file_name, const void *buf_, size_t size) +{ + const char *buf = buf_; + size_t ofs = 0; + size_t file_size; + + /* Warn about file of wrong size. Don't fail yet because we + may still be able to get more information by reading the + file. */ + file_size = filesize (fd); + if (file_size != size) + msg ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + /* Read the file block-by-block, comparing data as we go. */ + while (ofs < size) + { + char block[512]; + size_t block_size, ret_val; + + block_size = size - ofs; + if (block_size > sizeof block) + block_size = sizeof block; + + ret_val = read (fd, block, block_size); + if (ret_val != block_size) + fail ("read of %zu bytes at offset %zu in \"%s\" returned %zu", + block_size, ofs, file_name, ret_val); + + compare_bytes (block, buf + ofs, block_size, ofs, file_name); + ofs += block_size; + } + + /* Now fail due to wrong file size. */ + if (file_size != size) + fail ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + msg ("verified contents of \"%s\"", file_name); +} + +void +check_file (const char *file_name, const void *buf, size_t size) +{ + int fd; + + CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification", + file_name); + check_file_handle (fd, file_name, buf, size); + msg ("close \"%s\"", file_name); + close (fd); +} + +void +compare_bytes (const void *read_data_, const void *expected_data_, size_t size, + size_t ofs, const char *file_name) +{ + const uint8_t *read_data = read_data_; + const uint8_t *expected_data = expected_data_; + size_t i, j; + size_t show_cnt; + + if (!memcmp (read_data, expected_data, size)) + return; + + for (i = 0; i < size; i++) + if (read_data[i] != expected_data[i]) + break; + for (j = i + 1; j < size; j++) + if (read_data[j] == expected_data[j]) + break; + + quiet = false; + msg ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected.", j - i, ofs + i, file_name); + show_cnt = j - i; + if (j - i > 64) + { + show_cnt = 64; + msg ("Showing first differing %zu bytes.", show_cnt); + } + msg ("Data actually read:"); + hex_dump (ofs + i, read_data + i, show_cnt, true); + msg ("Expected data:"); + hex_dump (ofs + i, expected_data + i, show_cnt, true); + fail ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected", j - i, ofs + i, file_name); +} diff --git a/tests/lib.h b/tests/lib.h new file mode 100644 index 0000000..648327b --- /dev/null +++ b/tests/lib.h @@ -0,0 +1,50 @@ +#ifndef TESTS_LIB_H +#define TESTS_LIB_H + +#include +#include +#include +#include + +extern const char *test_name; +extern bool quiet; + +void msg (const char *, ...) PRINTF_FORMAT (1, 2); +void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN; + +/* Takes an expression to test for SUCCESS and a message, which + may include printf-style arguments. Logs the message, then + tests the expression. If it is zero, indicating failure, + emits the message as a failure. + + Somewhat tricky to use: + + - SUCCESS must not have side effects that affect the + message, because that will cause the original message and + the failure message to differ. + + - The message must not have side effects of its own, because + it will be printed twice on failure, or zero times on + success if quiet is set. */ +#define CHECK(SUCCESS, ...) \ + do \ + { \ + msg (__VA_ARGS__); \ + if (!(SUCCESS)) \ + fail (__VA_ARGS__); \ + } \ + while (0) + +void shuffle (void *, size_t cnt, size_t size); + +void exec_children (const char *child_name, pid_t pids[], size_t child_cnt); +void wait_children (pid_t pids[], size_t child_cnt); + +void check_file_handle (int fd, const char *file_name, + const void *buf_, size_t filesize); +void check_file (const char *file_name, const void *buf, size_t filesize); + +void compare_bytes (const void *read_data, const void *expected_data, + size_t size, size_t ofs, const char *file_name); + +#endif /* test/lib.h */ diff --git a/tests/lib.pm b/tests/lib.pm new file mode 100644 index 0000000..bc37ae5 --- /dev/null +++ b/tests/lib.pm @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use tests::random; + +sub shuffle { + my ($in, $cnt, $sz) = @_; + $cnt * $sz == length $in or die; + my (@a) = 0...$cnt - 1; + for my $i (0...$cnt - 1) { + my ($j) = $i + random_ulong () % ($cnt - $i); + @a[$i, $j] = @a[$j, $i]; + } + my ($out) = ""; + $out .= substr ($in, $_ * $sz, $sz) foreach @a; + return $out; +} + +1; diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..ad1b0f1 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,15 @@ +#include +#include "tests/lib.h" +#include "tests/main.h" + +int +main (int argc UNUSED, char *argv[]) +{ + test_name = argv[0]; + + msg ("begin"); + random_init (0); + test_main (); + msg ("end"); + return 0; +} diff --git a/tests/main.h b/tests/main.h new file mode 100644 index 0000000..f0e8818 --- /dev/null +++ b/tests/main.h @@ -0,0 +1,6 @@ +#ifndef TESTS_MAIN_H +#define TESTS_MAIN_H + +void test_main (void); + +#endif /* tests/main.h */ diff --git a/tests/make-grade b/tests/make-grade new file mode 100755 index 0000000..a3faa0e --- /dev/null +++ b/tests/make-grade @@ -0,0 +1,152 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +@ARGV == 3 || die; +my ($src_dir, $results_file, $grading_file) = @ARGV; + +# Read pass/file verdicts from $results_file. +open (RESULTS, '<', $results_file) || die "$results_file: open: $!\n"; +my (%verdicts, %verdict_counts); +while () { + my ($verdict, $test) = /^(pass|FAIL) (.*)$/ or die; + $verdicts{$test} = $verdict eq 'pass'; +} +close RESULTS; + +my (@failures); +my (@overall, @rubrics, @summary); +my ($pct_actual, $pct_possible) = (0, 0); + +# Read grading file. +my (@items); +open (GRADING, '<', $grading_file) || die "$grading_file: open: $!\n"; +while () { + s/#.*//; + next if /^\s*$/; + my ($max_pct, $rubric_suffix) = /^\s*(\d+(?:\.\d+)?)%\t(.*)/ or die; + my ($dir) = $rubric_suffix =~ /^(.*)\//; + my ($rubric_file) = "$src_dir/$rubric_suffix"; + open (RUBRIC, '<', $rubric_file) or die "$rubric_file: open: $!\n"; + + # Rubric file must begin with title line. + my $title = ; + chomp $title; + $title =~ s/:$// or die; + $title .= " ($rubric_suffix):"; + push (@rubrics, $title); + + my ($score, $possible) = (0, 0); + my ($cnt, $passed) = (0, 0); + my ($was_score) = 0; + while () { + chomp; + push (@rubrics, "\t$_"), next if /^-/; + push (@rubrics, ""), next if /^\s*$/; + my ($poss, $name) = /^(\d+)\t(.*)$/ or die; + my ($test) = "$dir/$name"; + my ($points) = 0; + if (!defined $verdicts{$test}) { + push (@overall, "warning: $test not tested, assuming failure"); + } elsif ($verdicts{$test}) { + $points = $poss; + $passed++; + } + push (@failures, $test) if !$points; + $verdict_counts{$test}++; + push (@rubrics, sprintf ("\t%4s%2d/%2d %s", + $points ? '' : '**', $points, $poss, $test)); + $score += $points; + $possible += $poss; + $cnt++; + } + close (RUBRIC); + + push (@rubrics, ""); + push (@rubrics, "\t- Section summary."); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $passed, $cnt, 'tests passed')); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $score, $possible, 'points subtotal')); + push (@rubrics, ''); + + my ($pct) = ($score / $possible) * $max_pct; + push (@summary, sprintf ("%-45s %3d/%3d %5.1f%%/%5.1f%%", + $rubric_suffix, + $score, $possible, + $pct, $max_pct)); + $pct_actual += $pct; + $pct_possible += $max_pct; +} +close GRADING; + +my ($sum_line) + = "--------------------------------------------- --- --- ------ ------"; +unshift (@summary, + "SUMMARY BY TEST SET", + '', + sprintf ("%-45s %3s %3s %6s %6s", + "Test Set", "Pts", "Max", "% Ttl", "% Max"), + $sum_line); +push (@summary, + $sum_line, + sprintf ("%-45s %3s %3s %5.1f%%/%5.1f%%", + 'Total', '', '', $pct_actual, $pct_possible)); + +unshift (@rubrics, + "SUMMARY OF INDIVIDUAL TESTS", + ''); + +foreach my $name (keys (%verdicts)) { + my ($count) = $verdict_counts{$name}; + if (!defined ($count) || $count != 1) { + if (!defined ($count) || !$count) { + push (@overall, "warning: test $name doesn't count for grading"); + } else { + push (@overall, + "warning: test $name counted $count times in grading"); + } + } +} +push (@overall, sprintf ("TOTAL TESTING SCORE: %.1f%%", $pct_actual)); +if (sprintf ("%.1f", $pct_actual) eq sprintf ("%.1f", $pct_possible)) { + push (@overall, "ALL TESTED PASSED -- PERFECT SCORE"); +} + +my (@divider) = ('', '- ' x 38, ''); + +print map ("$_\n", @overall, @divider, @summary, @divider, @rubrics); + +for my $test (@failures) { + print map ("$_\n", @divider); + print "DETAILS OF $test FAILURE:\n\n"; + + if (open (RESULT, '<', "$test.result")) { + my $first_line = ; + my ($cnt) = 0; + while () { + print; + $cnt++; + } + close (RESULT); + } + + if (open (OUTPUT, '<', "$test.output")) { + print "\nOUTPUT FROM $test:\n\n"; + + my ($panics, $boots) = (0, 0); + while () { + if (/PANIC/ && ++$panics > 2) { + print "[...details of additional panic(s) omitted...]\n"; + last; + } + print; + if (/Pintos booting/ && ++$boots > 1) { + print "[...details of reboot(s) omitted...]\n"; + last; + } + } + close (OUTPUT); + } +} diff --git a/tests/random.pm b/tests/random.pm new file mode 100644 index 0000000..be008ff --- /dev/null +++ b/tests/random.pm @@ -0,0 +1,27 @@ +use strict; +use warnings; + +use tests::arc4; + +my (@arc4); + +sub random_init { + if (@arc4 == 0) { + my ($seed) = @_; + $seed = 0 if !defined $seed; + @arc4 = arc4_init (pack ("V", $seed)); + } +} + +sub random_bytes { + random_init (); + my ($n) = @_; + return arc4_crypt (\@arc4, "\0" x $n); +} + +sub random_ulong { + random_init (); + return unpack ("V", random_bytes (4)); +} + +1; diff --git a/tests/tests.pm b/tests/tests.pm new file mode 100644 index 0000000..bf41c75 --- /dev/null +++ b/tests/tests.pm @@ -0,0 +1,622 @@ +use strict; +use warnings; +use tests::Algorithm::Diff; +use File::Temp 'tempfile'; +use Fcntl qw(SEEK_SET SEEK_CUR); + +sub fail; +sub pass; + +die if @ARGV != 2; +our ($test, $src_dir) = @ARGV; + +my ($msg_file) = tempfile (); +select ($msg_file); + +our (@prereq_tests) = (); +if ($test =~ /^(.*)-persistence$/) { + push (@prereq_tests, $1); +} +for my $prereq_test (@prereq_tests) { + my (@result) = read_text_file ("$prereq_test.result"); + fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS'; +} + + +# Generic testing. + +sub check_expected { + my ($expected) = pop @_; + my (@options) = @_; + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + compare_output ("run", @options, \@output, $expected); +} + +sub common_checks { + my ($run, @output) = @_; + + fail "\u$run produced no output at all\n" if @output == 0; + + check_for_panic ($run, @output); + check_for_keyword ($run, "FAIL", @output); + check_for_triple_fault ($run, @output); + check_for_keyword ($run, "TIMEOUT", @output); + + fail "\u$run didn't start up properly: no \"Pintos booting\" message\n" + if !grep (/Pintos booting with/, @output); + fail "\u$run didn't start up properly: no \"Boot complete\" message\n" + if !grep (/Boot complete/, @output); + fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n" + if !grep (/Timer: \d+ ticks/, @output); + fail "\u$run didn't shut down properly: no \"Powering off\" message\n" + if !grep (/Powering off/, @output); +} + +sub check_for_panic { + my ($run, @output) = @_; + + my ($panic) = grep (/PANIC/, @output); + return unless defined $panic; + + print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")), + "\n"; + + my (@stack_line) = grep (/Call stack:/, @output); + if (@stack_line != 0) { + my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/; + + # Find a user program to translate user virtual addresses. + my ($userprog) = ""; + $userprog = "$test" + if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test; + + # Get and print the backtrace. + my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`); + print "Call stack:$addrs\n"; + print "Translation of call stack:\n"; + print $trace; + + # Print disclaimer. + if ($userprog ne '' && index ($trace, $userprog) >= 0) { + print <capacity/) { + print < 0; + + print < $_), @$expected)}; + } + foreach my $key (keys %$expected) { + my (@expected) = split ("\n", $expected->{$key}); + + $msg .= "Acceptable output:\n"; + $msg .= join ('', map (" $_\n", @expected)); + + # Check whether actual and expected match. + # If it's a perfect match, we're done. + if ($#output == $#expected) { + my ($eq) = 1; + for (my ($i) = 0; $i <= $#expected; $i++) { + $eq = 0 if $output[$i] ne $expected[$i]; + } + return $key if $eq; + } + + # They differ. Output a diff. + my (@diff) = ""; + my ($d) = Algorithm::Diff->new (\@expected, \@output); + while ($d->Next ()) { + my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2)); + if ($d->Same ()) { + push (@diff, map (" $_\n", $d->Items (1))); + } else { + push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1); + push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2); + } + } + + $msg .= "Differences in `diff -u' format:\n"; + $msg .= join ('', @diff); + } + + # Failed to match. Report failure. + $msg .= "\n(Process exit codes are excluded for matching purposes.)\n" + if $ignore_exit_codes; + $msg .= "\n(User fault messages are excluded for matching purposes.)\n" + if $ignore_user_faults; + fail "Test output failed to match any acceptable form.\n\n$msg"; +} + +# File system extraction. + +# check_archive (\%CONTENTS) +# +# Checks that the extracted file system's contents match \%CONTENTS. +# Each key in the hash is a file name. Each value may be: +# +# - $FILE: Name of a host file containing the expected contents. +# +# - [$FILE, $OFFSET, $LENGTH]: An excerpt of host file $FILE +# comprising the $LENGTH bytes starting at $OFFSET. +# +# - [$CONTENTS]: The literal expected file contents, as a string. +# +# - {SUBDIR}: A subdirectory, in the same form described here, +# recursively. +sub check_archive { + my ($expected_hier) = @_; + + my (@output) = read_text_file ("$test.output"); + common_checks ("file system extraction run", @output); + + @output = get_core_output ("file system extraction run", @output); + @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output); + fail join ("\n", "Error extracting file system:", @output) if @output; + + my ($test_base_name) = $test; + $test_base_name =~ s%.*/%%; + $test_base_name =~ s%-persistence$%%; + $expected_hier->{$test_base_name} = $prereq_tests[0]; + $expected_hier->{'tar'} = 'tests/filesys/extended/tar'; + + my (%expected) = normalize_fs (flatten_hierarchy ($expected_hier, "")); + my (%actual) = read_tar ("$prereq_tests[0].tar"); + + my ($errors) = 0; + foreach my $name (sort keys %expected) { + if (exists $actual{$name}) { + if (is_dir ($actual{$name}) && !is_dir ($expected{$name})) { + print "$name is a directory but should be an ordinary file.\n"; + $errors++; + } elsif (!is_dir ($actual{$name}) && is_dir ($expected{$name})) { + print "$name is an ordinary file but should be a directory.\n"; + $errors++; + } + } else { + print "$name is missing from the file system.\n"; + $errors++; + } + } + foreach my $name (sort keys %actual) { + if (!exists $expected{$name}) { + if ($name =~ /^[[:print:]]+$/) { + print "$name exists in the file system but it should not.\n"; + } else { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print <[0]); + $file = tempfile (); + syswrite ($file, $value->[0]) == $length + or die "writing temporary file: $!\n"; + sysseek ($file, 0, SEEK_SET); + } elsif (@$value == 3) { + $length = $value->[2]; + open ($file, '<', $value->[0]) or die "$value->[0]: open: $!\n"; + die "$value->[0]: file is smaller than expected\n" + if -s $file < $value->[1] + $length; + sysseek ($file, $value->[1], SEEK_SET); + } else { + die; + } + return ($file, $length); +} + +# compare_files ($A, $A_SIZE, $B, $B_SIZE, $NAME, $VERBOSE) +# +# Compares $A_SIZE bytes in $A to $B_SIZE bytes in $B. +# ($A and $B are handles.) +# If their contents differ, prints a brief message describing +# the differences, using $NAME to identify the file. +# The message contains more detail if $VERBOSE is nonzero. +# Returns 1 if the contents are identical, 0 otherwise. +sub compare_files { + my ($a, $a_size, $b, $b_size, $name, $verbose) = @_; + my ($ofs) = 0; + select(STDOUT); + for (;;) { + my ($a_amt) = $a_size >= 1024 ? 1024 : $a_size; + my ($b_amt) = $b_size >= 1024 ? 1024 : $b_size; + my ($a_data, $b_data); + if (!defined (sysread ($a, $a_data, $a_amt)) + || !defined (sysread ($b, $b_data, $b_amt))) { + die "reading $name: $!\n"; + } + + my ($a_len) = length $a_data; + my ($b_len) = length $b_data; + last if $a_len == 0 && $b_len == 0; + + if ($a_data ne $b_data) { + my ($min_len) = $a_len < $b_len ? $a_len : $b_len; + my ($diff_ofs); + for ($diff_ofs = 0; $diff_ofs < $min_len; $diff_ofs++) { + last if (substr ($a_data, $diff_ofs, 1) + ne substr ($b_data, $diff_ofs, 1)); + } + + printf "\nFile $name differs from expected " + . "starting at offset 0x%x.\n", $ofs + $diff_ofs; + if ($verbose ) { + print "Expected contents:\n"; + hex_dump (substr ($a_data, $diff_ofs, 64), $ofs + $diff_ofs); + print "Actual contents:\n"; + hex_dump (substr ($b_data, $diff_ofs, 64), $ofs + $diff_ofs); + } + return 0; + } + + $ofs += $a_len; + $a_size -= $a_len; + $b_size -= $b_len; + } + return 1; +} + +# hex_dump ($DATA, $OFS) +# +# Prints $DATA in hex and text formats. +# The first byte of $DATA corresponds to logical offset $OFS +# in whatever file the data comes from. +sub hex_dump { + my ($data, $ofs) = @_; + + if ($data eq '') { + printf " (File ends at offset %08x.)\n", $ofs; + return; + } + + my ($per_line) = 16; + while ((my $size = length ($data)) > 0) { + my ($start) = $ofs % $per_line; + my ($end) = $per_line; + $end = $start + $size if $end - $start > $size; + my ($n) = $end - $start; + + printf "0x%08x ", int ($ofs / $per_line) * $per_line; + + # Hex version. + print " " x $start; + for my $i ($start...$end - 1) { + printf "%02x", ord (substr ($data, $i - $start, 1)); + print $i == $per_line / 2 - 1 ? '-' : ' '; + } + print " " x ($per_line - $end); + + # Character version. + my ($esc_data) = substr ($data, 0, $n); + $esc_data =~ s/[^[:print:]]/./g; + print "|", " " x $start, $esc_data, " " x ($per_line - $end), "|"; + + print "\n"; + + $data = substr ($data, $n); + $ofs += $n; + } +} + +# print_fs (%FS) +# +# Prints a list of files in %FS, which must be a file system +# as flattened by flatten_hierarchy() and normalized by +# normalize_fs(). +sub print_fs { + my (%fs) = @_; + foreach my $name (sort keys %fs) { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print "$esc_name: "; + if (!is_dir ($fs{$name})) { + print +file_size ($fs{$name}), "-byte file"; + } else { + print "directory"; + } + print "\n"; + } + print "(empty)\n" if !@_; +} + +# normalize_fs (%FS) +# +# Takes a file system as flattened by flatten_hierarchy(). +# Returns a similar file system in which values of the form $FILE +# are replaced by those of the form [$FILE, $OFFSET, $LENGTH]. +sub normalize_fs { + my (%fs) = @_; + foreach my $name (keys %fs) { + my ($value) = $fs{$name}; + next if is_dir ($value) || ref ($value) ne ''; + die "can't open $value\n" if !stat $value; + $fs{$name} = [$value, 0, -s _]; + } + return %fs; +} + +# is_dir ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns 1 if it represents a directory, 0 otherwise. +sub is_dir { + my ($value) = @_; + return ref ($value) eq '' && $value eq 'directory'; +} + +# file_size ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns the size of the file it represents. +sub file_size { + my ($value) = @_; + die if is_dir ($value); + die if ref ($value) ne 'ARRAY'; + return @$value > 1 ? $value->[2] : length ($value->[0]); +} + +# flatten_hierarchy ($HIER_FS, $PREFIX) +# +# Takes a file system in the format expected by check_archive() and +# returns a "flattened" version in which file names include all parent +# directory names and the value of directories is just "directory". +sub flatten_hierarchy { + my (%hier_fs) = %{$_[0]}; + my ($prefix) = $_[1]; + my (%flat_fs); + for my $name (keys %hier_fs) { + my ($value) = $hier_fs{$name}; + if (ref $value eq 'HASH') { + %flat_fs = (%flat_fs, flatten_hierarchy ($value, "$prefix$name/")); + $flat_fs{"$prefix$name"} = 'directory'; + } else { + $flat_fs{"$prefix$name"} = $value; + } + } + return %flat_fs; +} + +# read_tar ($ARCHIVE) +# +# Reads the ustar-format tar file in $ARCHIVE +# and returns a flattened file system for it. +sub read_tar { + my ($archive) = @_; + my (%content); + open (ARCHIVE, '<', $archive) or fail "$archive: open: $!\n"; + for (;;) { + my ($header); + if ((my $retval = sysread (ARCHIVE, $header, 512)) != 512) { + fail "$archive: unexpected end of file\n" if $retval >= 0; + fail "$archive: read: $!\n"; + } + + last if $header eq "\0" x 512; + + # Verify magic numbers. + if (substr ($header, 257, 6) ne "ustar\0" + || substr ($header, 263, 2) ne '00') { + fail "$archive: corrupt ustar header\n"; + } + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8, ' ' x 8))); + my ($correct_chksum) = unpack ("%32a*", $header); + fail "$archive: bad header checksum\n" if $chksum != $correct_chksum; + + # Get file name. + my ($name) = unpack ("Z100", $header); + my ($prefix) = unpack ("Z*", substr ($header, 345)); + $name = "$prefix/$name" if $prefix ne ''; + fail "$archive: contains file with empty name" if $name eq ''; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + $typeflag = '0' if $typeflag eq "\0"; + fail "unknown file type '$typeflag'\n" if $typeflag !~ /[05]/; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + fail "bad size $size\n" if $size < 0; + $size = 0 if $typeflag eq '5'; + + # Store content. + if (exists $content{$name}) { + fail "$archive: contains multiple entries for $name\n"; + } + if ($typeflag eq '5') { + $content{$name} = 'directory'; + } else { + my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR); + $content{$name} = [$archive, $position, $size]; + sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR); + } + } + close (ARCHIVE); + return %content; +} + +# Utilities. + +sub fail { + finish ("FAIL", @_); +} + +sub pass { + finish ("PASS", @_); +} + +sub finish { + my ($verdict, @messages) = @_; + + seek ($msg_file, 0, 0); + push (@messages, <$msg_file>); + close ($msg_file); + chomp (@messages); + + my ($result_fn) = "$test.result"; + open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n"; + print RESULT "$verdict\n"; + print RESULT "$_\n" foreach @messages; + close (RESULT); + + if ($verdict eq 'PASS') { + print STDOUT "pass $test\n"; + } else { + print STDOUT "FAIL $test\n"; + } + print STDOUT "$_\n" foreach @messages; + + exit 0; +} + +sub read_text_file { + my ($file_name) = @_; + open (FILE, '<', $file_name) or die "$file_name: open: $!\n"; + my (@content) = ; + chomp (@content); + close (FILE); + return @content; +} + +1; diff --git a/tests/threads/Grading b/tests/threads/Grading new file mode 100644 index 0000000..c9be35f --- /dev/null +++ b/tests/threads/Grading @@ -0,0 +1,6 @@ +# Percentage of the testing point total designated for each set of +# tests. + +20.0% tests/threads/Rubric.alarm +40.0% tests/threads/Rubric.priority +40.0% tests/threads/Rubric.mlfqs diff --git a/tests/threads/Make.tests b/tests/threads/Make.tests new file mode 100644 index 0000000..4569035 --- /dev/null +++ b/tests/threads/Make.tests @@ -0,0 +1,53 @@ +# -*- makefile -*- + +# Test names. +tests/threads_TESTS = $(addprefix tests/threads/,alarm-single \ +alarm-multiple alarm-simultaneous alarm-priority alarm-zero \ +alarm-negative priority-change priority-donate-one \ +priority-donate-multiple priority-donate-multiple2 \ +priority-donate-nest priority-donate-sema priority-donate-lower \ +priority-fifo priority-preempt priority-sema priority-condvar \ +priority-donate-chain \ +mlfqs-load-1 mlfqs-load-60 mlfqs-load-avg mlfqs-recent-1 mlfqs-fair-2 \ +mlfqs-fair-20 mlfqs-nice-2 mlfqs-nice-10 mlfqs-block) + +# Sources for tests. +tests/threads_SRC = tests/threads/tests.c +tests/threads_SRC += tests/threads/alarm-wait.c +tests/threads_SRC += tests/threads/alarm-simultaneous.c +tests/threads_SRC += tests/threads/alarm-priority.c +tests/threads_SRC += tests/threads/alarm-zero.c +tests/threads_SRC += tests/threads/alarm-negative.c +tests/threads_SRC += tests/threads/priority-change.c +tests/threads_SRC += tests/threads/priority-donate-one.c +tests/threads_SRC += tests/threads/priority-donate-multiple.c +tests/threads_SRC += tests/threads/priority-donate-multiple2.c +tests/threads_SRC += tests/threads/priority-donate-nest.c +tests/threads_SRC += tests/threads/priority-donate-sema.c +tests/threads_SRC += tests/threads/priority-donate-lower.c +tests/threads_SRC += tests/threads/priority-fifo.c +tests/threads_SRC += tests/threads/priority-preempt.c +tests/threads_SRC += tests/threads/priority-sema.c +tests/threads_SRC += tests/threads/priority-condvar.c +tests/threads_SRC += tests/threads/priority-donate-chain.c +tests/threads_SRC += tests/threads/mlfqs-load-1.c +tests/threads_SRC += tests/threads/mlfqs-load-60.c +tests/threads_SRC += tests/threads/mlfqs-load-avg.c +tests/threads_SRC += tests/threads/mlfqs-recent-1.c +tests/threads_SRC += tests/threads/mlfqs-fair.c +tests/threads_SRC += tests/threads/mlfqs-block.c + +MLFQS_OUTPUTS = \ +tests/threads/mlfqs-load-1.output \ +tests/threads/mlfqs-load-60.output \ +tests/threads/mlfqs-load-avg.output \ +tests/threads/mlfqs-recent-1.output \ +tests/threads/mlfqs-fair-2.output \ +tests/threads/mlfqs-fair-20.output \ +tests/threads/mlfqs-nice-2.output \ +tests/threads/mlfqs-nice-10.output \ +tests/threads/mlfqs-block.output + +$(MLFQS_OUTPUTS): KERNELFLAGS += -mlfqs +$(MLFQS_OUTPUTS): TIMEOUT = 480 + diff --git a/tests/threads/Rubric.alarm b/tests/threads/Rubric.alarm new file mode 100644 index 0000000..61abe85 --- /dev/null +++ b/tests/threads/Rubric.alarm @@ -0,0 +1,8 @@ +Functionality and robustness of alarm clock: +4 alarm-single +4 alarm-multiple +4 alarm-simultaneous +4 alarm-priority + +1 alarm-zero +1 alarm-negative diff --git a/tests/threads/Rubric.mlfqs b/tests/threads/Rubric.mlfqs new file mode 100644 index 0000000..f260091 --- /dev/null +++ b/tests/threads/Rubric.mlfqs @@ -0,0 +1,14 @@ +Functionality of advanced scheduler: +5 mlfqs-load-1 +5 mlfqs-load-60 +3 mlfqs-load-avg + +5 mlfqs-recent-1 + +5 mlfqs-fair-2 +3 mlfqs-fair-20 + +4 mlfqs-nice-2 +2 mlfqs-nice-10 + +5 mlfqs-block diff --git a/tests/threads/Rubric.priority b/tests/threads/Rubric.priority new file mode 100644 index 0000000..652bc99 --- /dev/null +++ b/tests/threads/Rubric.priority @@ -0,0 +1,15 @@ +Functionality of priority scheduler: +3 priority-change +3 priority-preempt + +3 priority-fifo +3 priority-sema +3 priority-condvar + +3 priority-donate-one +3 priority-donate-multiple +3 priority-donate-multiple2 +3 priority-donate-nest +5 priority-donate-chain +3 priority-donate-sema +3 priority-donate-lower diff --git a/tests/threads/alarm-multiple.ck b/tests/threads/alarm-multiple.ck new file mode 100644 index 0000000..fd83bcd --- /dev/null +++ b/tests/threads/alarm-multiple.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (7); diff --git a/tests/threads/alarm-negative.c b/tests/threads/alarm-negative.c new file mode 100644 index 0000000..aec52cf --- /dev/null +++ b/tests/threads/alarm-negative.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(-100). Only requirement is that it not crash. */ + +#include +#include "tests/threads/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_negative (void) +{ + timer_sleep (-100); + pass (); +} diff --git a/tests/threads/alarm-negative.ck b/tests/threads/alarm-negative.ck new file mode 100644 index 0000000..0d2bab0 --- /dev/null +++ b/tests/threads/alarm-negative.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-negative) begin +(alarm-negative) PASS +(alarm-negative) end +EOF +pass; diff --git a/tests/threads/alarm-priority.c b/tests/threads/alarm-priority.c new file mode 100644 index 0000000..2288ff6 --- /dev/null +++ b/tests/threads/alarm-priority.c @@ -0,0 +1,58 @@ +/* Checks that when the alarm clock wakes up threads, the + higher-priority threads run first. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func alarm_priority_thread; +static int64_t wake_time; +static struct semaphore wait_sema; + +void +test_alarm_priority (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + wake_time = timer_ticks () + 5 * TIMER_FREQ; + sema_init (&wait_sema, 0); + + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 5) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, alarm_priority_thread, NULL); + } + + thread_set_priority (PRI_MIN); + + for (i = 0; i < 10; i++) + sema_down (&wait_sema); +} + +static void +alarm_priority_thread (void *aux UNUSED) +{ + /* Busy-wait until the current time changes. */ + int64_t start_time = timer_ticks (); + while (timer_elapsed (start_time) == 0) + continue; + + /* Now we know we're at the very beginning of a timer tick, so + we can call timer_sleep() without worrying about races + between checking the time and a timer interrupt. */ + timer_sleep (wake_time - timer_ticks ()); + + /* Print a message on wake-up. */ + msg ("Thread %s woke up.", thread_name ()); + + sema_up (&wait_sema); +} diff --git a/tests/threads/alarm-priority.ck b/tests/threads/alarm-priority.ck new file mode 100644 index 0000000..b57c78b --- /dev/null +++ b/tests/threads/alarm-priority.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-priority) begin +(alarm-priority) Thread priority 30 woke up. +(alarm-priority) Thread priority 29 woke up. +(alarm-priority) Thread priority 28 woke up. +(alarm-priority) Thread priority 27 woke up. +(alarm-priority) Thread priority 26 woke up. +(alarm-priority) Thread priority 25 woke up. +(alarm-priority) Thread priority 24 woke up. +(alarm-priority) Thread priority 23 woke up. +(alarm-priority) Thread priority 22 woke up. +(alarm-priority) Thread priority 21 woke up. +(alarm-priority) end +EOF +pass; diff --git a/tests/threads/alarm-simultaneous.c b/tests/threads/alarm-simultaneous.c new file mode 100644 index 0000000..844eea4 --- /dev/null +++ b/tests/threads/alarm-simultaneous.c @@ -0,0 +1,94 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_simultaneous (void) +{ + test_sleep (3, 5); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + int *output_pos; /* Current position in output buffer. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + int *output; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Each thread sleeps 10 ticks each time."); + msg ("Within an iteration, all threads should wake up on the same tick."); + + /* Allocate memory. */ + output = malloc (sizeof *output * iterations * thread_cnt * 2); + if (output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + char name[16]; + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, &test); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + iterations * 10 + 100); + + /* Print completion order. */ + msg ("iteration 0, thread 0: woke up after %d ticks", output[0]); + for (i = 1; i < test.output_pos - output; i++) + msg ("iteration %d, thread %d: woke up %d ticks later", + i / thread_cnt, i % thread_cnt, output[i] - output[i - 1]); + + free (output); +} + +/* Sleeper thread. */ +static void +sleeper (void *test_) +{ + struct sleep_test *test = test_; + int i; + + /* Make sure we're at the beginning of a timer tick. */ + timer_sleep (1); + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * 10; + timer_sleep (sleep_until - timer_ticks ()); + *test->output_pos++ = timer_ticks () - test->start; + thread_yield (); + } +} diff --git a/tests/threads/alarm-simultaneous.ck b/tests/threads/alarm-simultaneous.ck new file mode 100644 index 0000000..406b8b0 --- /dev/null +++ b/tests/threads/alarm-simultaneous.ck @@ -0,0 +1,27 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-simultaneous) begin +(alarm-simultaneous) Creating 3 threads to sleep 5 times each. +(alarm-simultaneous) Each thread sleeps 10 ticks each time. +(alarm-simultaneous) Within an iteration, all threads should wake up on the same tick. +(alarm-simultaneous) iteration 0, thread 0: woke up after 10 ticks +(alarm-simultaneous) iteration 0, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 0, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 1, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 2, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 3, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 4, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 2: woke up 0 ticks later +(alarm-simultaneous) end +EOF +pass; diff --git a/tests/threads/alarm-single.ck b/tests/threads/alarm-single.ck new file mode 100644 index 0000000..31215df --- /dev/null +++ b/tests/threads/alarm-single.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (1); diff --git a/tests/threads/alarm-wait.c b/tests/threads/alarm-wait.c new file mode 100644 index 0000000..37d3afc --- /dev/null +++ b/tests/threads/alarm-wait.c @@ -0,0 +1,152 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_single (void) +{ + test_sleep (5, 1); +} + +void +test_alarm_multiple (void) +{ + test_sleep (5, 7); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + + /* Output. */ + struct lock output_lock; /* Lock protecting output buffer. */ + int *output_pos; /* Current position in output buffer. */ + }; + +/* Information about an individual thread in the test. */ +struct sleep_thread + { + struct sleep_test *test; /* Info shared between all threads. */ + int id; /* Sleeper ID. */ + int duration; /* Number of ticks to sleep. */ + int iterations; /* Iterations counted so far. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + struct sleep_thread *threads; + int *output, *op; + int product; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Thread 0 sleeps 10 ticks each time,"); + msg ("thread 1 sleeps 20 ticks each time, and so on."); + msg ("If successful, product of iteration count and"); + msg ("sleep duration will appear in nondescending order."); + + /* Allocate memory. */ + threads = malloc (sizeof *threads * thread_cnt); + output = malloc (sizeof *output * iterations * thread_cnt * 2); + if (threads == NULL || output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + lock_init (&test.output_lock); + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + struct sleep_thread *t = threads + i; + char name[16]; + + t->test = &test; + t->id = i; + t->duration = (i + 1) * 10; + t->iterations = 0; + + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, t); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + thread_cnt * iterations * 10 + 100); + + /* Acquire the output lock in case some rogue thread is still + running. */ + lock_acquire (&test.output_lock); + + /* Print completion order. */ + product = 0; + for (op = output; op < test.output_pos; op++) + { + struct sleep_thread *t; + int new_prod; + + ASSERT (*op >= 0 && *op < thread_cnt); + t = threads + *op; + + new_prod = ++t->iterations * t->duration; + + msg ("thread %d: duration=%d, iteration=%d, product=%d", + t->id, t->duration, t->iterations, new_prod); + + if (new_prod >= product) + product = new_prod; + else + fail ("thread %d woke up out of order (%d > %d)!", + t->id, product, new_prod); + } + + /* Verify that we had the proper number of wakeups. */ + for (i = 0; i < thread_cnt; i++) + if (threads[i].iterations != iterations) + fail ("thread %d woke up %d times instead of %d", + i, threads[i].iterations, iterations); + + lock_release (&test.output_lock); + free (output); + free (threads); +} + +/* Sleeper thread. */ +static void +sleeper (void *t_) +{ + struct sleep_thread *t = t_; + struct sleep_test *test = t->test; + int i; + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * t->duration; + timer_sleep (sleep_until - timer_ticks ()); + lock_acquire (&test->output_lock); + *test->output_pos++ = t->id; + lock_release (&test->output_lock); + } +} diff --git a/tests/threads/alarm-zero.c b/tests/threads/alarm-zero.c new file mode 100644 index 0000000..c8a3ee2 --- /dev/null +++ b/tests/threads/alarm-zero.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(0), which should return immediately. */ + +#include +#include "tests/threads/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_zero (void) +{ + timer_sleep (0); + pass (); +} diff --git a/tests/threads/alarm-zero.ck b/tests/threads/alarm-zero.ck new file mode 100644 index 0000000..a6b1a3c --- /dev/null +++ b/tests/threads/alarm-zero.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-zero) begin +(alarm-zero) PASS +(alarm-zero) end +EOF +pass; diff --git a/tests/threads/alarm.pm b/tests/threads/alarm.pm new file mode 100644 index 0000000..84b3b7f --- /dev/null +++ b/tests/threads/alarm.pm @@ -0,0 +1,32 @@ +sub check_alarm { + my ($iterations) = @_; + our ($test); + + @output = read_text_file ("$test.output"); + common_checks ("run", @output); + + my (@products); + for (my ($i) = 0; $i < $iterations; $i++) { + for (my ($t) = 0; $t < 5; $t++) { + push (@products, ($i + 1) * ($t + 1) * 10); + } + } + @products = sort {$a <=> $b} @products; + + local ($_); + foreach (@output) { + fail $_ if /out of order/i; + + my ($p) = /product=(\d+)$/; + next if !defined $p; + + my ($q) = shift (@products); + fail "Too many wakeups.\n" if !defined $q; + fail "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME + } + fail scalar (@products) . " fewer wakeups than expected.\n" + if @products != 0; + pass; +} + +1; diff --git a/tests/threads/mlfqs-block.c b/tests/threads/mlfqs-block.c new file mode 100644 index 0000000..6d4992d --- /dev/null +++ b/tests/threads/mlfqs-block.c @@ -0,0 +1,64 @@ +/* Checks that recent_cpu and priorities are updated for blocked + threads. + + The main thread sleeps for 25 seconds, spins for 5 seconds, + then releases a lock. The "block" thread spins for 20 seconds + then attempts to acquire the lock, which will block for 10 + seconds (until the main thread releases it). If recent_cpu + decays properly while the "block" thread sleeps, then the + block thread should be immediately scheduled when the main + thread releases the lock. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void block_thread (void *lock_); + +void +test_mlfqs_block (void) +{ + int64_t start_time; + struct lock lock; + + ASSERT (thread_mlfqs); + + msg ("Main thread acquiring lock."); + lock_init (&lock); + lock_acquire (&lock); + + msg ("Main thread creating block thread, sleeping 25 seconds..."); + thread_create ("block", PRI_DEFAULT, block_thread, &lock); + timer_sleep (25 * TIMER_FREQ); + + msg ("Main thread spinning for 5 seconds..."); + start_time = timer_ticks (); + while (timer_elapsed (start_time) < 5 * TIMER_FREQ) + continue; + + msg ("Main thread releasing lock."); + lock_release (&lock); + + msg ("Block thread should have already acquired lock."); +} + +static void +block_thread (void *lock_) +{ + struct lock *lock = lock_; + int64_t start_time; + + msg ("Block thread spinning for 20 seconds..."); + start_time = timer_ticks (); + while (timer_elapsed (start_time) < 20 * TIMER_FREQ) + continue; + + msg ("Block thread acquiring lock..."); + lock_acquire (lock); + + msg ("...got it."); +} diff --git a/tests/threads/mlfqs-block.ck b/tests/threads/mlfqs-block.ck new file mode 100644 index 0000000..8833a3a --- /dev/null +++ b/tests/threads/mlfqs-block.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(mlfqs-block) begin +(mlfqs-block) Main thread acquiring lock. +(mlfqs-block) Main thread creating block thread, sleeping 25 seconds... +(mlfqs-block) Block thread spinning for 20 seconds... +(mlfqs-block) Block thread acquiring lock... +(mlfqs-block) Main thread spinning for 5 seconds... +(mlfqs-block) Main thread releasing lock. +(mlfqs-block) ...got it. +(mlfqs-block) Block thread should have already acquired lock. +(mlfqs-block) end +EOF +pass; diff --git a/tests/threads/mlfqs-fair-2.ck b/tests/threads/mlfqs-fair-2.ck new file mode 100644 index 0000000..5b19ff1 --- /dev/null +++ b/tests/threads/mlfqs-fair-2.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0, 0], 50); diff --git a/tests/threads/mlfqs-fair-20.ck b/tests/threads/mlfqs-fair-20.ck new file mode 100644 index 0000000..bb4d051 --- /dev/null +++ b/tests/threads/mlfqs-fair-20.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([(0) x 20], 20); diff --git a/tests/threads/mlfqs-fair.c b/tests/threads/mlfqs-fair.c new file mode 100644 index 0000000..3b1bea5 --- /dev/null +++ b/tests/threads/mlfqs-fair.c @@ -0,0 +1,124 @@ +/* Measures the correctness of the "nice" implementation. + + The "fair" tests run either 2 or 20 threads all niced to 0. + The threads should all receive approximately the same number + of ticks. Each test runs for 30 seconds, so the ticks should + also sum to approximately 30 * 100 == 3000 ticks. + + The mlfqs-nice-2 test runs 2 threads, one with nice 0, the + other with nice 5, which should receive 1,904 and 1,096 ticks, + respectively, over 30 seconds. + + The mlfqs-nice-10 test runs 10 threads with nice 0 through 9. + They should receive 672, 588, 492, 408, 316, 232, 152, 92, 40, + and 8 ticks, respectively, over 30 seconds. + + (The above are computed via simulation in mlfqs.pm.) */ + +#include +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step); + +void +test_mlfqs_fair_2 (void) +{ + test_mlfqs_fair (2, 0, 0); +} + +void +test_mlfqs_fair_20 (void) +{ + test_mlfqs_fair (20, 0, 0); +} + +void +test_mlfqs_nice_2 (void) +{ + test_mlfqs_fair (2, 0, 5); +} + +void +test_mlfqs_nice_10 (void) +{ + test_mlfqs_fair (10, 0, 1); +} + +#define MAX_THREAD_CNT 20 + +struct thread_info + { + int64_t start_time; + int tick_count; + int nice; + }; + +static void load_thread (void *aux); + +static void +test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step) +{ + struct thread_info info[MAX_THREAD_CNT]; + int64_t start_time; + int nice; + int i; + + ASSERT (thread_mlfqs); + ASSERT (thread_cnt <= MAX_THREAD_CNT); + ASSERT (nice_min >= -10); + ASSERT (nice_step >= 0); + ASSERT (nice_min + nice_step * (thread_cnt - 1) <= 20); + + thread_set_nice (-20); + + start_time = timer_ticks (); + msg ("Starting %d threads...", thread_cnt); + nice = nice_min; + for (i = 0; i < thread_cnt; i++) + { + struct thread_info *ti = &info[i]; + char name[16]; + + ti->start_time = start_time; + ti->tick_count = 0; + ti->nice = nice; + + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, ti); + + nice += nice_step; + } + msg ("Starting threads took %"PRId64" ticks.", timer_elapsed (start_time)); + + msg ("Sleeping 40 seconds to let threads run, please wait..."); + timer_sleep (40 * TIMER_FREQ); + + for (i = 0; i < thread_cnt; i++) + msg ("Thread %d received %d ticks.", i, info[i].tick_count); +} + +static void +load_thread (void *ti_) +{ + struct thread_info *ti = ti_; + int64_t sleep_time = 5 * TIMER_FREQ; + int64_t spin_time = sleep_time + 30 * TIMER_FREQ; + int64_t last_time = 0; + + thread_set_nice (ti->nice); + timer_sleep (sleep_time - timer_elapsed (ti->start_time)); + while (timer_elapsed (ti->start_time) < spin_time) + { + int64_t cur_time = timer_ticks (); + if (cur_time != last_time) + ti->tick_count++; + last_time = cur_time; + } +} diff --git a/tests/threads/mlfqs-load-1.c b/tests/threads/mlfqs-load-1.c new file mode 100644 index 0000000..a39eea2 --- /dev/null +++ b/tests/threads/mlfqs-load-1.c @@ -0,0 +1,60 @@ +/* Verifies that a single busy thread raises the load average to + 0.5 in 38 to 45 seconds. The expected time is 42 seconds, as + you can verify: + perl -e '$i++,$a=(59*$a+1)/60while$a<=.5;print "$i\n"' + + Then, verifies that 10 seconds of inactivity drop the load + average back below 0.5 again. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_mlfqs_load_1 (void) +{ + int64_t start_time; + int elapsed; + int load_avg; + + ASSERT (thread_mlfqs); + + msg ("spinning for up to 45 seconds, please wait..."); + + start_time = timer_ticks (); + for (;;) + { + load_avg = thread_get_load_avg (); + ASSERT (load_avg >= 0); + elapsed = timer_elapsed (start_time) / TIMER_FREQ; + if (load_avg > 100) + fail ("load average is %d.%02d " + "but should be between 0 and 1 (after %d seconds)", + load_avg / 100, load_avg % 100, elapsed); + else if (load_avg > 50) + break; + else if (elapsed > 45) + fail ("load average stayed below 0.5 for more than 45 seconds"); + } + + if (elapsed < 38) + fail ("load average took only %d seconds to rise above 0.5", elapsed); + msg ("load average rose to 0.5 after %d seconds", elapsed); + + msg ("sleeping for another 10 seconds, please wait..."); + timer_sleep (TIMER_FREQ * 10); + + load_avg = thread_get_load_avg (); + if (load_avg < 0) + fail ("load average fell below 0"); + if (load_avg > 50) + fail ("load average stayed above 0.5 for more than 10 seconds"); + msg ("load average fell back below 0.5 (to %d.%02d)", + load_avg / 100, load_avg % 100); + + pass (); +} diff --git a/tests/threads/mlfqs-load-1.ck b/tests/threads/mlfqs-load-1.ck new file mode 100644 index 0000000..faf0ffa --- /dev/null +++ b/tests/threads/mlfqs-load-1.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +@output = get_core_output ("run", @output); +fail "missing PASS in output" + unless grep ($_ eq '(mlfqs-load-1) PASS', @output); + +pass; diff --git a/tests/threads/mlfqs-load-60.c b/tests/threads/mlfqs-load-60.c new file mode 100644 index 0000000..b6a3eb6 --- /dev/null +++ b/tests/threads/mlfqs-load-60.c @@ -0,0 +1,155 @@ +/* Starts 60 threads that each sleep for 10 seconds, then spin in + a tight loop for 60 seconds, and sleep for another 60 seconds. + Every 2 seconds after the initial sleep, the main thread + prints the load average. + + The expected output is this (some margin of error is allowed): + + After 0 seconds, load average=1.00. + After 2 seconds, load average=2.95. + After 4 seconds, load average=4.84. + After 6 seconds, load average=6.66. + After 8 seconds, load average=8.42. + After 10 seconds, load average=10.13. + After 12 seconds, load average=11.78. + After 14 seconds, load average=13.37. + After 16 seconds, load average=14.91. + After 18 seconds, load average=16.40. + After 20 seconds, load average=17.84. + After 22 seconds, load average=19.24. + After 24 seconds, load average=20.58. + After 26 seconds, load average=21.89. + After 28 seconds, load average=23.15. + After 30 seconds, load average=24.37. + After 32 seconds, load average=25.54. + After 34 seconds, load average=26.68. + After 36 seconds, load average=27.78. + After 38 seconds, load average=28.85. + After 40 seconds, load average=29.88. + After 42 seconds, load average=30.87. + After 44 seconds, load average=31.84. + After 46 seconds, load average=32.77. + After 48 seconds, load average=33.67. + After 50 seconds, load average=34.54. + After 52 seconds, load average=35.38. + After 54 seconds, load average=36.19. + After 56 seconds, load average=36.98. + After 58 seconds, load average=37.74. + After 60 seconds, load average=37.48. + After 62 seconds, load average=36.24. + After 64 seconds, load average=35.04. + After 66 seconds, load average=33.88. + After 68 seconds, load average=32.76. + After 70 seconds, load average=31.68. + After 72 seconds, load average=30.63. + After 74 seconds, load average=29.62. + After 76 seconds, load average=28.64. + After 78 seconds, load average=27.69. + After 80 seconds, load average=26.78. + After 82 seconds, load average=25.89. + After 84 seconds, load average=25.04. + After 86 seconds, load average=24.21. + After 88 seconds, load average=23.41. + After 90 seconds, load average=22.64. + After 92 seconds, load average=21.89. + After 94 seconds, load average=21.16. + After 96 seconds, load average=20.46. + After 98 seconds, load average=19.79. + After 100 seconds, load average=19.13. + After 102 seconds, load average=18.50. + After 104 seconds, load average=17.89. + After 106 seconds, load average=17.30. + After 108 seconds, load average=16.73. + After 110 seconds, load average=16.17. + After 112 seconds, load average=15.64. + After 114 seconds, load average=15.12. + After 116 seconds, load average=14.62. + After 118 seconds, load average=14.14. + After 120 seconds, load average=13.67. + After 122 seconds, load average=13.22. + After 124 seconds, load average=12.78. + After 126 seconds, load average=12.36. + After 128 seconds, load average=11.95. + After 130 seconds, load average=11.56. + After 132 seconds, load average=11.17. + After 134 seconds, load average=10.80. + After 136 seconds, load average=10.45. + After 138 seconds, load average=10.10. + After 140 seconds, load average=9.77. + After 142 seconds, load average=9.45. + After 144 seconds, load average=9.13. + After 146 seconds, load average=8.83. + After 148 seconds, load average=8.54. + After 150 seconds, load average=8.26. + After 152 seconds, load average=7.98. + After 154 seconds, load average=7.72. + After 156 seconds, load average=7.47. + After 158 seconds, load average=7.22. + After 160 seconds, load average=6.98. + After 162 seconds, load average=6.75. + After 164 seconds, load average=6.53. + After 166 seconds, load average=6.31. + After 168 seconds, load average=6.10. + After 170 seconds, load average=5.90. + After 172 seconds, load average=5.70. + After 174 seconds, load average=5.52. + After 176 seconds, load average=5.33. + After 178 seconds, load average=5.16. +*/ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static int64_t start_time; + +static void load_thread (void *aux); + +#define THREAD_CNT 60 + +void +test_mlfqs_load_60 (void) +{ + int i; + + ASSERT (thread_mlfqs); + + start_time = timer_ticks (); + msg ("Starting %d niced load threads...", THREAD_CNT); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, NULL); + } + msg ("Starting threads took %d seconds.", + timer_elapsed (start_time) / TIMER_FREQ); + + for (i = 0; i < 90; i++) + { + int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10); + int load_avg; + timer_sleep (sleep_until - timer_ticks ()); + load_avg = thread_get_load_avg (); + msg ("After %d seconds, load average=%d.%02d.", + i * 2, load_avg / 100, load_avg % 100); + } +} + +static void +load_thread (void *aux UNUSED) +{ + int64_t sleep_time = 10 * TIMER_FREQ; + int64_t spin_time = sleep_time + 60 * TIMER_FREQ; + int64_t exit_time = spin_time + 60 * TIMER_FREQ; + + thread_set_nice (20); + timer_sleep (sleep_time - timer_elapsed (start_time)); + while (timer_elapsed (start_time) < spin_time) + continue; + timer_sleep (exit_time - timer_elapsed (start_time)); +} diff --git a/tests/threads/mlfqs-load-60.ck b/tests/threads/mlfqs-load-60.ck new file mode 100644 index 0000000..cb69220 --- /dev/null +++ b/tests/threads/mlfqs-load-60.ck @@ -0,0 +1,36 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); + +my (@output) = read_text_file ("$test.output"); +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./ + or next; + $actual[$t] = $load_avg; +} + +# Calculate expected values. +my ($load_avg) = 0; +my ($recent) = 0; +my (@expected); +for (my ($t) = 0; $t < 180; $t++) { + my ($ready) = $t < 60 ? 60 : 0; + $load_avg = (59/60) * $load_avg + (1/60) * $ready; + $expected[$t] = $load_avg; +} + +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 3.5, [2, 178, 2], + "Some load average values were missing or " + . "differed from those expected " + . "by more than 3.5."); +pass; diff --git a/tests/threads/mlfqs-load-avg.c b/tests/threads/mlfqs-load-avg.c new file mode 100644 index 0000000..50e83e2 --- /dev/null +++ b/tests/threads/mlfqs-load-avg.c @@ -0,0 +1,167 @@ +/* Starts 60 threads numbered 0 through 59. Thread #i sleeps for + (10+i) seconds, then spins in a loop for 60 seconds, then + sleeps until a total of 120 seconds have passed. Every 2 + seconds, starting 10 seconds in, the main thread prints the + load average. + + The expected output is listed below. Some margin of error is + allowed. + + If your implementation fails this test but passes most other + tests, then consider whether you are doing too much work in + the timer interrupt. If the timer interrupt handler takes too + long, then the test's main thread will not have enough time to + do its own work (printing a message) and go back to sleep + before the next tick arrives. Then the main thread will be + ready, instead of sleeping, when the tick arrives, + artificially driving up the load average. + + After 0 seconds, load average=0.00. + After 2 seconds, load average=0.05. + After 4 seconds, load average=0.16. + After 6 seconds, load average=0.34. + After 8 seconds, load average=0.58. + After 10 seconds, load average=0.87. + After 12 seconds, load average=1.22. + After 14 seconds, load average=1.63. + After 16 seconds, load average=2.09. + After 18 seconds, load average=2.60. + After 20 seconds, load average=3.16. + After 22 seconds, load average=3.76. + After 24 seconds, load average=4.42. + After 26 seconds, load average=5.11. + After 28 seconds, load average=5.85. + After 30 seconds, load average=6.63. + After 32 seconds, load average=7.46. + After 34 seconds, load average=8.32. + After 36 seconds, load average=9.22. + After 38 seconds, load average=10.15. + After 40 seconds, load average=11.12. + After 42 seconds, load average=12.13. + After 44 seconds, load average=13.16. + After 46 seconds, load average=14.23. + After 48 seconds, load average=15.33. + After 50 seconds, load average=16.46. + After 52 seconds, load average=17.62. + After 54 seconds, load average=18.81. + After 56 seconds, load average=20.02. + After 58 seconds, load average=21.26. + After 60 seconds, load average=22.52. + After 62 seconds, load average=23.71. + After 64 seconds, load average=24.80. + After 66 seconds, load average=25.78. + After 68 seconds, load average=26.66. + After 70 seconds, load average=27.45. + After 72 seconds, load average=28.14. + After 74 seconds, load average=28.75. + After 76 seconds, load average=29.27. + After 78 seconds, load average=29.71. + After 80 seconds, load average=30.06. + After 82 seconds, load average=30.34. + After 84 seconds, load average=30.55. + After 86 seconds, load average=30.68. + After 88 seconds, load average=30.74. + After 90 seconds, load average=30.73. + After 92 seconds, load average=30.66. + After 94 seconds, load average=30.52. + After 96 seconds, load average=30.32. + After 98 seconds, load average=30.06. + After 100 seconds, load average=29.74. + After 102 seconds, load average=29.37. + After 104 seconds, load average=28.95. + After 106 seconds, load average=28.47. + After 108 seconds, load average=27.94. + After 110 seconds, load average=27.36. + After 112 seconds, load average=26.74. + After 114 seconds, load average=26.07. + After 116 seconds, load average=25.36. + After 118 seconds, load average=24.60. + After 120 seconds, load average=23.81. + After 122 seconds, load average=23.02. + After 124 seconds, load average=22.26. + After 126 seconds, load average=21.52. + After 128 seconds, load average=20.81. + After 130 seconds, load average=20.12. + After 132 seconds, load average=19.46. + After 134 seconds, load average=18.81. + After 136 seconds, load average=18.19. + After 138 seconds, load average=17.59. + After 140 seconds, load average=17.01. + After 142 seconds, load average=16.45. + After 144 seconds, load average=15.90. + After 146 seconds, load average=15.38. + After 148 seconds, load average=14.87. + After 150 seconds, load average=14.38. + After 152 seconds, load average=13.90. + After 154 seconds, load average=13.44. + After 156 seconds, load average=13.00. + After 158 seconds, load average=12.57. + After 160 seconds, load average=12.15. + After 162 seconds, load average=11.75. + After 164 seconds, load average=11.36. + After 166 seconds, load average=10.99. + After 168 seconds, load average=10.62. + After 170 seconds, load average=10.27. + After 172 seconds, load average=9.93. + After 174 seconds, load average=9.61. + After 176 seconds, load average=9.29. + After 178 seconds, load average=8.98. +*/ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static int64_t start_time; + +static void load_thread (void *seq_no); + +#define THREAD_CNT 60 + +void +test_mlfqs_load_avg (void) +{ + int i; + + ASSERT (thread_mlfqs); + + start_time = timer_ticks (); + msg ("Starting %d load threads...", THREAD_CNT); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, (void *) i); + } + msg ("Starting threads took %d seconds.", + timer_elapsed (start_time) / TIMER_FREQ); + thread_set_nice (-20); + + for (i = 0; i < 90; i++) + { + int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10); + int load_avg; + timer_sleep (sleep_until - timer_ticks ()); + load_avg = thread_get_load_avg (); + msg ("After %d seconds, load average=%d.%02d.", + i * 2, load_avg / 100, load_avg % 100); + } +} + +static void +load_thread (void *seq_no_) +{ + int seq_no = (int) seq_no_; + int sleep_time = TIMER_FREQ * (10 + seq_no); + int spin_time = sleep_time + TIMER_FREQ * THREAD_CNT; + int exit_time = TIMER_FREQ * (THREAD_CNT * 2); + + timer_sleep (sleep_time - timer_elapsed (start_time)); + while (timer_elapsed (start_time) < spin_time) + continue; + timer_sleep (exit_time - timer_elapsed (start_time)); +} diff --git a/tests/threads/mlfqs-load-avg.ck b/tests/threads/mlfqs-load-avg.ck new file mode 100644 index 0000000..2254d05 --- /dev/null +++ b/tests/threads/mlfqs-load-avg.ck @@ -0,0 +1,36 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./ + or next; + $actual[$t] = $load_avg; +} + +# Calculate expected values. +my ($load_avg) = 0; +my ($recent) = 0; +my (@expected); +for (my ($t) = 0; $t < 180; $t++) { + my ($ready) = $t < 60 ? $t : $t < 120 ? 120 - $t : 0; + $load_avg = (59/60) * $load_avg + (1/60) * $ready; + $expected[$t] = $load_avg; +} + +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2], + "Some load average values were missing or " + . "differed from those expected " + . "by more than 2.5."); +pass; diff --git a/tests/threads/mlfqs-nice-10.ck b/tests/threads/mlfqs-nice-10.ck new file mode 100644 index 0000000..53e0abe --- /dev/null +++ b/tests/threads/mlfqs-nice-10.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0...9], 25); diff --git a/tests/threads/mlfqs-nice-2.ck b/tests/threads/mlfqs-nice-2.ck new file mode 100644 index 0000000..ada366b --- /dev/null +++ b/tests/threads/mlfqs-nice-2.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0, 5], 50); diff --git a/tests/threads/mlfqs-recent-1.c b/tests/threads/mlfqs-recent-1.c new file mode 100644 index 0000000..4258671 --- /dev/null +++ b/tests/threads/mlfqs-recent-1.c @@ -0,0 +1,144 @@ +/* Checks that recent_cpu is calculated properly for the case of + a single ready process. + + The expected output is this (some margin of error is allowed): + + After 2 seconds, recent_cpu is 6.40, load_avg is 0.03. + After 4 seconds, recent_cpu is 12.60, load_avg is 0.07. + After 6 seconds, recent_cpu is 18.61, load_avg is 0.10. + After 8 seconds, recent_cpu is 24.44, load_avg is 0.13. + After 10 seconds, recent_cpu is 30.08, load_avg is 0.15. + After 12 seconds, recent_cpu is 35.54, load_avg is 0.18. + After 14 seconds, recent_cpu is 40.83, load_avg is 0.21. + After 16 seconds, recent_cpu is 45.96, load_avg is 0.24. + After 18 seconds, recent_cpu is 50.92, load_avg is 0.26. + After 20 seconds, recent_cpu is 55.73, load_avg is 0.29. + After 22 seconds, recent_cpu is 60.39, load_avg is 0.31. + After 24 seconds, recent_cpu is 64.90, load_avg is 0.33. + After 26 seconds, recent_cpu is 69.27, load_avg is 0.35. + After 28 seconds, recent_cpu is 73.50, load_avg is 0.38. + After 30 seconds, recent_cpu is 77.60, load_avg is 0.40. + After 32 seconds, recent_cpu is 81.56, load_avg is 0.42. + After 34 seconds, recent_cpu is 85.40, load_avg is 0.44. + After 36 seconds, recent_cpu is 89.12, load_avg is 0.45. + After 38 seconds, recent_cpu is 92.72, load_avg is 0.47. + After 40 seconds, recent_cpu is 96.20, load_avg is 0.49. + After 42 seconds, recent_cpu is 99.57, load_avg is 0.51. + After 44 seconds, recent_cpu is 102.84, load_avg is 0.52. + After 46 seconds, recent_cpu is 106.00, load_avg is 0.54. + After 48 seconds, recent_cpu is 109.06, load_avg is 0.55. + After 50 seconds, recent_cpu is 112.02, load_avg is 0.57. + After 52 seconds, recent_cpu is 114.89, load_avg is 0.58. + After 54 seconds, recent_cpu is 117.66, load_avg is 0.60. + After 56 seconds, recent_cpu is 120.34, load_avg is 0.61. + After 58 seconds, recent_cpu is 122.94, load_avg is 0.62. + After 60 seconds, recent_cpu is 125.46, load_avg is 0.64. + After 62 seconds, recent_cpu is 127.89, load_avg is 0.65. + After 64 seconds, recent_cpu is 130.25, load_avg is 0.66. + After 66 seconds, recent_cpu is 132.53, load_avg is 0.67. + After 68 seconds, recent_cpu is 134.73, load_avg is 0.68. + After 70 seconds, recent_cpu is 136.86, load_avg is 0.69. + After 72 seconds, recent_cpu is 138.93, load_avg is 0.70. + After 74 seconds, recent_cpu is 140.93, load_avg is 0.71. + After 76 seconds, recent_cpu is 142.86, load_avg is 0.72. + After 78 seconds, recent_cpu is 144.73, load_avg is 0.73. + After 80 seconds, recent_cpu is 146.54, load_avg is 0.74. + After 82 seconds, recent_cpu is 148.29, load_avg is 0.75. + After 84 seconds, recent_cpu is 149.99, load_avg is 0.76. + After 86 seconds, recent_cpu is 151.63, load_avg is 0.76. + After 88 seconds, recent_cpu is 153.21, load_avg is 0.77. + After 90 seconds, recent_cpu is 154.75, load_avg is 0.78. + After 92 seconds, recent_cpu is 156.23, load_avg is 0.79. + After 94 seconds, recent_cpu is 157.67, load_avg is 0.79. + After 96 seconds, recent_cpu is 159.06, load_avg is 0.80. + After 98 seconds, recent_cpu is 160.40, load_avg is 0.81. + After 100 seconds, recent_cpu is 161.70, load_avg is 0.81. + After 102 seconds, recent_cpu is 162.96, load_avg is 0.82. + After 104 seconds, recent_cpu is 164.18, load_avg is 0.83. + After 106 seconds, recent_cpu is 165.35, load_avg is 0.83. + After 108 seconds, recent_cpu is 166.49, load_avg is 0.84. + After 110 seconds, recent_cpu is 167.59, load_avg is 0.84. + After 112 seconds, recent_cpu is 168.66, load_avg is 0.85. + After 114 seconds, recent_cpu is 169.69, load_avg is 0.85. + After 116 seconds, recent_cpu is 170.69, load_avg is 0.86. + After 118 seconds, recent_cpu is 171.65, load_avg is 0.86. + After 120 seconds, recent_cpu is 172.58, load_avg is 0.87. + After 122 seconds, recent_cpu is 173.49, load_avg is 0.87. + After 124 seconds, recent_cpu is 174.36, load_avg is 0.88. + After 126 seconds, recent_cpu is 175.20, load_avg is 0.88. + After 128 seconds, recent_cpu is 176.02, load_avg is 0.88. + After 130 seconds, recent_cpu is 176.81, load_avg is 0.89. + After 132 seconds, recent_cpu is 177.57, load_avg is 0.89. + After 134 seconds, recent_cpu is 178.31, load_avg is 0.89. + After 136 seconds, recent_cpu is 179.02, load_avg is 0.90. + After 138 seconds, recent_cpu is 179.72, load_avg is 0.90. + After 140 seconds, recent_cpu is 180.38, load_avg is 0.90. + After 142 seconds, recent_cpu is 181.03, load_avg is 0.91. + After 144 seconds, recent_cpu is 181.65, load_avg is 0.91. + After 146 seconds, recent_cpu is 182.26, load_avg is 0.91. + After 148 seconds, recent_cpu is 182.84, load_avg is 0.92. + After 150 seconds, recent_cpu is 183.41, load_avg is 0.92. + After 152 seconds, recent_cpu is 183.96, load_avg is 0.92. + After 154 seconds, recent_cpu is 184.49, load_avg is 0.92. + After 156 seconds, recent_cpu is 185.00, load_avg is 0.93. + After 158 seconds, recent_cpu is 185.49, load_avg is 0.93. + After 160 seconds, recent_cpu is 185.97, load_avg is 0.93. + After 162 seconds, recent_cpu is 186.43, load_avg is 0.93. + After 164 seconds, recent_cpu is 186.88, load_avg is 0.94. + After 166 seconds, recent_cpu is 187.31, load_avg is 0.94. + After 168 seconds, recent_cpu is 187.73, load_avg is 0.94. + After 170 seconds, recent_cpu is 188.14, load_avg is 0.94. + After 172 seconds, recent_cpu is 188.53, load_avg is 0.94. + After 174 seconds, recent_cpu is 188.91, load_avg is 0.95. + After 176 seconds, recent_cpu is 189.27, load_avg is 0.95. + After 178 seconds, recent_cpu is 189.63, load_avg is 0.95. + After 180 seconds, recent_cpu is 189.97, load_avg is 0.95. +*/ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +/* Sensitive to assumption that recent_cpu updates happen exactly + when timer_ticks() % TIMER_FREQ == 0. */ + +void +test_mlfqs_recent_1 (void) +{ + int64_t start_time; + int last_elapsed = 0; + + ASSERT (thread_mlfqs); + + do + { + msg ("Sleeping 10 seconds to allow recent_cpu to decay, please wait..."); + start_time = timer_ticks (); + timer_sleep (DIV_ROUND_UP (start_time, TIMER_FREQ) - start_time + + 10 * TIMER_FREQ); + } + while (thread_get_recent_cpu () > 700); + + start_time = timer_ticks (); + for (;;) + { + int elapsed = timer_elapsed (start_time); + if (elapsed % (TIMER_FREQ * 2) == 0 && elapsed > last_elapsed) + { + int recent_cpu = thread_get_recent_cpu (); + int load_avg = thread_get_load_avg (); + int elapsed_seconds = elapsed / TIMER_FREQ; + msg ("After %d seconds, recent_cpu is %d.%02d, load_avg is %d.%02d.", + elapsed_seconds, + recent_cpu / 100, recent_cpu % 100, + load_avg / 100, load_avg % 100); + if (elapsed_seconds >= 180) + break; + } + last_elapsed = elapsed; + } +} diff --git a/tests/threads/mlfqs-recent-1.ck b/tests/threads/mlfqs-recent-1.ck new file mode 100644 index 0000000..a2ba44d --- /dev/null +++ b/tests/threads/mlfqs-recent-1.ck @@ -0,0 +1,31 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); +my (@output) = read_text_file ("$test.output"); +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $recent_cpu) = /After (\d+) seconds, recent_cpu is (\d+\.\d+),/ + or next; + $actual[$t] = $recent_cpu; +} + +# Calculate expected values. +my ($expected_load_avg, $expected_recent_cpu) + = mlfqs_expected_load ([(1) x 180], [(100) x 180]); +my (@expected) = @$expected_recent_cpu; + +# Compare actual and expected values. +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2], + "Some recent_cpu values were missing or " + . "differed from those expected " + . "by more than 2.5."); +pass; diff --git a/tests/threads/mlfqs.pm b/tests/threads/mlfqs.pm new file mode 100644 index 0000000..184ac16 --- /dev/null +++ b/tests/threads/mlfqs.pm @@ -0,0 +1,146 @@ +# -*- perl -*- +use strict; +use warnings; + +sub mlfqs_expected_load { + my ($ready, $recent_delta) = @_; + my (@load_avg) = 0; + my (@recent_cpu) = 0; + my ($load_avg) = 0; + my ($recent_cpu) = 0; + for my $i (0...$#$ready) { + $load_avg = (59/60) * $load_avg + (1/60) * $ready->[$i]; + push (@load_avg, $load_avg); + + if (defined $recent_delta->[$i]) { + my ($twice_load) = $load_avg * 2; + my ($load_factor) = $twice_load / ($twice_load + 1); + $recent_cpu = ($recent_cpu + $recent_delta->[$i]) * $load_factor; + push (@recent_cpu, $recent_cpu); + } + } + return (\@load_avg, \@recent_cpu); +} + +sub mlfqs_expected_ticks { + my (@nice) = @_; + my ($thread_cnt) = scalar (@nice); + my (@recent_cpu) = (0) x $thread_cnt; + my (@slices) = (0) x $thread_cnt; + my (@fifo) = (0) x $thread_cnt; + my ($next_fifo) = 1; + my ($load_avg) = 0; + for my $i (1...750) { + if ($i % 25 == 0) { + # Update load average. + $load_avg = (59/60) * $load_avg + (1/60) * $thread_cnt; + + # Update recent_cpu. + my ($twice_load) = $load_avg * 2; + my ($load_factor) = $twice_load / ($twice_load + 1); + $recent_cpu[$_] = $recent_cpu[$_] * $load_factor + $nice[$_] + foreach 0...($thread_cnt - 1); + } + + # Update priorities. + my (@priority); + foreach my $j (0...($thread_cnt - 1)) { + my ($priority) = int ($recent_cpu[$j] / 4 + $nice[$j] * 2); + $priority = 0 if $priority < 0; + $priority = 63 if $priority > 63; + push (@priority, $priority); + } + + # Choose thread to run. + my $max = 0; + for my $j (1...$#priority) { + if ($priority[$j] < $priority[$max] + || ($priority[$j] == $priority[$max] + && $fifo[$j] < $fifo[$max])) { + $max = $j; + } + } + $fifo[$max] = $next_fifo++; + + # Run thread. + $recent_cpu[$max] += 4; + $slices[$max] += 4; + } + return @slices; +} + +sub check_mlfqs_fair { + my ($nice, $maxdiff) = @_; + our ($test); + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + @output = get_core_output ("run", @output); + + my (@actual); + local ($_); + foreach (@output) { + my ($id, $count) = /Thread (\d+) received (\d+) ticks\./ or next; + $actual[$id] = $count; + } + + my (@expected) = mlfqs_expected_ticks (@$nice); + mlfqs_compare ("thread", "%d", + \@actual, \@expected, $maxdiff, [0, $#$nice, 1], + "Some tick counts were missing or differed from those " + . "expected by more than $maxdiff."); + pass; +} + +sub mlfqs_compare { + my ($indep_var, $format, + $actual_ref, $expected_ref, $maxdiff, $t_range, $message) = @_; + my ($t_min, $t_max, $t_step) = @$t_range; + + my ($ok) = 1; + for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) { + my ($actual) = $actual_ref->[$t]; + my ($expected) = $expected_ref->[$t]; + $ok = 0, last + if !defined ($actual) || abs ($actual - $expected) > $maxdiff + .01; + } + return if $ok; + + print "$message\n"; + mlfqs_row ($indep_var, "actual", "<->", "expected", "explanation"); + mlfqs_row ("------", "--------", "---", "--------", '-' x 40); + for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) { + my ($actual) = $actual_ref->[$t]; + my ($expected) = $expected_ref->[$t]; + my ($diff, $rationale); + if (!defined $actual) { + $actual = 'undef' ; + $diff = ''; + $rationale = 'Missing value.'; + } else { + my ($delta) = abs ($actual - $expected); + if ($delta > $maxdiff + .01) { + my ($excess) = $delta - $maxdiff; + if ($actual > $expected) { + $diff = '>>>'; + $rationale = sprintf "Too big, by $format.", $excess; + } else { + $diff = '<<<'; + $rationale = sprintf "Too small, by $format.", $excess; + } + } else { + $diff = ' = '; + $rationale = ''; + } + $actual = sprintf ($format, $actual); + } + $expected = sprintf ($format, $expected); + mlfqs_row ($t, $actual, $diff, $expected, $rationale); + } + fail; +} + +sub mlfqs_row { + printf "%6s %8s %3s %-8s %s\n", @_; +} + +1; diff --git a/tests/threads/priority-change.c b/tests/threads/priority-change.c new file mode 100644 index 0000000..810b05a --- /dev/null +++ b/tests/threads/priority-change.c @@ -0,0 +1,31 @@ +/* Verifies that lowering a thread's priority so that it is no + longer the highest-priority thread in the system causes it to + yield immediately. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/thread.h" + +static thread_func changing_thread; + +void +test_priority_change (void) +{ + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating a high-priority thread 2."); + thread_create ("thread 2", PRI_DEFAULT + 1, changing_thread, NULL); + msg ("Thread 2 should have just lowered its priority."); + thread_set_priority (PRI_DEFAULT - 2); + msg ("Thread 2 should have just exited."); +} + +static void +changing_thread (void *aux UNUSED) +{ + msg ("Thread 2 now lowering priority."); + thread_set_priority (PRI_DEFAULT - 1); + msg ("Thread 2 exiting."); +} diff --git a/tests/threads/priority-change.ck b/tests/threads/priority-change.ck new file mode 100644 index 0000000..f4d9b2f --- /dev/null +++ b/tests/threads/priority-change.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-change) begin +(priority-change) Creating a high-priority thread 2. +(priority-change) Thread 2 now lowering priority. +(priority-change) Thread 2 should have just lowered its priority. +(priority-change) Thread 2 exiting. +(priority-change) Thread 2 should have just exited. +(priority-change) end +EOF +pass; diff --git a/tests/threads/priority-condvar.c b/tests/threads/priority-condvar.c new file mode 100644 index 0000000..c1efb1b --- /dev/null +++ b/tests/threads/priority-condvar.c @@ -0,0 +1,53 @@ +/* Tests that cond_signal() wakes up the highest-priority thread + waiting in cond_wait(). */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func priority_condvar_thread; +static struct lock lock; +static struct condition condition; + +void +test_priority_condvar (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + lock_init (&lock); + cond_init (&condition); + + thread_set_priority (PRI_MIN); + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 7) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, priority_condvar_thread, NULL); + } + + for (i = 0; i < 10; i++) + { + lock_acquire (&lock); + msg ("Signaling..."); + cond_signal (&condition, &lock); + lock_release (&lock); + } +} + +static void +priority_condvar_thread (void *aux UNUSED) +{ + msg ("Thread %s starting.", thread_name ()); + lock_acquire (&lock); + cond_wait (&condition, &lock); + msg ("Thread %s woke up.", thread_name ()); + lock_release (&lock); +} diff --git a/tests/threads/priority-condvar.ck b/tests/threads/priority-condvar.ck new file mode 100644 index 0000000..195c1ab --- /dev/null +++ b/tests/threads/priority-condvar.ck @@ -0,0 +1,39 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-condvar) begin +(priority-condvar) Thread priority 23 starting. +(priority-condvar) Thread priority 22 starting. +(priority-condvar) Thread priority 21 starting. +(priority-condvar) Thread priority 30 starting. +(priority-condvar) Thread priority 29 starting. +(priority-condvar) Thread priority 28 starting. +(priority-condvar) Thread priority 27 starting. +(priority-condvar) Thread priority 26 starting. +(priority-condvar) Thread priority 25 starting. +(priority-condvar) Thread priority 24 starting. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 30 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 29 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 28 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 27 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 26 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 25 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 24 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 23 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 22 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 21 woke up. +(priority-condvar) end +EOF +pass; diff --git a/tests/threads/priority-donate-chain.c b/tests/threads/priority-donate-chain.c new file mode 100644 index 0000000..3ffabca --- /dev/null +++ b/tests/threads/priority-donate-chain.c @@ -0,0 +1,114 @@ +/* The main thread set its priority to PRI_MIN and creates 7 threads + (thread 1..7) with priorities PRI_MIN + 3, 6, 9, 12, ... + The main thread initializes 8 locks: lock 0..7 and acquires lock 0. + + When thread[i] starts, it first acquires lock[i] (unless i == 7.) + Subsequently, thread[i] attempts to acquire lock[i-1], which is held by + thread[i-1], except for lock[0], which is held by the main thread. + Because the lock is held, thread[i] donates its priority to thread[i-1], + which donates to thread[i-2], and so on until the main thread + receives the donation. + + After threads[1..7] have been created and are blocked on locks[0..7], + the main thread releases lock[0], unblocking thread[1], and being + preempted by it. + Thread[1] then completes acquiring lock[0], then releases lock[0], + then releases lock[1], unblocking thread[2], etc. + Thread[7] finally acquires & releases lock[7] and exits, allowing + thread[6], then thread[5] etc. to run and exit until finally the + main thread exits. + + In addition, interloper threads are created at priority levels + p = PRI_MIN + 2, 5, 8, 11, ... which should not be run until the + corresponding thread with priority p + 1 has finished. + + Written by Godmar Back */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +#define NESTING_DEPTH 8 + +struct lock_pair + { + struct lock *second; + struct lock *first; + }; + +static thread_func donor_thread_func; +static thread_func interloper_thread_func; + +void +test_priority_donate_chain (void) +{ + int i; + struct lock locks[NESTING_DEPTH - 1]; + struct lock_pair lock_pairs[NESTING_DEPTH]; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + thread_set_priority (PRI_MIN); + + for (i = 0; i < NESTING_DEPTH - 1; i++) + lock_init (&locks[i]); + + lock_acquire (&locks[0]); + msg ("%s got lock.", thread_name ()); + + for (i = 1; i < NESTING_DEPTH; i++) + { + char name[16]; + int thread_priority; + + snprintf (name, sizeof name, "thread %d", i); + thread_priority = PRI_MIN + i * 3; + lock_pairs[i].first = i < NESTING_DEPTH - 1 ? locks + i: NULL; + lock_pairs[i].second = locks + i - 1; + + thread_create (name, thread_priority, donor_thread_func, lock_pairs + i); + msg ("%s should have priority %d. Actual priority: %d.", + thread_name (), thread_priority, thread_get_priority ()); + + snprintf (name, sizeof name, "interloper %d", i); + thread_create (name, thread_priority - 1, interloper_thread_func, NULL); + } + + lock_release (&locks[0]); + msg ("%s finishing with priority %d.", thread_name (), + thread_get_priority ()); +} + +static void +donor_thread_func (void *locks_) +{ + struct lock_pair *locks = locks_; + + if (locks->first) + lock_acquire (locks->first); + + lock_acquire (locks->second); + msg ("%s got lock", thread_name ()); + + lock_release (locks->second); + msg ("%s should have priority %d. Actual priority: %d", + thread_name (), (NESTING_DEPTH - 1) * 3, + thread_get_priority ()); + + if (locks->first) + lock_release (locks->first); + + msg ("%s finishing with priority %d.", thread_name (), + thread_get_priority ()); +} + +static void +interloper_thread_func (void *arg_ UNUSED) +{ + msg ("%s finished.", thread_name ()); +} + +// vim: sw=2 diff --git a/tests/threads/priority-donate-chain.ck b/tests/threads/priority-donate-chain.ck new file mode 100644 index 0000000..213e649 --- /dev/null +++ b/tests/threads/priority-donate-chain.ck @@ -0,0 +1,46 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-chain) begin +(priority-donate-chain) main got lock. +(priority-donate-chain) main should have priority 3. Actual priority: 3. +(priority-donate-chain) main should have priority 6. Actual priority: 6. +(priority-donate-chain) main should have priority 9. Actual priority: 9. +(priority-donate-chain) main should have priority 12. Actual priority: 12. +(priority-donate-chain) main should have priority 15. Actual priority: 15. +(priority-donate-chain) main should have priority 18. Actual priority: 18. +(priority-donate-chain) main should have priority 21. Actual priority: 21. +(priority-donate-chain) thread 1 got lock +(priority-donate-chain) thread 1 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 2 got lock +(priority-donate-chain) thread 2 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 3 got lock +(priority-donate-chain) thread 3 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 4 got lock +(priority-donate-chain) thread 4 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 5 got lock +(priority-donate-chain) thread 5 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 6 got lock +(priority-donate-chain) thread 6 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 7 got lock +(priority-donate-chain) thread 7 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 7 finishing with priority 21. +(priority-donate-chain) interloper 7 finished. +(priority-donate-chain) thread 6 finishing with priority 18. +(priority-donate-chain) interloper 6 finished. +(priority-donate-chain) thread 5 finishing with priority 15. +(priority-donate-chain) interloper 5 finished. +(priority-donate-chain) thread 4 finishing with priority 12. +(priority-donate-chain) interloper 4 finished. +(priority-donate-chain) thread 3 finishing with priority 9. +(priority-donate-chain) interloper 3 finished. +(priority-donate-chain) thread 2 finishing with priority 6. +(priority-donate-chain) interloper 2 finished. +(priority-donate-chain) thread 1 finishing with priority 3. +(priority-donate-chain) interloper 1 finished. +(priority-donate-chain) main finishing with priority 0. +(priority-donate-chain) end +EOF +pass; diff --git a/tests/threads/priority-donate-lower.c b/tests/threads/priority-donate-lower.c new file mode 100644 index 0000000..4965d75 --- /dev/null +++ b/tests/threads/priority-donate-lower.c @@ -0,0 +1,51 @@ +/* The main thread acquires a lock. Then it creates a + higher-priority thread that blocks acquiring the lock, causing + it to donate their priorities to the main thread. The main + thread attempts to lower its priority, which should not take + effect until the donation is released. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func acquire_thread_func; + +void +test_priority_donate_lower (void) +{ + struct lock lock; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&lock); + lock_acquire (&lock); + thread_create ("acquire", PRI_DEFAULT + 10, acquire_thread_func, &lock); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 10, thread_get_priority ()); + + msg ("Lowering base priority..."); + thread_set_priority (PRI_DEFAULT - 10); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 10, thread_get_priority ()); + lock_release (&lock); + msg ("acquire must already have finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT - 10, thread_get_priority ()); +} + +static void +acquire_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire: got the lock"); + lock_release (lock); + msg ("acquire: done"); +} diff --git a/tests/threads/priority-donate-lower.ck b/tests/threads/priority-donate-lower.ck new file mode 100644 index 0000000..c9bb61b --- /dev/null +++ b/tests/threads/priority-donate-lower.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-lower) begin +(priority-donate-lower) Main thread should have priority 41. Actual priority: 41. +(priority-donate-lower) Lowering base priority... +(priority-donate-lower) Main thread should have priority 41. Actual priority: 41. +(priority-donate-lower) acquire: got the lock +(priority-donate-lower) acquire: done +(priority-donate-lower) acquire must already have finished. +(priority-donate-lower) Main thread should have priority 21. Actual priority: 21. +(priority-donate-lower) end +EOF +pass; diff --git a/tests/threads/priority-donate-multiple.c b/tests/threads/priority-donate-multiple.c new file mode 100644 index 0000000..df4689c --- /dev/null +++ b/tests/threads/priority-donate-multiple.c @@ -0,0 +1,77 @@ +/* The main thread acquires locks A and B, then it creates two + higher-priority threads. Each of these threads blocks + acquiring one of the locks and thus donate their priority to + the main thread. The main thread releases the locks in turn + and relinquishes its donated priorities. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func a_thread_func; +static thread_func b_thread_func; + +void +test_priority_donate_multiple (void) +{ + struct lock a, b; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + lock_acquire (&b); + + thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + + lock_release (&b); + msg ("Thread b should have just finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + lock_release (&a); + msg ("Thread a should have just finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +a_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread a acquired lock a."); + lock_release (lock); + msg ("Thread a finished."); +} + +static void +b_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread b acquired lock b."); + lock_release (lock); + msg ("Thread b finished."); +} diff --git a/tests/threads/priority-donate-multiple.ck b/tests/threads/priority-donate-multiple.ck new file mode 100644 index 0000000..0afd20b --- /dev/null +++ b/tests/threads/priority-donate-multiple.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple) begin +(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32. +(priority-donate-multiple) Main thread should have priority 33. Actual priority: 33. +(priority-donate-multiple) Thread b acquired lock b. +(priority-donate-multiple) Thread b finished. +(priority-donate-multiple) Thread b should have just finished. +(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32. +(priority-donate-multiple) Thread a acquired lock a. +(priority-donate-multiple) Thread a finished. +(priority-donate-multiple) Thread a should have just finished. +(priority-donate-multiple) Main thread should have priority 31. Actual priority: 31. +(priority-donate-multiple) end +EOF +pass; diff --git a/tests/threads/priority-donate-multiple2.c b/tests/threads/priority-donate-multiple2.c new file mode 100644 index 0000000..7f65fef --- /dev/null +++ b/tests/threads/priority-donate-multiple2.c @@ -0,0 +1,90 @@ +/* The main thread acquires locks A and B, then it creates three + higher-priority threads. The first two of these threads block + acquiring one of the locks and thus donate their priority to + the main thread. The main thread releases the locks in turn + and relinquishes its donated priorities, allowing the third thread + to run. + + In this test, the main thread releases the locks in a different + order compared to priority-donate-multiple.c. + + Written by Godmar Back . + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func a_thread_func; +static thread_func b_thread_func; +static thread_func c_thread_func; + +void +test_priority_donate_multiple2 (void) +{ + struct lock a, b; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + lock_acquire (&b); + + thread_create ("a", PRI_DEFAULT + 3, a_thread_func, &a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 3, thread_get_priority ()); + + thread_create ("c", PRI_DEFAULT + 1, c_thread_func, NULL); + + thread_create ("b", PRI_DEFAULT + 5, b_thread_func, &b); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 5, thread_get_priority ()); + + lock_release (&a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 5, thread_get_priority ()); + + lock_release (&b); + msg ("Threads b, a, c should have just finished, in that order."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +a_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread a acquired lock a."); + lock_release (lock); + msg ("Thread a finished."); +} + +static void +b_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread b acquired lock b."); + lock_release (lock); + msg ("Thread b finished."); +} + +static void +c_thread_func (void *a_ UNUSED) +{ + msg ("Thread c finished."); +} diff --git a/tests/threads/priority-donate-multiple2.ck b/tests/threads/priority-donate-multiple2.ck new file mode 100644 index 0000000..b23533a --- /dev/null +++ b/tests/threads/priority-donate-multiple2.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple2) begin +(priority-donate-multiple2) Main thread should have priority 34. Actual priority: 34. +(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36. +(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36. +(priority-donate-multiple2) Thread b acquired lock b. +(priority-donate-multiple2) Thread b finished. +(priority-donate-multiple2) Thread a acquired lock a. +(priority-donate-multiple2) Thread a finished. +(priority-donate-multiple2) Thread c finished. +(priority-donate-multiple2) Threads b, a, c should have just finished, in that order. +(priority-donate-multiple2) Main thread should have priority 31. Actual priority: 31. +(priority-donate-multiple2) end +EOF +pass; diff --git a/tests/threads/priority-donate-nest.c b/tests/threads/priority-donate-nest.c new file mode 100644 index 0000000..3a3a9a5 --- /dev/null +++ b/tests/threads/priority-donate-nest.c @@ -0,0 +1,94 @@ +/* Low-priority main thread L acquires lock A. Medium-priority + thread M then acquires lock B then blocks on acquiring lock A. + High-priority thread H then blocks on acquiring lock B. Thus, + thread H donates its priority to M, which in turn donates it + to thread L. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct locks + { + struct lock *a; + struct lock *b; + }; + +static thread_func medium_thread_func; +static thread_func high_thread_func; + +void +test_priority_donate_nest (void) +{ + struct lock a, b; + struct locks locks; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + + locks.a = &a; + locks.b = &b; + thread_create ("medium", PRI_DEFAULT + 1, medium_thread_func, &locks); + thread_yield (); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + thread_create ("high", PRI_DEFAULT + 2, high_thread_func, &b); + thread_yield (); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + + lock_release (&a); + thread_yield (); + msg ("Medium thread should just have finished."); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +medium_thread_func (void *locks_) +{ + struct locks *locks = locks_; + + lock_acquire (locks->b); + lock_acquire (locks->a); + + msg ("Medium thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + msg ("Medium thread got the lock."); + + lock_release (locks->a); + thread_yield (); + + lock_release (locks->b); + thread_yield (); + + msg ("High thread should have just finished."); + msg ("Middle thread finished."); +} + +static void +high_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("High thread got the lock."); + lock_release (lock); + msg ("High thread finished."); +} diff --git a/tests/threads/priority-donate-nest.ck b/tests/threads/priority-donate-nest.ck new file mode 100644 index 0000000..923460e --- /dev/null +++ b/tests/threads/priority-donate-nest.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-nest) begin +(priority-donate-nest) Low thread should have priority 32. Actual priority: 32. +(priority-donate-nest) Low thread should have priority 33. Actual priority: 33. +(priority-donate-nest) Medium thread should have priority 33. Actual priority: 33. +(priority-donate-nest) Medium thread got the lock. +(priority-donate-nest) High thread got the lock. +(priority-donate-nest) High thread finished. +(priority-donate-nest) High thread should have just finished. +(priority-donate-nest) Middle thread finished. +(priority-donate-nest) Medium thread should just have finished. +(priority-donate-nest) Low thread should have priority 31. Actual priority: 31. +(priority-donate-nest) end +EOF +pass; diff --git a/tests/threads/priority-donate-one.c b/tests/threads/priority-donate-one.c new file mode 100644 index 0000000..3189f3a --- /dev/null +++ b/tests/threads/priority-donate-one.c @@ -0,0 +1,65 @@ +/* The main thread acquires a lock. Then it creates two + higher-priority threads that block acquiring the lock, causing + them to donate their priorities to the main thread. When the + main thread releases the lock, the other threads should + acquire it in priority order. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin , + Greg Hutchins , Yu Ping Hu + . Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func acquire1_thread_func; +static thread_func acquire2_thread_func; + +void +test_priority_donate_one (void) +{ + struct lock lock; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&lock); + lock_acquire (&lock); + thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock); + msg ("This thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock); + msg ("This thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + lock_release (&lock); + msg ("acquire2, acquire1 must already have finished, in that order."); + msg ("This should be the last line before finishing this test."); +} + +static void +acquire1_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire1: got the lock"); + lock_release (lock); + msg ("acquire1: done"); +} + +static void +acquire2_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire2: got the lock"); + lock_release (lock); + msg ("acquire2: done"); +} diff --git a/tests/threads/priority-donate-one.ck b/tests/threads/priority-donate-one.ck new file mode 100644 index 0000000..b7c8e6f --- /dev/null +++ b/tests/threads/priority-donate-one.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-one) begin +(priority-donate-one) This thread should have priority 32. Actual priority: 32. +(priority-donate-one) This thread should have priority 33. Actual priority: 33. +(priority-donate-one) acquire2: got the lock +(priority-donate-one) acquire2: done +(priority-donate-one) acquire1: got the lock +(priority-donate-one) acquire1: done +(priority-donate-one) acquire2, acquire1 must already have finished, in that order. +(priority-donate-one) This should be the last line before finishing this test. +(priority-donate-one) end +EOF +pass; diff --git a/tests/threads/priority-donate-sema.c b/tests/threads/priority-donate-sema.c new file mode 100644 index 0000000..b33cb72 --- /dev/null +++ b/tests/threads/priority-donate-sema.c @@ -0,0 +1,82 @@ +/* Low priority thread L acquires a lock, then blocks downing a + semaphore. Medium priority thread M then blocks waiting on + the same semaphore. Next, high priority thread H attempts to + acquire the lock, donating its priority to L. + + Next, the main thread ups the semaphore, waking up L. L + releases the lock, which wakes up H. H "up"s the semaphore, + waking up M. H terminates, then M, then L, and finally the + main thread. + + Written by Godmar Back . */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct lock_and_sema + { + struct lock lock; + struct semaphore sema; + }; + +static thread_func l_thread_func; +static thread_func m_thread_func; +static thread_func h_thread_func; + +void +test_priority_donate_sema (void) +{ + struct lock_and_sema ls; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&ls.lock); + sema_init (&ls.sema, 0); + thread_create ("low", PRI_DEFAULT + 1, l_thread_func, &ls); + thread_create ("med", PRI_DEFAULT + 3, m_thread_func, &ls); + thread_create ("high", PRI_DEFAULT + 5, h_thread_func, &ls); + sema_up (&ls.sema); + msg ("Main thread finished."); +} + +static void +l_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + lock_acquire (&ls->lock); + msg ("Thread L acquired lock."); + sema_down (&ls->sema); + msg ("Thread L downed semaphore."); + lock_release (&ls->lock); + msg ("Thread L finished."); +} + +static void +m_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + sema_down (&ls->sema); + msg ("Thread M finished."); +} + +static void +h_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + lock_acquire (&ls->lock); + msg ("Thread H acquired lock."); + + sema_up (&ls->sema); + lock_release (&ls->lock); + msg ("Thread H finished."); +} diff --git a/tests/threads/priority-donate-sema.ck b/tests/threads/priority-donate-sema.ck new file mode 100644 index 0000000..92b8d07 --- /dev/null +++ b/tests/threads/priority-donate-sema.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-sema) begin +(priority-donate-sema) Thread L acquired lock. +(priority-donate-sema) Thread L downed semaphore. +(priority-donate-sema) Thread H acquired lock. +(priority-donate-sema) Thread H finished. +(priority-donate-sema) Thread M finished. +(priority-donate-sema) Thread L finished. +(priority-donate-sema) Main thread finished. +(priority-donate-sema) end +EOF +pass; diff --git a/tests/threads/priority-fifo.c b/tests/threads/priority-fifo.c new file mode 100644 index 0000000..3af98a3 --- /dev/null +++ b/tests/threads/priority-fifo.c @@ -0,0 +1,99 @@ +/* Creates several threads all at the same priority and ensures + that they consistently run in the same round-robin order. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by by Matt Franklin + , Greg Hutchins + , Yu Ping Hu . + Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "devices/timer.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct simple_thread_data + { + int id; /* Sleeper ID. */ + int iterations; /* Iterations so far. */ + struct lock *lock; /* Lock on output. */ + int **op; /* Output buffer position. */ + }; + +#define THREAD_CNT 16 +#define ITER_CNT 16 + +static thread_func simple_thread_func; + +void +test_priority_fifo (void) +{ + struct simple_thread_data data[THREAD_CNT]; + struct lock lock; + int *output, *op; + int i, cnt; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + msg ("%d threads will iterate %d times in the same order each time.", + THREAD_CNT, ITER_CNT); + msg ("If the order varies then there is a bug."); + + output = op = malloc (sizeof *output * THREAD_CNT * ITER_CNT * 2); + ASSERT (output != NULL); + lock_init (&lock); + + thread_set_priority (PRI_DEFAULT + 2); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + struct simple_thread_data *d = data + i; + snprintf (name, sizeof name, "%d", i); + d->id = i; + d->iterations = 0; + d->lock = &lock; + d->op = &op; + thread_create (name, PRI_DEFAULT + 1, simple_thread_func, d); + } + + thread_set_priority (PRI_DEFAULT); + /* All the other threads now run to termination here. */ + ASSERT (lock.holder == NULL); + + cnt = 0; + for (; output < op; output++) + { + struct simple_thread_data *d; + + ASSERT (*output >= 0 && *output < THREAD_CNT); + d = data + *output; + if (cnt % THREAD_CNT == 0) + printf ("(priority-fifo) iteration:"); + printf (" %d", d->id); + if (++cnt % THREAD_CNT == 0) + printf ("\n"); + d->iterations++; + } +} + +static void +simple_thread_func (void *data_) +{ + struct simple_thread_data *data = data_; + int i; + + for (i = 0; i < ITER_CNT; i++) + { + lock_acquire (data->lock); + *(*data->op)++ = data->id; + lock_release (data->lock); + thread_yield (); + } +} diff --git a/tests/threads/priority-fifo.ck b/tests/threads/priority-fifo.ck new file mode 100644 index 0000000..11f1dd3 --- /dev/null +++ b/tests/threads/priority-fifo.ck @@ -0,0 +1,63 @@ +# -*- perl -*- + +# The expected output looks like this: +# +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# +# A different permutation of 0...15 is acceptable, but every line must +# be in the same order. + +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +my ($thread_cnt) = 16; +my ($iter_cnt) = 16; +my (@order); +my (@t) = (-1) x $thread_cnt; + +my (@iterations) = grep (/iteration:/, @output); +fail "No iterations found in output.\n" if !@iterations; + +my (@numbering) = $iterations[0] =~ /(\d+)/g; +fail "First iteration does not list exactly $thread_cnt threads.\n" + if @numbering != $thread_cnt; + +my (@sorted_numbering) = sort { $a <=> $b } @numbering; +for my $i (0...$#sorted_numbering) { + if ($sorted_numbering[$i] != $i) { + fail "First iteration does not list all threads " + . "0...$#sorted_numbering\n"; + } +} + +for my $i (1...$#iterations) { + if ($iterations[$i] ne $iterations[0]) { + fail "Iteration $i differs from iteration 0\n"; + } +} + +fail "$iter_cnt iterations expected but " . scalar (@iterations) . " found\n" + if $iter_cnt != @iterations; + +pass; diff --git a/tests/threads/priority-preempt.c b/tests/threads/priority-preempt.c new file mode 100644 index 0000000..3c3aacb --- /dev/null +++ b/tests/threads/priority-preempt.c @@ -0,0 +1,41 @@ +/* Ensures that a high-priority thread really preempts. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by by Matt Franklin + , Greg Hutchins + , Yu Ping Hu . + Modified by arens. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func simple_thread_func; + +void +test_priority_preempt (void) +{ + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL); + msg ("The high-priority thread should have already completed."); +} + +static void +simple_thread_func (void *aux UNUSED) +{ + int i; + + for (i = 0; i < 5; i++) + { + msg ("Thread %s iteration %d", thread_name (), i); + thread_yield (); + } + msg ("Thread %s done!", thread_name ()); +} diff --git a/tests/threads/priority-preempt.ck b/tests/threads/priority-preempt.ck new file mode 100644 index 0000000..43a26ee --- /dev/null +++ b/tests/threads/priority-preempt.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-preempt) begin +(priority-preempt) Thread high-priority iteration 0 +(priority-preempt) Thread high-priority iteration 1 +(priority-preempt) Thread high-priority iteration 2 +(priority-preempt) Thread high-priority iteration 3 +(priority-preempt) Thread high-priority iteration 4 +(priority-preempt) Thread high-priority done! +(priority-preempt) The high-priority thread should have already completed. +(priority-preempt) end +EOF +pass; diff --git a/tests/threads/priority-sema.c b/tests/threads/priority-sema.c new file mode 100644 index 0000000..2834a88 --- /dev/null +++ b/tests/threads/priority-sema.c @@ -0,0 +1,45 @@ +/* Tests that the highest-priority thread waiting on a semaphore + is the first to wake up. */ + +#include +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func priority_sema_thread; +static struct semaphore sema; + +void +test_priority_sema (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + sema_init (&sema, 0); + thread_set_priority (PRI_MIN); + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 3) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, priority_sema_thread, NULL); + } + + for (i = 0; i < 10; i++) + { + sema_up (&sema); + msg ("Back in main thread."); + } +} + +static void +priority_sema_thread (void *aux UNUSED) +{ + sema_down (&sema); + msg ("Thread %s woke up.", thread_name ()); +} diff --git a/tests/threads/priority-sema.ck b/tests/threads/priority-sema.ck new file mode 100644 index 0000000..559988d --- /dev/null +++ b/tests/threads/priority-sema.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-sema) begin +(priority-sema) Thread priority 30 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 29 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 28 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 27 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 26 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 25 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 24 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 23 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 22 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 21 woke up. +(priority-sema) Back in main thread. +(priority-sema) end +EOF +pass; diff --git a/tests/threads/tests.c b/tests/threads/tests.c new file mode 100644 index 0000000..af15aee --- /dev/null +++ b/tests/threads/tests.c @@ -0,0 +1,102 @@ +#include "tests/threads/tests.h" +#include +#include +#include + +struct test + { + const char *name; + test_func *function; + }; + +static const struct test tests[] = + { + {"alarm-single", test_alarm_single}, + {"alarm-multiple", test_alarm_multiple}, + {"alarm-simultaneous", test_alarm_simultaneous}, + {"alarm-priority", test_alarm_priority}, + {"alarm-zero", test_alarm_zero}, + {"alarm-negative", test_alarm_negative}, + {"priority-change", test_priority_change}, + {"priority-donate-one", test_priority_donate_one}, + {"priority-donate-multiple", test_priority_donate_multiple}, + {"priority-donate-multiple2", test_priority_donate_multiple2}, + {"priority-donate-nest", test_priority_donate_nest}, + {"priority-donate-sema", test_priority_donate_sema}, + {"priority-donate-lower", test_priority_donate_lower}, + {"priority-donate-chain", test_priority_donate_chain}, + {"priority-fifo", test_priority_fifo}, + {"priority-preempt", test_priority_preempt}, + {"priority-sema", test_priority_sema}, + {"priority-condvar", test_priority_condvar}, + {"mlfqs-load-1", test_mlfqs_load_1}, + {"mlfqs-load-60", test_mlfqs_load_60}, + {"mlfqs-load-avg", test_mlfqs_load_avg}, + {"mlfqs-recent-1", test_mlfqs_recent_1}, + {"mlfqs-fair-2", test_mlfqs_fair_2}, + {"mlfqs-fair-20", test_mlfqs_fair_20}, + {"mlfqs-nice-2", test_mlfqs_nice_2}, + {"mlfqs-nice-10", test_mlfqs_nice_10}, + {"mlfqs-block", test_mlfqs_block}, + }; + +static const char *test_name; + +/* Runs the test named NAME. */ +void +run_test (const char *name) +{ + const struct test *t; + + for (t = tests; t < tests + sizeof tests / sizeof *tests; t++) + if (!strcmp (name, t->name)) + { + test_name = name; + msg ("begin"); + t->function (); + msg ("end"); + return; + } + PANIC ("no test named \"%s\"", name); +} + +/* Prints FORMAT as if with printf(), + prefixing the output by the name of the test + and following it with a new-line character. */ +void +msg (const char *format, ...) +{ + va_list args; + + printf ("(%s) ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); +} + +/* Prints failure message FORMAT as if with printf(), + prefixing the output by the name of the test and FAIL: + and following it with a new-line character, + and then panics the kernel. */ +void +fail (const char *format, ...) +{ + va_list args; + + printf ("(%s) FAIL: ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); + + PANIC ("test failed"); +} + +/* Prints a message indicating the current test passed. */ +void +pass (void) +{ + printf ("(%s) PASS\n", test_name); +} + diff --git a/tests/threads/tests.h b/tests/threads/tests.h new file mode 100644 index 0000000..cd9d489 --- /dev/null +++ b/tests/threads/tests.h @@ -0,0 +1,41 @@ +#ifndef TESTS_THREADS_TESTS_H +#define TESTS_THREADS_TESTS_H + +void run_test (const char *); + +typedef void test_func (void); + +extern test_func test_alarm_single; +extern test_func test_alarm_multiple; +extern test_func test_alarm_simultaneous; +extern test_func test_alarm_priority; +extern test_func test_alarm_zero; +extern test_func test_alarm_negative; +extern test_func test_priority_change; +extern test_func test_priority_donate_one; +extern test_func test_priority_donate_multiple; +extern test_func test_priority_donate_multiple2; +extern test_func test_priority_donate_sema; +extern test_func test_priority_donate_nest; +extern test_func test_priority_donate_lower; +extern test_func test_priority_donate_chain; +extern test_func test_priority_fifo; +extern test_func test_priority_preempt; +extern test_func test_priority_sema; +extern test_func test_priority_condvar; +extern test_func test_mlfqs_load_1; +extern test_func test_mlfqs_load_60; +extern test_func test_mlfqs_load_avg; +extern test_func test_mlfqs_recent_1; +extern test_func test_mlfqs_fair_2; +extern test_func test_mlfqs_fair_20; +extern test_func test_mlfqs_nice_2; +extern test_func test_mlfqs_nice_10; +extern test_func test_mlfqs_block; + +void msg (const char *, ...); +void fail (const char *, ...); +void pass (void); + +#endif /* tests/threads/tests.h */ + diff --git a/threads/Make.vars b/threads/Make.vars new file mode 100644 index 0000000..1c90f59 --- /dev/null +++ b/threads/Make.vars @@ -0,0 +1,6 @@ +# -*- makefile -*- + +os.dsk: DEFINES = +KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS) +TEST_SUBDIRS = tests/threads +GRADING_FILE = $(SRCDIR)/tests/threads/Grading diff --git a/threads/Makefile b/threads/Makefile new file mode 100644 index 0000000..34c10aa --- /dev/null +++ b/threads/Makefile @@ -0,0 +1 @@ +include ../Makefile.kernel diff --git a/threads/init.c b/threads/init.c new file mode 100644 index 0000000..b4b7f5d --- /dev/null +++ b/threads/init.c @@ -0,0 +1,351 @@ +#include "threads/init.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "devices/kbd.h" +#include "devices/input.h" +#include "devices/serial.h" +#include "devices/timer.h" +#include "devices/vga.h" +#include "threads/interrupt.h" +#include "threads/io.h" +#include "threads/loader.h" +#include "threads/malloc.h" +#include "threads/mmu.h" +#include "threads/palloc.h" +#include "threads/pte.h" +#include "threads/thread.h" +#ifdef USERPROG +#include "userprog/process.h" +#include "userprog/exception.h" +#include "userprog/gdt.h" +#include "userprog/syscall.h" +#include "userprog/tss.h" +#else +#include "tests/threads/tests.h" +#endif +#ifdef FILESYS +#include "devices/disk.h" +#include "filesys/filesys.h" +#include "filesys/fsutil.h" +#endif + +/* Page-map-level-4 with kernel mappings only. */ +uint64_t *base_pml4; + +#ifdef FILESYS +/* -f: Format the file system? */ +static bool format_filesys; +#endif + +/* -q: Power off after kernel tasks complete? */ +bool power_off_when_done; + +static void bss_init (void); +static void paging_init (uint64_t mem_end); + +static char **read_command_line (void); +static char **parse_options (char **argv); +static void run_actions (char **argv); +static void usage (void); + +static void print_stats (void); + + +int main (void) NO_RETURN; + +/* Pintos main program. */ +int +main (void) { + uint64_t mem_end; + char **argv; + + /* Clear BSS and get machine's RAM size. */ + bss_init (); + + /* Break command line into arguments and parse options. */ + argv = read_command_line (); + argv = parse_options (argv); + + /* Initialize ourselves as a thread so we can use locks, + then enable console locking. */ + thread_init (); + console_init (); + + /* Initialize memory system. */ + mem_end = palloc_init (); + malloc_init (); + paging_init (mem_end); + +#ifdef USERPROG + tss_init (); + gdt_init (); +#endif + + /* Initialize interrupt handlers. */ + intr_init (); + timer_init (); + kbd_init (); + input_init (); +#ifdef USERPROG + exception_init (); + syscall_init (); +#endif + /* Start thread scheduler and enable interrupts. */ + thread_start (); + serial_init_queue (); + timer_calibrate (); + +#ifdef FILESYS + /* Initialize file system. */ + disk_init (); + filesys_init (format_filesys); +#endif + + printf ("Boot complete.\n"); + + /* Run actions specified on kernel command line. */ + run_actions (argv); + + /* Finish up. */ + if (power_off_when_done) + power_off (); + thread_exit (); +} + +/* Clear BSS */ +static void +bss_init (void) { + /* The "BSS" is a segment that should be initialized to zeros. + It isn't actually stored on disk or zeroed by the kernel + loader, so we have to zero it ourselves. + + The start and end of the BSS segment is recorded by the + linker as _start_bss and _end_bss. See kernel.lds. */ + extern char _start_bss, _end_bss; + memset (&_start_bss, 0, &_end_bss - &_start_bss); +} + +/* Populates the page table with the kernel virtual mapping, + * and then sets up the CPU to use the new page directory. + * Points base_pml4 to the pml4 it creates. */ +static void +paging_init (uint64_t mem_end) { + uint64_t *pml4, *pte; + int perm; + pml4 = base_pml4 = palloc_get_page (PAL_ASSERT | PAL_ZERO); + + extern char start, _end_kernel_text; + // Maps physical address [0 ~ mem_end] to + // [LOADER_KERN_BASE ~ LOADER_KERN_BASE + mem_end]. + for (uint64_t pa = 0; pa < mem_end; pa += PGSIZE) { + uint64_t va = (uint64_t) ptov(pa); + + perm = PTE_P | PTE_W; + if ((uint64_t) &start <= va && va < (uint64_t) &_end_kernel_text) + perm &= ~PTE_W; + + if ((pte = pml4e_walk (pml4, va, 1)) != NULL) + *pte = pa | perm; + } + + // reload cr3 + pml4_activate(0); +} + +/* Breaks the kernel command line into words and returns them as + an argv-like array. */ +static char ** +read_command_line (void) { + static char *argv[LOADER_ARGS_LEN / 2 + 1]; + char *p, *end; + int argc; + int i; + + argc = *(uint32_t *) ptov (LOADER_ARG_CNT); + p = ptov (LOADER_ARGS); + end = p + LOADER_ARGS_LEN; + for (i = 0; i < argc; i++) { + if (p >= end) + PANIC ("command line arguments overflow"); + + argv[i] = p; + p += strnlen (p, end - p) + 1; + } + argv[argc] = NULL; + + /* Print kernel command line. */ + printf ("Kernel command line:"); + for (i = 0; i < argc; i++) + if (strchr (argv[i], ' ') == NULL) + printf (" %s", argv[i]); + else + printf (" '%s'", argv[i]); + printf ("\n"); + + return argv; +} + +/* Parses options in ARGV[] + and returns the first non-option argument. */ +static char ** +parse_options (char **argv) { + for (; *argv != NULL && **argv == '-'; argv++) { + char *save_ptr; + char *name = strtok_r (*argv, "=", &save_ptr); + char *value = strtok_r (NULL, "", &save_ptr); + + if (!strcmp (name, "-h")) + usage (); + else if (!strcmp (name, "-q")) + power_off_when_done = true; +#ifdef FILESYS + else if (!strcmp (name, "-f")) + format_filesys = true; +#endif + else if (!strcmp (name, "-rs")) + random_init (atoi (value)); + else if (!strcmp (name, "-mlfqs")) + thread_mlfqs = true; +#ifdef USERPROG + else if (!strcmp (name, "-ul")) + user_page_limit = atoi (value); +#endif + else + PANIC ("unknown option `%s' (use -h for help)", name); + } + + return argv; +} + +/* Runs the task specified in ARGV[1]. */ +static void +run_task (char **argv) { + const char *task = argv[1]; + + printf ("Executing '%s':\n", task); +#ifdef USERPROG + process_wait (process_create_initd (task)); +#else + run_test (task); +#endif + printf ("Execution of '%s' complete.\n", task); +} + +/* Executes all of the actions specified in ARGV[] + up to the null pointer sentinel. */ +static void +run_actions (char **argv) { + /* An action. */ + struct action { + char *name; /* Action name. */ + int argc; /* # of args, including action name. */ + void (*function) (char **argv); /* Function to execute action. */ + }; + + /* Table of supported actions. */ + static const struct action actions[] = { + {"run", 2, run_task}, +#ifdef FILESYS + {"ls", 1, fsutil_ls}, + {"cat", 2, fsutil_cat}, + {"rm", 2, fsutil_rm}, + {"put", 2, fsutil_put}, + {"get", 2, fsutil_get}, +#endif + {NULL, 0, NULL}, + }; + + while (*argv != NULL) { + const struct action *a; + int i; + + /* Find action name. */ + for (a = actions; ; a++) + if (a->name == NULL) + PANIC ("unknown action `%s' (use -h for help)", *argv); + else if (!strcmp (*argv, a->name)) + break; + + /* Check for required arguments. */ + for (i = 1; i < a->argc; i++) + if (argv[i] == NULL) + PANIC ("action `%s' requires %d argument(s)", *argv, a->argc - 1); + + /* Invoke action and advance. */ + a->function (argv); + argv += a->argc; + } + +} + +/* Prints a kernel command line help message and powers off the + machine. */ +static void +usage (void) { + printf ("\nCommand line syntax: [OPTION...] [ACTION...]\n" + "Options must precede actions.\n" + "Actions are executed in the order specified.\n" + "\nAvailable actions:\n" +#ifdef USERPROG + " run 'PROG [ARG...]' Run PROG and wait for it to complete.\n" +#else + " run TEST Run TEST.\n" +#endif +#ifdef FILESYS + " ls List files in the root directory.\n" + " cat FILE Print FILE to the console.\n" + " rm FILE Delete FILE.\n" + "Use these actions indirectly via `pintos' -g and -p options:\n" + " put FILE Put FILE into file system from scratch disk.\n" + " get FILE Get FILE from file system into scratch disk.\n" +#endif + "\nOptions:\n" + " -h Print this help message and power off.\n" + " -q Power off VM after actions or on panic.\n" + " -f Format file system disk during startup.\n" + " -rs=SEED Set random number seed to SEED.\n" + " -mlfqs Use multi-level feedback queue scheduler.\n" +#ifdef USERPROG + " -ul=COUNT Limit user memory to COUNT pages.\n" +#endif + ); + power_off (); +} + + +/* Powers down the machine we're running on, + as long as we're running on Bochs or QEMU. */ +void +power_off (void) { +#ifdef FILESYS + filesys_done (); +#endif + + print_stats (); + + printf ("Powering off...\n"); + outw (0x604, 0x2000); /* Poweroff command for qemu */ + for (;;); +} + +/* Print statistics about Pintos execution. */ +static void +print_stats (void) { + timer_print_stats (); + thread_print_stats (); +#ifdef FILESYS + disk_print_stats (); +#endif + console_print_stats (); + kbd_print_stats (); +#ifdef USERPROG + exception_print_stats (); +#endif +} diff --git a/threads/interrupt.c b/threads/interrupt.c new file mode 100644 index 0000000..4fdc9a4 --- /dev/null +++ b/threads/interrupt.c @@ -0,0 +1,405 @@ +#include "threads/interrupt.h" +#include +#include +#include +#include +#include "threads/flags.h" +#include "threads/intr-stubs.h" +#include "threads/io.h" +#include "threads/thread.h" +#include "threads/mmu.h" +#include "threads/vaddr.h" +#include "devices/timer.h" +#include "intrinsic.h" +#ifdef USERPROG +#include "userprog/gdt.h" +#endif + +/* Number of x86_64 interrupts. */ +#define INTR_CNT 256 + +/* Creates an gate that invokes FUNCTION. + + The gate has descriptor privilege level DPL, meaning that it + can be invoked intentionally when the processor is in the DPL + or lower-numbered ring. In practice, DPL==3 allows user mode + to call into the gate and DPL==0 prevents such calls. Faults + and exceptions that occur in user mode still cause gates with + DPL==0 to be invoked. + + TYPE must be either 14 (for an interrupt gate) or 15 (for a + trap gate). The difference is that entering an interrupt gate + disables interrupts, but entering a trap gate does not. See + [IA32-v3a] section 5.12.1.2 "Flag Usage By Exception- or + Interrupt-Handler Procedure" for discussion. */ + +struct gate { + unsigned off_15_0 : 16; // low 16 bits of offset in segment + unsigned ss : 16; // segment selector + unsigned ist : 3; // # args, 0 for interrupt/trap gates + unsigned rsv1 : 5; // reserved(should be zero I guess) + unsigned type : 4; // type(STS_{TG,IG32,TG32}) + unsigned s : 1; // must be 0 (system) + unsigned dpl : 2; // descriptor(meaning new) privilege level + unsigned p : 1; // Present + unsigned off_31_16 : 16; // high bits of offset in segment + uint32_t off_32_63; + uint32_t rsv2; +}; + +/* The Interrupt Descriptor Table (IDT). The format is fixed by + the CPU. See [IA32-v3a] sections 5.10 "Interrupt Descriptor + Table (IDT)", 5.11 "IDT Descriptors", 5.12.1.2 "Flag Usage By + Exception- or Interrupt-Handler Procedure". */ +static struct gate idt[INTR_CNT]; + +static struct desc_ptr idt_desc = { + .size = sizeof(idt) - 1, + .address = (uint64_t) idt +}; + + +#define make_gate(g, function, d, t) \ +{ \ + ASSERT ((function) != NULL); \ + ASSERT ((d) >= 0 && (d) <= 3); \ + ASSERT ((t) >= 0 && (t) <= 15); \ + *(g) = (struct gate) { \ + .off_15_0 = (uint64_t) (function) & 0xffff, \ + .ss = SEL_KCSEG, \ + .ist = 0, \ + .rsv1 = 0, \ + .type = (t), \ + .s = 0, \ + .dpl = (d), \ + .p = 1, \ + .off_31_16 = ((uint64_t) (function) >> 16) & 0xffff, \ + .off_32_63 = ((uint64_t) (function) >> 32) & 0xffffffff, \ + .rsv2 = 0, \ + }; \ +} + +/* Creates an interrupt gate that invokes FUNCTION with the given DPL. */ +#define make_intr_gate(g, function, dpl) make_gate((g), (function), (dpl), 14) + +/* Creates a trap gate that invokes FUNCTION with the given DPL. */ +#define make_trap_gate(g, function, dpl) make_gate((g), (function), (dpl), 15) + + + +/* Interrupt handler functions for each interrupt. */ +static intr_handler_func *intr_handlers[INTR_CNT]; + +/* Names for each interrupt, for debugging purposes. */ +static const char *intr_names[INTR_CNT]; + +/* External interrupts are those generated by devices outside the + CPU, such as the timer. External interrupts run with + interrupts turned off, so they never nest, nor are they ever + pre-empted. Handlers for external interrupts also may not + sleep, although they may invoke intr_yield_on_return() to + request that a new process be scheduled just before the + interrupt returns. */ +static bool in_external_intr; /* Are we processing an external interrupt? */ +static bool yield_on_return; /* Should we yield on interrupt return? */ + +/* Programmable Interrupt Controller helpers. */ +static void pic_init (void); +static void pic_end_of_interrupt (int irq); + +/* Interrupt handlers. */ +void intr_handler (struct intr_frame *args); + +/* Returns the current interrupt status. */ +enum intr_level +intr_get_level (void) { + uint64_t flags; + + /* Push the flags register on the processor stack, then pop the + value off the stack into `flags'. See [IA32-v2b] "PUSHF" + and "POP" and [IA32-v3a] 5.8.1 "Masking Maskable Hardware + Interrupts". */ + asm volatile ("pushfq; popq %0" : "=g" (flags)); + + return flags & FLAG_IF ? INTR_ON : INTR_OFF; +} + +/* Enables or disables interrupts as specified by LEVEL and + returns the previous interrupt status. */ +enum intr_level +intr_set_level (enum intr_level level) { + return level == INTR_ON ? intr_enable () : intr_disable (); +} + +/* Enables interrupts and returns the previous interrupt status. */ +enum intr_level +intr_enable (void) { + enum intr_level old_level = intr_get_level (); + ASSERT (!intr_context ()); + + /* Enable interrupts by setting the interrupt flag. + + See [IA32-v2b] "STI" and [IA32-v3a] 5.8.1 "Masking Maskable + Hardware Interrupts". */ + asm volatile ("sti"); + + return old_level; +} + +/* Disables interrupts and returns the previous interrupt status. */ +enum intr_level +intr_disable (void) { + enum intr_level old_level = intr_get_level (); + + /* Disable interrupts by clearing the interrupt flag. + See [IA32-v2b] "CLI" and [IA32-v3a] 5.8.1 "Masking Maskable + Hardware Interrupts". */ + asm volatile ("cli" : : : "memory"); + + return old_level; +} + +/* Initializes the interrupt system. */ +void +intr_init (void) { + int i; + + /* Initialize interrupt controller. */ + pic_init (); + + /* Initialize IDT. */ + for (i = 0; i < INTR_CNT; i++) { + make_intr_gate(&idt[i], intr_stubs[i], 0); + intr_names[i] = "unknown"; + } + +#ifdef USERPROG + /* Load TSS. */ + ltr (SEL_TSS); +#endif + + /* Load IDT register. */ + lidt(&idt_desc); + + /* Initialize intr_names. */ + intr_names[0] = "#DE Divide Error"; + intr_names[1] = "#DB Debug Exception"; + intr_names[2] = "NMI Interrupt"; + intr_names[3] = "#BP Breakpoint Exception"; + intr_names[4] = "#OF Overflow Exception"; + intr_names[5] = "#BR BOUND Range Exceeded Exception"; + intr_names[6] = "#UD Invalid Opcode Exception"; + intr_names[7] = "#NM Device Not Available Exception"; + intr_names[8] = "#DF Double Fault Exception"; + intr_names[9] = "Coprocessor Segment Overrun"; + intr_names[10] = "#TS Invalid TSS Exception"; + intr_names[11] = "#NP Segment Not Present"; + intr_names[12] = "#SS Stack Fault Exception"; + intr_names[13] = "#GP General Protection Exception"; + intr_names[14] = "#PF Page-Fault Exception"; + intr_names[16] = "#MF x87 FPU Floating-Point Error"; + intr_names[17] = "#AC Alignment Check Exception"; + intr_names[18] = "#MC Machine-Check Exception"; + intr_names[19] = "#XF SIMD Floating-Point Exception"; +} + +/* Registers interrupt VEC_NO to invoke HANDLER with descriptor + privilege level DPL. Names the interrupt NAME for debugging + purposes. The interrupt handler will be invoked with + interrupt status set to LEVEL. */ +static void +register_handler (uint8_t vec_no, int dpl, enum intr_level level, + intr_handler_func *handler, const char *name) { + ASSERT (intr_handlers[vec_no] == NULL); + if (level == INTR_ON) { + make_trap_gate(&idt[vec_no], intr_stubs[vec_no], dpl); + } + else { + make_intr_gate(&idt[vec_no], intr_stubs[vec_no], dpl); + } + intr_handlers[vec_no] = handler; + intr_names[vec_no] = name; +} + +/* Registers external interrupt VEC_NO to invoke HANDLER, which + is named NAME for debugging purposes. The handler will + execute with interrupts disabled. */ +void +intr_register_ext (uint8_t vec_no, intr_handler_func *handler, + const char *name) { + ASSERT (vec_no >= 0x20 && vec_no <= 0x2f); + register_handler (vec_no, 0, INTR_OFF, handler, name); +} + +/* Registers internal interrupt VEC_NO to invoke HANDLER, which + is named NAME for debugging purposes. The interrupt handler + will be invoked with interrupt status LEVEL. + + The handler will have descriptor privilege level DPL, meaning + that it can be invoked intentionally when the processor is in + the DPL or lower-numbered ring. In practice, DPL==3 allows + user mode to invoke the interrupts and DPL==0 prevents such + invocation. Faults and exceptions that occur in user mode + still cause interrupts with DPL==0 to be invoked. See + [IA32-v3a] sections 4.5 "Privilege Levels" and 4.8.1.1 + "Accessing Nonconforming Code Segments" for further + discussion. */ +void +intr_register_int (uint8_t vec_no, int dpl, enum intr_level level, + intr_handler_func *handler, const char *name) +{ + ASSERT (vec_no < 0x20 || vec_no > 0x2f); + register_handler (vec_no, dpl, level, handler, name); +} + +/* Returns true during processing of an external interrupt + and false at all other times. */ +bool +intr_context (void) { + return in_external_intr; +} + +/* During processing of an external interrupt, directs the + interrupt handler to yield to a new process just before + returning from the interrupt. May not be called at any other + time. */ +void +intr_yield_on_return (void) { + ASSERT (intr_context ()); + yield_on_return = true; +} + +/* 8259A Programmable Interrupt Controller. */ + +/* Every PC has two 8259A Programmable Interrupt Controller (PIC) + chips. One is a "master" accessible at ports 0x20 and 0x21. + The other is a "slave" cascaded onto the master's IRQ 2 line + and accessible at ports 0xa0 and 0xa1. Accesses to port 0x20 + set the A0 line to 0 and accesses to 0x21 set the A1 line to + 1. The situation is similar for the slave PIC. + + By default, interrupts 0...15 delivered by the PICs will go to + interrupt vectors 0...15. Unfortunately, those vectors are + also used for CPU traps and exceptions. We reprogram the PICs + so that interrupts 0...15 are delivered to interrupt vectors + 32...47 (0x20...0x2f) instead. */ + +/* Initializes the PICs. Refer to [8259A] for details. */ +static void +pic_init (void) { + /* Mask all interrupts on both PICs. */ + outb (0x21, 0xff); + outb (0xa1, 0xff); + + /* Initialize master. */ + outb (0x20, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ + outb (0x21, 0x20); /* ICW2: line IR0...7 -> irq 0x20...0x27. */ + outb (0x21, 0x04); /* ICW3: slave PIC on line IR2. */ + outb (0x21, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ + + /* Initialize slave. */ + outb (0xa0, 0x11); /* ICW1: single mode, edge triggered, expect ICW4. */ + outb (0xa1, 0x28); /* ICW2: line IR0...7 -> irq 0x28...0x2f. */ + outb (0xa1, 0x02); /* ICW3: slave ID is 2. */ + outb (0xa1, 0x01); /* ICW4: 8086 mode, normal EOI, non-buffered. */ + + /* Unmask all interrupts. */ + outb (0x21, 0x00); + outb (0xa1, 0x00); +} + +/* Sends an end-of-interrupt signal to the PIC for the given IRQ. + If we don't acknowledge the IRQ, it will never be delivered to + us again, so this is important. */ +static void +pic_end_of_interrupt (int irq) { + ASSERT (irq >= 0x20 && irq < 0x30); + + /* Acknowledge master PIC. */ + outb (0x20, 0x20); + + /* Acknowledge slave PIC if this is a slave interrupt. */ + if (irq >= 0x28) + outb (0xa0, 0x20); +} +/* Interrupt handlers. */ + +/* Handler for all interrupts, faults, and exceptions. This + function is called by the assembly language interrupt stubs in + intr-stubs.S. FRAME describes the interrupt and the + interrupted thread's registers. */ +void +intr_handler (struct intr_frame *frame) { + bool external; + intr_handler_func *handler; + + /* External interrupts are special. + We only handle one at a time (so interrupts must be off) + and they need to be acknowledged on the PIC (see below). + An external interrupt handler cannot sleep. */ + external = frame->vec_no >= 0x20 && frame->vec_no < 0x30; + if (external) { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (!intr_context ()); + + in_external_intr = true; + yield_on_return = false; + } + + /* Invoke the interrupt's handler. */ + handler = intr_handlers[frame->vec_no]; + if (handler != NULL) + handler (frame); + else if (frame->vec_no == 0x27 || frame->vec_no == 0x2f) { + /* There is no handler, but this interrupt can trigger + spuriously due to a hardware fault or hardware race + condition. Ignore it. */ + } else { + /* No handler and not spurious. Invoke the unexpected + interrupt handler. */ + intr_dump_frame (frame); + PANIC ("Unexpected interrupt"); + } + + /* Complete the processing of an external interrupt. */ + if (external) { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (intr_context ()); + + in_external_intr = false; + pic_end_of_interrupt (frame->vec_no); + + if (yield_on_return) + thread_yield (); + } +} + +/* Dumps interrupt frame F to the console, for debugging. */ +void +intr_dump_frame (const struct intr_frame *f) { + /* CR2 is the linear address of the last page fault. + See [IA32-v2a] "MOV--Move to/from Control Registers" and + [IA32-v3a] 5.14 "Interrupt 14--Page Fault Exception + (#PF)". */ + uint64_t cr2 = rcr2(); + printf ("Interrupt %#04llx (%s) at rip=%llx\n", + f->vec_no, intr_names[f->vec_no], f->rip); + printf (" cr2=%016llx error=%16llx\n", cr2, f->error_code); + printf ("rax %016llx rbx %016llx rcx %016llx rdx %016llx\n", + f->R.rax, f->R.rbx, f->R.rcx, f->R.rdx); + printf ("rsp %016llx rbp %016llx rsi %016llx rdi %016llx\n", + f->rsp, f->R.rbp, f->R.rsi, f->R.rdi); + printf ("rip %016llx r8 %016llx r9 %016llx r10 %016llx\n", + f->rip, f->R.r8, f->R.r9, f->R.r10); + printf ("r11 %016llx r12 %016llx r13 %016llx r14 %016llx\n", + f->R.r11, f->R.r12, f->R.r13, f->R.r14); + printf ("r15 %016llx rflags %08llx\n", f->R.r15, f->eflags); + printf ("es: %04x ds: %04x cs: %04x ss: %04x\n", + f->es, f->ds, f->cs, f->ss); +} + +/* Returns the name of interrupt VEC. */ +const char * +intr_name (uint8_t vec) { + return intr_names[vec]; +} diff --git a/threads/intr-stubs.S b/threads/intr-stubs.S new file mode 100644 index 0000000..f1c7c6f --- /dev/null +++ b/threads/intr-stubs.S @@ -0,0 +1,200 @@ +#include "threads/loader.h" + +/* Main interrupt entry point. + + An internal or external interrupt starts in one of the + intrNN_stub routines, which push the `struct intr_frame' + frame_pointer, error_code, and vec_no members on the stack, + then jump here. + + We save the rest of the `struct intr_frame' members to the + stack, set up some registers as needed by the kernel, and then + call intr_handler(), which actually handles the interrupt. +*/ +.section .text +.func intr_entry +intr_entry: + /* Save caller's registers. */ + subq $16,%rsp + movw %ds,8(%rsp) + movw %es,0(%rsp) + subq $120,%rsp + movq %rax,112(%rsp) + movq %rbx,104(%rsp) + movq %rcx,96(%rsp) + movq %rdx,88(%rsp) + movq %rbp,80(%rsp) + movq %rdi,72(%rsp) + movq %rsi,64(%rsp) + movq %r8,56(%rsp) + movq %r9,48(%rsp) + movq %r10,40(%rsp) + movq %r11,32(%rsp) + movq %r12,24(%rsp) + movq %r13,16(%rsp) + movq %r14,8(%rsp) + movq %r15,0(%rsp) + cld /* String instructions go upward. */ + movq $SEL_KDSEG, %rax + movw %ax, %ds + movw %ax, %es + movw %ax, %ss + movw %ax, %fs + movw %ax, %gs + movq %rsp,%rdi + call intr_handler + movq 0(%rsp), %r15 + movq 8(%rsp), %r14 + movq 16(%rsp), %r13 + movq 24(%rsp), %r12 + movq 32(%rsp), %r11 + movq 40(%rsp), %r10 + movq 48(%rsp), %r9 + movq 56(%rsp), %r8 + movq 64(%rsp), %rsi + movq 72(%rsp), %rdi + movq 80(%rsp), %rbp + movq 88(%rsp), %rdx + movq 96(%rsp), %rcx + movq 104(%rsp), %rbx + movq 112(%rsp), %rax + addq $120, %rsp + movw 8(%rsp), %ds + movw (%rsp), %es + addq $32, %rsp + iretq +.endfunc + +/* Interrupt stubs. + + This defines 256 fragments of code, named `intr00_stub' + through `intrff_stub', each of which is used as the entry + point for the corresponding interrupt vector. It also puts + the address of each of these functions in the correct spot in + `intr_stubs', an array of function pointers. + + Most of the stubs do this: + + 1. Push %ebp on the stack (frame_pointer in `struct intr_frame'). + + 2. Push 0 on the stack (error_code). + + 3. Push the interrupt number on the stack (vec_no). + + The CPU pushes an extra "error code" on the stack for a few + interrupts. Because we want %ebp to be where the error code + is, we follow a different path: + + 1. Push a duplicate copy of the error code on the stack. + + 2. Replace the original copy of the error code by %ebp. + + 3. Push the interrupt number on the stack. */ + +/* This implements steps 1 and 2, described above, in the common + case where we just push a 0 error code. */ +#define zero pushq $0; + +/* This implements steps 1 and 2, described above, in the case + where the CPU already pushed an error code. */ +#define REAL + +.section .data +.globl intr_stubs +intr_stubs: + +/* Emits a stub for interrupt vector NUMBER. + TYPE is `zero', for the case where we push a 0 error code, + or `REAL', if the CPU pushes an error code for us. */ +#define STUB(NUMBER, TYPE) \ +.section .text; \ +.globl intr##NUMBER##_stub; \ +.func intr##NUMBER##_stub; \ +intr##NUMBER##_stub: \ + TYPE; \ + push $0x##NUMBER; \ + jmp intr_entry; \ +.endfunc; \ +.section .data; \ +.quad intr##NUMBER##_stub; + +/* All the stubs. */ +STUB(00, zero) STUB(01, zero) STUB(02, zero) STUB(03, zero) +STUB(04, zero) STUB(05, zero) STUB(06, zero) STUB(07, zero) +STUB(08, REAL) STUB(09, zero) STUB(0a, REAL) STUB(0b, REAL) +STUB(0c, zero) STUB(0d, REAL) STUB(0e, REAL) STUB(0f, zero) + +STUB(10, zero) STUB(11, REAL) STUB(12, zero) STUB(13, zero) +STUB(14, zero) STUB(15, zero) STUB(16, zero) STUB(17, zero) +STUB(18, REAL) STUB(19, zero) STUB(1a, REAL) STUB(1b, REAL) +STUB(1c, zero) STUB(1d, REAL) STUB(1e, REAL) STUB(1f, zero) + +STUB(20, zero) STUB(21, zero) STUB(22, zero) STUB(23, zero) +STUB(24, zero) STUB(25, zero) STUB(26, zero) STUB(27, zero) +STUB(28, zero) STUB(29, zero) STUB(2a, zero) STUB(2b, zero) +STUB(2c, zero) STUB(2d, zero) STUB(2e, zero) STUB(2f, zero) + +STUB(30, zero) STUB(31, zero) STUB(32, zero) STUB(33, zero) +STUB(34, zero) STUB(35, zero) STUB(36, zero) STUB(37, zero) +STUB(38, zero) STUB(39, zero) STUB(3a, zero) STUB(3b, zero) +STUB(3c, zero) STUB(3d, zero) STUB(3e, zero) STUB(3f, zero) + +STUB(40, zero) STUB(41, zero) STUB(42, zero) STUB(43, zero) +STUB(44, zero) STUB(45, zero) STUB(46, zero) STUB(47, zero) +STUB(48, zero) STUB(49, zero) STUB(4a, zero) STUB(4b, zero) +STUB(4c, zero) STUB(4d, zero) STUB(4e, zero) STUB(4f, zero) + +STUB(50, zero) STUB(51, zero) STUB(52, zero) STUB(53, zero) +STUB(54, zero) STUB(55, zero) STUB(56, zero) STUB(57, zero) +STUB(58, zero) STUB(59, zero) STUB(5a, zero) STUB(5b, zero) +STUB(5c, zero) STUB(5d, zero) STUB(5e, zero) STUB(5f, zero) + +STUB(60, zero) STUB(61, zero) STUB(62, zero) STUB(63, zero) +STUB(64, zero) STUB(65, zero) STUB(66, zero) STUB(67, zero) +STUB(68, zero) STUB(69, zero) STUB(6a, zero) STUB(6b, zero) +STUB(6c, zero) STUB(6d, zero) STUB(6e, zero) STUB(6f, zero) + +STUB(70, zero) STUB(71, zero) STUB(72, zero) STUB(73, zero) +STUB(74, zero) STUB(75, zero) STUB(76, zero) STUB(77, zero) +STUB(78, zero) STUB(79, zero) STUB(7a, zero) STUB(7b, zero) +STUB(7c, zero) STUB(7d, zero) STUB(7e, zero) STUB(7f, zero) + +STUB(80, zero) STUB(81, zero) STUB(82, zero) STUB(83, zero) +STUB(84, zero) STUB(85, zero) STUB(86, zero) STUB(87, zero) +STUB(88, zero) STUB(89, zero) STUB(8a, zero) STUB(8b, zero) +STUB(8c, zero) STUB(8d, zero) STUB(8e, zero) STUB(8f, zero) + +STUB(90, zero) STUB(91, zero) STUB(92, zero) STUB(93, zero) +STUB(94, zero) STUB(95, zero) STUB(96, zero) STUB(97, zero) +STUB(98, zero) STUB(99, zero) STUB(9a, zero) STUB(9b, zero) +STUB(9c, zero) STUB(9d, zero) STUB(9e, zero) STUB(9f, zero) + +STUB(a0, zero) STUB(a1, zero) STUB(a2, zero) STUB(a3, zero) +STUB(a4, zero) STUB(a5, zero) STUB(a6, zero) STUB(a7, zero) +STUB(a8, zero) STUB(a9, zero) STUB(aa, zero) STUB(ab, zero) +STUB(ac, zero) STUB(ad, zero) STUB(ae, zero) STUB(af, zero) + +STUB(b0, zero) STUB(b1, zero) STUB(b2, zero) STUB(b3, zero) +STUB(b4, zero) STUB(b5, zero) STUB(b6, zero) STUB(b7, zero) +STUB(b8, zero) STUB(b9, zero) STUB(ba, zero) STUB(bb, zero) +STUB(bc, zero) STUB(bd, zero) STUB(be, zero) STUB(bf, zero) + +STUB(c0, zero) STUB(c1, zero) STUB(c2, zero) STUB(c3, zero) +STUB(c4, zero) STUB(c5, zero) STUB(c6, zero) STUB(c7, zero) +STUB(c8, zero) STUB(c9, zero) STUB(ca, zero) STUB(cb, zero) +STUB(cc, zero) STUB(cd, zero) STUB(ce, zero) STUB(cf, zero) + +STUB(d0, zero) STUB(d1, zero) STUB(d2, zero) STUB(d3, zero) +STUB(d4, zero) STUB(d5, zero) STUB(d6, zero) STUB(d7, zero) +STUB(d8, zero) STUB(d9, zero) STUB(da, zero) STUB(db, zero) +STUB(dc, zero) STUB(dd, zero) STUB(de, zero) STUB(df, zero) + +STUB(e0, zero) STUB(e1, zero) STUB(e2, zero) STUB(e3, zero) +STUB(e4, zero) STUB(e5, zero) STUB(e6, zero) STUB(e7, zero) +STUB(e8, zero) STUB(e9, zero) STUB(ea, zero) STUB(eb, zero) +STUB(ec, zero) STUB(ed, zero) STUB(ee, zero) STUB(ef, zero) + +STUB(f0, zero) STUB(f1, zero) STUB(f2, zero) STUB(f3, zero) +STUB(f4, zero) STUB(f5, zero) STUB(f6, zero) STUB(f7, zero) +STUB(f8, zero) STUB(f9, zero) STUB(fa, zero) STUB(fb, zero) +STUB(fc, zero) STUB(fd, zero) STUB(fe, zero) STUB(ff, zero) diff --git a/threads/kernel.lds.S b/threads/kernel.lds.S new file mode 100644 index 0000000..79dcacd --- /dev/null +++ b/threads/kernel.lds.S @@ -0,0 +1,35 @@ +#include "threads/loader.h" + +OUTPUT_FORMAT("elf64-x86-64") +OUTPUT_ARCH(i386:x86-64) +ENTRY(_start) /* Kernel starts at "start" symbol. */ + +SECTIONS +{ + /* Specifies the virtual address for the kernel base. */ + . = LOADER_KERN_BASE + LOADER_PHYS_BASE; + + PROVIDE(start = .); + /* Kernel starts with code, followed by read-only data and writable data. */ + .text : AT(LOADER_PHYS_BASE) { + *(.entry) + *(.text .text.* .stub .gnu.linkonce.t.*) + } = 0x90 + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + + . = ALIGN(0x1000); + PROVIDE(_end_kernel_text = .); + + .data : { *(.data) *(.data.*)} + + /* BSS (zero-initialized data) is after everything else. */ + PROVIDE(_start_bss = .); + .bss : { *(.bss) } + PROVIDE(_end_bss = .); + + PROVIDE(_end = .); + + /DISCARD/ : { + *(.eh_frame .note.GNU-stack .stab) + } +} diff --git a/threads/loader.S b/threads/loader.S new file mode 100644 index 0000000..2a26a7a --- /dev/null +++ b/threads/loader.S @@ -0,0 +1,313 @@ +/* This file is derived from source code used in MIT's 6.828 + course. The original copyright notice is reproduced in full + below. */ + +/* + * Copyright (C) 1997 Massachusetts Institute of Technology + * + * This software is being provided by the copyright holders under the + * following license. By obtaining, using and/or copying this software, + * you agree that you have read, understood, and will comply with the + * following terms and conditions: + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose and without fee or royalty is + * hereby granted, provided that the full text of this NOTICE appears on + * ALL copies of the software and documentation or portions thereof, + * including modifications, that you make. + * + * THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO + * REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, + * BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR + * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR + * THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY + * THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT + * HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR + * DOCUMENTATION. + * + * The name and trademarks of copyright holders may NOT be used in + * advertising or publicity pertaining to the software without specific, + * written prior permission. Title to copyright in this software and any + * associated documentation will at all times remain with copyright + * holders. See the file AUTHORS which should have accompanied this software + * for a list of all copyright holders. + * + * This file may be derived from previously copyrighted software. This + * copyright applies only to those changes made by the copyright + * holders listed in the AUTHORS file. The rest of this file is covered by + * the copyright notices, if any, listed below. + */ + +#include "threads/loader.h" + +#### Kernel loader. + +#### This code should be stored in the first sector of the hard disk. +#### When the BIOS runs, it loads this code at physical address +#### 0x7c00-0x7e00 (512 bytes). Then it jumps to the beginning of it, +#### in real mode. This code switches into protected mode (32-bit +#### mode) so that all of memory can accessed, loads the kernel into +#### memory, and jumps to the first byte of the kernel, where start.S +#### is linked. + +/* Flags in control register 0. */ +#define CR0_PE 0x00000001 /* Protection Enable. */ +#define CR0_EM 0x00000004 /* (Floating-point) Emulation. */ +#define CR0_PG 0x80000000 /* Paging. */ +#define CR0_WP 0x00010000 /* Write-Protect enable in kernel mode. */ + + +.globl start +start: + +# Code runs in real mode, which is a 16-bit segment. + .code16 + +# Disable interrupts, because we will not be prepared to handle them +# in protected mode until much later. +# String instructions go upward (e.g. for "rep stosl" below). + + cli + cld + +# Set up data segments. + + subw %ax, %ax + movw %ax, %es + movw %ax, %ds + +# Set up stack segment. +# Stack grows downward starting from us. +# We don't ever use the stack, but we call into the BIOS, +# which might. + + movw %ax, %ss + movw $0x7c00, %sp + +#### Enable A20. Address line 20 is tied to low when the machine +#### boots, which prevents addressing memory about 1 MB. This code +#### fixes it. + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +# Send command for writing output port. + + movb $0xd1, %al + outb %al, $0x64 + +# Poll status register while busy. + +1: inb $0x64, %al + testb $0x2, %al + jnz 1b + +# Enable A20 line. + + movb $0xdf, %al + outb %al, $0x60 + + +#### Get memory size, via interrupt 15h function e820h. Now our pintos +#### runs on x86_64, "Get Extended Memory Size" operation is not sufficient +#### for our purpose. + mov $0xe820, %eax # command + mov $(E820_MAP4), %edi # dst + xor %ebx, %ebx + mov $0x534d4150, %edx # magic + mov $24, %ecx + int $0x15 + cmp %eax, %edx + test %ebx, %ebx + je panic + mov $24, %ebp + +parse_e820: + mov %ecx, -4(%edi) + add $24, %edi + mov $0xe820, %eax + mov $24, %ecx + int $0x15 + jc e820_parse_done + add $24, %ebp + test %ebx, %ebx + jne parse_e820 + +e820_parse_done: + mov %ecx, -4(%edi) + movl $0x40, MULTIBOOT_FLAG + movl %ebp, MULTIBOOT_MMAP_LEN + movl $(E820_MAP), MULTIBOOT_MMAP_ADDR # e820 + +#### Switch to protected mode. + +# Note that interrupts are still off. + +# Point the GDTR to our GDT. Protected mode requires a GDT. +# We need a data32 prefix to ensure that all 32 bits of the GDT +# descriptor are loaded (default is to load only 24 bits). + + data32 lgdt gdtdesc + +# Then we turn on the following bits in CR0: +# PE (Protect Enable): this turns on protected mode. +# PG (Paging): turns on paging. +# WP (Write Protect): if unset, ring 0 code ignores +# write-protect bits in page tables (!). +# EM (Emulation): forces floating-point instructions to trap. +# We don't support floating point. + + movl %cr0, %eax + orl $CR0_PE, %eax + movl %eax, %cr0 + +# We're now in protected mode in a 16-bit segment. The CPU still has +# the real-mode code segment cached in %cs's segment descriptor. We +# need to reload %cs, and the easiest way is to use a far jump. +# Because we're not in a 32-bit segment the data32 prefix is needed to +# jump to a 32-bit offset. + + data32 ljmp $SEL_KCSEG, $protcseg + +# We're now in protected mode in a 32-bit segment. + + .code32 + +# Reload all the other segment registers and the stack pointer to +# point into our new GDT. + +protcseg: + movw $SEL_KDSEG, %ax + movw %ax, %ds + movw %ax, %es + movw %ax, %fs + movw %ax, %gs + movw %ax, %ss + movl $LOADER_PHYS_BASE + 0x30000, %esp + +#### Load kernel starting at physical address LOADER_PHYS_BASE by +#### frobbing the IDE controller directly. + + movl $1, %ebx + movl $LOADER_PHYS_BASE, %edi + +# Disable interrupt delivery by IDE controller, because we will be +# polling for data. +# (If we don't do this, Bochs 2.2.6 will never deliver any IDE +# interrupt to us later after we reset the interrupt controller during +# boot, even if we also reset the IDE controller.) + + movw $0x3f6, %dx + movb $0x02, %al + outb %al, %dx + +read_sector: + +# Poll status register while controller busy. + + movl $0x1f7, %edx +1: inb %dx, %al + testb $0x80, %al + jnz 1b + +# Read a single sector. + + movl $0x1f2, %edx + movb $1, %al + outb %al, %dx + +# Sector number to write in low 28 bits. +# LBA mode, device 0 in top 4 bits. + + movl %ebx, %eax + andl $0x0fffffff, %eax + orl $0xe0000000, %eax + +# Dump %eax to ports 0x1f3...0x1f6. + + movl $4, %ecx +1: incw %dx + outb %al, %dx + shrl $8, %eax + loop 1b + +# READ command to command register. + + incw %dx + movb $0x20, %al + outb %al, %dx + +# Poll status register while controller busy. + +1: inb %dx, %al + testb $0x80, %al + jnz 1b + +# Poll status register until data ready. + +1: inb %dx, %al + testb $0x08, %al + jz 1b + +# Transfer sector. + + movl $256, %ecx + movl $0x1f0, %edx + rep insw + +# Next sector. + + incl %ebx + cmpl $KERNEL_LOAD_PAGES*8 + 1, %ebx + jnz read_sector + +#### Jump to kernel entry point. + movl $LOADER_PHYS_BASE, %eax + call *%eax + jmp panic + +#### GDT + +gdt: + .quad 0x0000000000000000 # null seg + .quad 0x00cf9a000000ffff # code seg + .quad 0x00cf92000000ffff # data seg + +gdtdesc: + .word 0x17 # sizeof (gdt) - 1 + .long gdt + +#### Fatal error. +#### Print panic_message (with help from the BIOS) and spin. + +panic: .code16 # We only panic in real mode. + movw $panic_message, %si + movb $0xe, %ah + subb %bh, %bh +1: lodsb + test %al, %al +2: jz 2b # Spin. + int $0x10 + jmp 1b + +panic_message: + .ascii "Panic!" + .byte 0 + +#### Command-line arguments and their count. +#### This is written by the `pintos' utility and read by the kernel. +#### The loader itself does not do anything with the command line. + .org LOADER_ARG_CNT - LOADER_BASE +arg_cnt: + .long 0 + .org LOADER_ARGS - LOADER_BASE +args: + .fill 0x80, 1, 0 + +#### Boot-sector signature. +#### The BIOS checks that this is set properly. + .org LOADER_SIG - LOADER_BASE + .word 0xaa55 diff --git a/threads/malloc.c b/threads/malloc.c new file mode 100644 index 0000000..8c138b2 --- /dev/null +++ b/threads/malloc.c @@ -0,0 +1,268 @@ +#include "threads/malloc.h" +#include +#include +#include +#include +#include +#include +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/vaddr.h" + +/* A simple implementation of malloc(). + + The size of each request, in bytes, is rounded up to a power + of 2 and assigned to the "descriptor" that manages blocks of + that size. The descriptor keeps a list of free blocks. If + the free list is nonempty, one of its blocks is used to + satisfy the request. + + Otherwise, a new page of memory, called an "arena", is + obtained from the page allocator (if none is available, + malloc() returns a null pointer). The new arena is divided + into blocks, all of which are added to the descriptor's free + list. Then we return one of the new blocks. + + When we free a block, we add it to its descriptor's free list. + But if the arena that the block was in now has no in-use + blocks, we remove all of the arena's blocks from the free list + and give the arena back to the page allocator. + + We can't handle blocks bigger than 2 kB using this scheme, + because they're too big to fit in a single page with a + descriptor. We handle those by allocating contiguous pages + with the page allocator and sticking the allocation size at + the beginning of the allocated block's arena header. */ + +/* Descriptor. */ +struct desc { + size_t block_size; /* Size of each element in bytes. */ + size_t blocks_per_arena; /* Number of blocks in an arena. */ + struct list free_list; /* List of free blocks. */ + struct lock lock; /* Lock. */ +}; + +/* Magic number for detecting arena corruption. */ +#define ARENA_MAGIC 0x9a548eed + +/* Arena. */ +struct arena { + unsigned magic; /* Always set to ARENA_MAGIC. */ + struct desc *desc; /* Owning descriptor, null for big block. */ + size_t free_cnt; /* Free blocks; pages in big block. */ +}; + +/* Free block. */ +struct block { + struct list_elem free_elem; /* Free list element. */ +}; + +/* Our set of descriptors. */ +static struct desc descs[10]; /* Descriptors. */ +static size_t desc_cnt; /* Number of descriptors. */ + +static struct arena *block_to_arena (struct block *); +static struct block *arena_to_block (struct arena *, size_t idx); + +/* Initializes the malloc() descriptors. */ +void +malloc_init (void) { + size_t block_size; + + for (block_size = 16; block_size < PGSIZE / 2; block_size *= 2) { + struct desc *d = &descs[desc_cnt++]; + ASSERT (desc_cnt <= sizeof descs / sizeof *descs); + d->block_size = block_size; + d->blocks_per_arena = (PGSIZE - sizeof (struct arena)) / block_size; + list_init (&d->free_list); + lock_init (&d->lock); + } +} + +/* Obtains and returns a new block of at least SIZE bytes. + Returns a null pointer if memory is not available. */ +void * +malloc (size_t size) { + struct desc *d; + struct block *b; + struct arena *a; + + /* A null pointer satisfies a request for 0 bytes. */ + if (size == 0) + return NULL; + + /* Find the smallest descriptor that satisfies a SIZE-byte + request. */ + for (d = descs; d < descs + desc_cnt; d++) + if (d->block_size >= size) + break; + if (d == descs + desc_cnt) { + /* SIZE is too big for any descriptor. + Allocate enough pages to hold SIZE plus an arena. */ + size_t page_cnt = DIV_ROUND_UP (size + sizeof *a, PGSIZE); + a = palloc_get_multiple (0, page_cnt); + if (a == NULL) + return NULL; + + /* Initialize the arena to indicate a big block of PAGE_CNT + pages, and return it. */ + a->magic = ARENA_MAGIC; + a->desc = NULL; + a->free_cnt = page_cnt; + return a + 1; + } + + lock_acquire (&d->lock); + + /* If the free list is empty, create a new arena. */ + if (list_empty (&d->free_list)) { + size_t i; + + /* Allocate a page. */ + a = palloc_get_page (0); + if (a == NULL) { + lock_release (&d->lock); + return NULL; + } + + /* Initialize arena and add its blocks to the free list. */ + a->magic = ARENA_MAGIC; + a->desc = d; + a->free_cnt = d->blocks_per_arena; + for (i = 0; i < d->blocks_per_arena; i++) { + struct block *b = arena_to_block (a, i); + list_push_back (&d->free_list, &b->free_elem); + } + } + + /* Get a block from free list and return it. */ + b = list_entry (list_pop_front (&d->free_list), struct block, free_elem); + a = block_to_arena (b); + a->free_cnt--; + lock_release (&d->lock); + return b; +} + +/* Allocates and return A times B bytes initialized to zeroes. + Returns a null pointer if memory is not available. */ +void * +calloc (size_t a, size_t b) { + void *p; + size_t size; + + /* Calculate block size and make sure it fits in size_t. */ + size = a * b; + if (size < a || size < b) + return NULL; + + /* Allocate and zero memory. */ + p = malloc (size); + if (p != NULL) + memset (p, 0, size); + + return p; +} + +/* Returns the number of bytes allocated for BLOCK. */ +static size_t +block_size (void *block) { + struct block *b = block; + struct arena *a = block_to_arena (b); + struct desc *d = a->desc; + + return d != NULL ? d->block_size : PGSIZE * a->free_cnt - pg_ofs (block); +} + +/* Attempts to resize OLD_BLOCK to NEW_SIZE bytes, possibly + moving it in the process. + If successful, returns the new block; on failure, returns a + null pointer. + A call with null OLD_BLOCK is equivalent to malloc(NEW_SIZE). + A call with zero NEW_SIZE is equivalent to free(OLD_BLOCK). */ +void * +realloc (void *old_block, size_t new_size) { + if (new_size == 0) { + free (old_block); + return NULL; + } else { + void *new_block = malloc (new_size); + if (old_block != NULL && new_block != NULL) { + size_t old_size = block_size (old_block); + size_t min_size = new_size < old_size ? new_size : old_size; + memcpy (new_block, old_block, min_size); + free (old_block); + } + return new_block; + } +} + +/* Frees block P, which must have been previously allocated with + malloc(), calloc(), or realloc(). */ +void +free (void *p) { + if (p != NULL) { + struct block *b = p; + struct arena *a = block_to_arena (b); + struct desc *d = a->desc; + + if (d != NULL) { + /* It's a normal block. We handle it here. */ + +#ifndef NDEBUG + /* Clear the block to help detect use-after-free bugs. */ + memset (b, 0xcc, d->block_size); +#endif + + lock_acquire (&d->lock); + + /* Add block to free list. */ + list_push_front (&d->free_list, &b->free_elem); + + /* If the arena is now entirely unused, free it. */ + if (++a->free_cnt >= d->blocks_per_arena) { + size_t i; + + ASSERT (a->free_cnt == d->blocks_per_arena); + for (i = 0; i < d->blocks_per_arena; i++) { + struct block *b = arena_to_block (a, i); + list_remove (&b->free_elem); + } + palloc_free_page (a); + } + + lock_release (&d->lock); + } else { + /* It's a big block. Free its pages. */ + palloc_free_multiple (a, a->free_cnt); + return; + } + } +} + +/* Returns the arena that block B is inside. */ +static struct arena * +block_to_arena (struct block *b) { + struct arena *a = pg_round_down (b); + + /* Check that the arena is valid. */ + ASSERT (a != NULL); + ASSERT (a->magic == ARENA_MAGIC); + + /* Check that the block is properly aligned for the arena. */ + ASSERT (a->desc == NULL + || (pg_ofs (b) - sizeof *a) % a->desc->block_size == 0); + ASSERT (a->desc != NULL || pg_ofs (b) == sizeof *a); + + return a; +} + +/* Returns the (IDX - 1)'th block within arena A. */ +static struct block * +arena_to_block (struct arena *a, size_t idx) { + ASSERT (a != NULL); + ASSERT (a->magic == ARENA_MAGIC); + ASSERT (idx < a->desc->blocks_per_arena); + return (struct block *) ((uint8_t *) a + + sizeof *a + + idx * a->desc->block_size); +} diff --git a/threads/mmu.c b/threads/mmu.c new file mode 100644 index 0000000..dbd305f --- /dev/null +++ b/threads/mmu.c @@ -0,0 +1,273 @@ +#include +#include +#include +#include "threads/init.h" +#include "threads/pte.h" +#include "threads/palloc.h" +#include "threads/mmu.h" +#include "intrinsic.h" + +static uint64_t * +pgdir_walk (uint64_t *pdp, const uint64_t va, int create) { + int idx = PDX (va); + if (pdp) { + uint64_t *pte = (uint64_t *) pdp[idx]; + if (!((uint64_t) pte & PTE_P)) { + if (create) { + uint64_t *new_page = palloc_get_page (PAL_ASSERT | PAL_ZERO); + if (new_page) { + pdp[idx] = vtop (new_page) | PTE_U | PTE_W | PTE_P; + } + } else + return NULL; + } + return (uint64_t *) ptov (PTE_ADDR (pdp[idx]) + 8 * PTX (va)); + } + return NULL; +} + +static uint64_t * +pdpe_walk (uint64_t *pdpe, const uint64_t va, int create) { + uint64_t *pte = NULL; + int idx = PDPE (va); + int allocated = 0; + if (pdpe) { + uint64_t *pde = (uint64_t *) pdpe[idx]; + if (!((uint64_t) pde & PTE_P)) { + if (create) { + uint64_t *new_page = palloc_get_page (PAL_ASSERT | PAL_ZERO); + if (new_page) { + pdpe[idx] = vtop (new_page) | PTE_U | PTE_W | PTE_P; + allocated = 1; + } + } else + return NULL; + } + pte = pgdir_walk (ptov (PTE_ADDR (pdpe[idx])), va, create); + } + if (pte == NULL && allocated) { + palloc_free_page ((void *) PTE_ADDR (pdpe[idx])); + pdpe[idx] = 0; + } + return pte; +} + +/* Returns the address of the page table entry for virtual + * address VADDR in page map level 4, pml4. + * If PML4E does not have a page table for VADDR, behavior depends + * on CREATE. If CREATE is true, then a new page table is + * created and a pointer into it is returned. Otherwise, a null + * pointer is returned. */ +uint64_t * +pml4e_walk (uint64_t *pml4e, const uint64_t va, int create) { + uint64_t *pte = NULL; + int idx = PML4 (va); + int allocated = 0; + if (pml4e) { + uint64_t *pdpe = (uint64_t *) pml4e[idx]; + if (!((uint64_t) pdpe & PTE_P)) { + if (create) { + uint64_t *new_page = palloc_get_page (PAL_ASSERT | PAL_ZERO); + if (new_page) { + pml4e[idx] = vtop (new_page) | PTE_U | PTE_W | PTE_P; + allocated = 1; + } + } else + return NULL; + } + pte = pdpe_walk (ptov (PTE_ADDR (pml4e[idx])), va, create); + } + if (pte == NULL && allocated) { + palloc_free_page ((void *) PTE_ADDR (pml4e[idx])); + pml4e[idx] = 0; + } + return pte; +} + +/* Creates a new page map level 4 (pml4) has mappings for kernel + * virtual addresses, but none for user virtual addresses. + * Returns the new page directory, or a null pointer if memory + * allocation fails. */ +uint64_t * +pml4_create (void) { + uint64_t *pml4 = palloc_get_page (0); + if (pml4) + memcpy (pml4, base_pml4, PGSIZE); + return pml4; +} + +static void +pgdir_for_each (uint64_t *pdp, pte_for_each_func *func, void *aux) { + for (unsigned i = 0; i < PGSIZE / sizeof(uint64_t *); i++) { + uint64_t *pte = ptov((uint64_t *) pdp[i]); + if (((uint64_t) pte) & PTE_P) + func (pte, aux); + } +} + +static void +pdpe_for_each (uint64_t *pdpe, pte_for_each_func *func, void *aux) { + for (unsigned i = 0; i < PGSIZE / sizeof(uint64_t *); i++) { + uint64_t *pde = ptov((uint64_t *) pdpe[i]); + if (((uint64_t) pde) & PTE_P) + pgdir_for_each ((uint64_t *) PTE_ADDR (pde), func, aux); + } +} + +/* Apply FUNC to each available pte entries including kernel's. */ +void pml4_for_each (uint64_t *pml4, pte_for_each_func *func, void *aux) { + for (unsigned i = 0; i < PGSIZE / sizeof(uint64_t *); i++) { + uint64_t *pdpe = ptov((uint64_t *) pml4[i]); + if (((uint64_t) pdpe) & PTE_P) + pdpe_for_each ((uint64_t *) PTE_ADDR (pdpe), func, aux); + } +} + +static void +pgdir_destroy (uint64_t *pdp) { + for (unsigned i = 0; i < PGSIZE / sizeof(uint64_t *); i++) { + uint64_t *pte = ptov((uint64_t *) pdp[i]); + if (((uint64_t) pte) & PTE_P) + palloc_free_page ((void *) PTE_ADDR (pte)); + } + palloc_free_page ((void *) PTE_ADDR (pdp)); +} + +static void +pdpe_destroy (uint64_t *pdpe) { + for (unsigned i = 0; i < PGSIZE / sizeof(uint64_t *); i++) { + uint64_t *pde = ptov((uint64_t *) pdpe[i]); + if (((uint64_t) pde) & PTE_P) + pgdir_destroy ((void *) PTE_ADDR (pde)); + } + palloc_free_page ((void *) PTE_ADDR (pdpe)); +} + +/* Destroys pml4e, freeing all the pages it references. */ +void +pml4_destroy (uint64_t *pml4) { + if (pml4 == NULL) + return; + ASSERT (pml4 != base_pml4); + + /* if PML4 (vaddr) >= 1, it's kernel space by define. */ + uint64_t *pdpe = ptov ((uint64_t *) pml4[0]); + if (((uint64_t) pdpe) & PTE_P) + pdpe_destroy ((void *) PTE_ADDR (pdpe)); + palloc_free_page ((void *) pml4); +} + +/* Loads page directory PD into the CPU's page directory base + * register. */ +void +pml4_activate (uint64_t *pml4) { + lcr3 (vtop (pml4 ? pml4 : base_pml4)); +} + +/* Looks up the physical address that corresponds to user virtual + * address UADDR in pml4. Returns the kernel virtual address + * corresponding to that physical address, or a null pointer if + * UADDR is unmapped. */ +void * +pml4_get_page (uint64_t *pml4, const void *uaddr) { + ASSERT (is_user_vaddr (uaddr)); + + uint64_t *pte = pml4e_walk (pml4, (uint64_t) uaddr, 0); + + if (pte && (*pte & PTE_P)) + return ptov (PTE_ADDR (*pte)) + pg_ofs (uaddr); + return NULL; +} + +/* Adds a mapping in page map level 4 PML4 from user virtual page + * UPAGE to the physical frame identified by kernel virtual address KPAGE. + * UPAGE must not already be mapped. KPAGE should probably be a page obtained + * from the user pool with palloc_get_page(). + * If WRITABLE is true, the new page is read/write; + * otherwise it is read-only. + * Returns true if successful, false if memory allocation + * failed. */ +bool +pml4_set_page (uint64_t *pml4, void *upage, void *kpage, bool rw) { + ASSERT (pg_ofs (upage) == 0); + ASSERT (pg_ofs (kpage) == 0); + ASSERT (is_user_vaddr (upage)); + ASSERT (pml4 != base_pml4); + + uint64_t *pte = pml4e_walk (pml4, (uint64_t) upage, 1); + + if (pte) + *pte = vtop (kpage) | PTE_P | (rw ? PTE_W : 0) | PTE_U; + return pte != NULL; +} + +/* Marks user virtual page UPAGE "not present" in page + * directory PD. Later accesses to the page will fault. Other + * bits in the page table entry are preserved. + * UPAGE need not be mapped. */ +void +pml4_clear_page (uint64_t *pml4, void *upage) { + uint64_t *pte; + ASSERT (pg_ofs (upage) == 0); + ASSERT (is_user_vaddr (upage)); + + pte = pml4e_walk (pml4, (uint64_t) upage, false); + + if (pte != NULL && (*pte & PTE_P) != 0) { + *pte &= ~PTE_P; + if (rcr3 () == vtop (pml4)) + invlpg ((uint64_t) upage); + } +} + +/* Returns true if the PTE for virtual page VPAGE in PML4 is dirty, + * that is, if the page has been modified since the PTE was + * installed. + * Returns false if PML4 contains no PTE for VPAGE. */ +bool +pml4_is_dirty (uint64_t *pml4, const void *vpage) { + uint64_t *pte = pml4e_walk (pml4, (uint64_t) vpage, false); + return pte != NULL && (*pte & PTE_D) != 0; +} + +/* Set the dirty bit to DIRTY in the PTE for virtual page VPAGE + * in PML4. */ +void +pml4_set_dirty (uint64_t *pml4, const void *vpage, bool dirty) { + uint64_t *pte = pml4e_walk (pml4, (uint64_t) vpage, false); + if (pte) { + if (dirty) + *pte |= PTE_D; + else + *pte &= ~(uint32_t) PTE_D; + + if (rcr3 () == vtop (pml4)) + invlpg ((uint64_t) vpage); + } +} + +/* Returns true if the PTE for virtual page VPAGE in PML4 has been + * accessed recently, that is, between the time the PTE was + * installed and the last time it was cleared. Returns false if + * PML4 contains no PTE for VPAGE. */ +bool +pml4_is_accessed (uint64_t *pml4, const void *vpage) { + uint64_t *pte = pml4e_walk (pml4, (uint64_t) vpage, false); + return pte != NULL && (*pte & PTE_A) != 0; +} + +/* Sets the accessed bit to ACCESSED in the PTE for virtual page + VPAGE in PD. */ +void +pml4_set_accessed (uint64_t *pml4, const void *vpage, bool accessed) { + uint64_t *pte = pml4e_walk (pml4, (uint64_t) vpage, false); + if (pte) { + if (accessed) + *pte |= PTE_A; + else + *pte &= ~(uint32_t) PTE_A; + + if (rcr3 () == vtop (pml4)) + invlpg ((uint64_t) vpage); + } +} diff --git a/threads/palloc.c b/threads/palloc.c new file mode 100644 index 0000000..59a9195 --- /dev/null +++ b/threads/palloc.c @@ -0,0 +1,359 @@ +#include "threads/palloc.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "threads/init.h" +#include "threads/loader.h" +#include "threads/synch.h" +#include "threads/vaddr.h" + +/* Page allocator. Hands out memory in page-size (or + page-multiple) chunks. See malloc.h for an allocator that + hands out smaller chunks. + + System memory is divided into two "pools" called the kernel + and user pools. The user pool is for user (virtual) memory + pages, the kernel pool for everything else. The idea here is + that the kernel needs to have memory for its own operations + even if user processes are swapping like mad. + + By default, half of system RAM is given to the kernel pool and + half to the user pool. That should be huge overkill for the + kernel pool, but that's just fine for demonstration purposes. */ + +/* A memory pool. */ +struct pool { + struct lock lock; /* Mutual exclusion. */ + struct bitmap *used_map; /* Bitmap of free pages. */ + uint8_t *base; /* Base of pool. */ +}; + +/* Two pools: one for kernel data, one for user pages. */ +static struct pool kernel_pool, user_pool; + +/* Maximum number of pages to put in user pool. */ +size_t user_page_limit = SIZE_MAX; +static void +init_pool (struct pool *p, void **bm_base, uint64_t start, uint64_t end); + +static bool page_from_pool (const struct pool *, void *page); + +/* multiboot info */ +struct multiboot_info { + uint32_t flags; + uint32_t mem_low; + uint32_t mem_high; + uint32_t __unused[8]; + uint32_t mmap_len; + uint32_t mmap_base; +}; + +/* e820 entry */ +struct e820_entry { + uint32_t size; + uint32_t mem_lo; + uint32_t mem_hi; + uint32_t len_lo; + uint32_t len_hi; + uint32_t type; +}; + +/* Represent the range information of the ext_mem/base_mem */ +struct area { + uint64_t start; + uint64_t end; + uint64_t size; +}; + +#define BASE_MEM_THRESHOLD 0x100000 +#define USABLE 1 +#define ACPI_RECLAIMABLE 2 +#define APPEND_HILO(hi, lo) (((uint64_t) ((hi)) << 32) + (lo)) + +/* Iterate on the e820 entry, parse the range of basemem and extmem. */ +static void +resolve_area_info (struct area *base_mem, struct area *ext_mem) { + struct multiboot_info *mb_info = ptov (MULTIBOOT_INFO); + struct e820_entry *entries = ptov (mb_info->mmap_base); + uint32_t i; + + for (i = 0; i < mb_info->mmap_len / sizeof (struct e820_entry); i++) { + struct e820_entry *entry = &entries[i]; + if (entry->type == ACPI_RECLAIMABLE || entry->type == USABLE || 1) { + uint64_t start = APPEND_HILO (entry->mem_hi, entry->mem_lo); + uint64_t size = APPEND_HILO (entry->len_hi, entry->len_lo); + uint64_t end = start + size; + + struct area *area = start < BASE_MEM_THRESHOLD ? base_mem : ext_mem; + + // First entry that belong to this area. + if (area->size == 0) { + *area = (struct area) { + .start = start, + .end = end, + .size = size, + }; + } else { // otherwise + // Extend start + if (area->start > start) + area->start = start; + // Extend end + if (area->end < end) + area->end = end; + // Extend size + area->size += size; + } + } + } +} + +/* + * Populate the pool. + * All the pages are manged by this allocator, even include code page. + * Basically, give half of memory to kernel, half to user. + * We push base_mem portion to the kernel as much as possible. + */ +static void +populate_pools (struct area *base_mem, struct area *ext_mem) { + extern char _end; + void *free_start = pg_round_up (&_end); + + uint64_t total_pages = (base_mem->size + ext_mem->size) / PGSIZE; + uint64_t user_pages = total_pages / 2 > user_page_limit ? + user_page_limit : total_pages / 2; + uint64_t kern_pages = total_pages - user_pages; + + // Parse E820 map to claim the memory region for each pool. + enum { KERN_START, KERN, USER_START, USER } state = KERN_START; + uint64_t rem = kern_pages; + uint64_t region_start = 0, end = 0, start, size, size_in_pg; + + struct multiboot_info *mb_info = ptov (MULTIBOOT_INFO); + struct e820_entry *entries = ptov (mb_info->mmap_base); + + uint32_t i; + for (i = 0; i < mb_info->mmap_len / sizeof (struct e820_entry); i++) { + struct e820_entry *entry = &entries[i]; + if (entry->type == ACPI_RECLAIMABLE || entry->type == USABLE) { + start = (uint64_t) ptov (APPEND_HILO (entry->mem_hi, entry->mem_lo)); + size = APPEND_HILO (entry->len_hi, entry->len_lo); + end = start + size; + size_in_pg = size / PGSIZE; + + if (state == KERN_START) { + region_start = start; + state = KERN; + } + + switch (state) { + case KERN: + if (rem > size_in_pg) { + rem -= size_in_pg; + break; + } + // generate kernel pool + init_pool (&kernel_pool, + &free_start, region_start, start + rem * PGSIZE); + // Transition to the next state + if (rem == size_in_pg) { + rem = user_pages; + state = USER_START; + } else { + region_start = start + rem * PGSIZE; + rem = user_pages - size_in_pg + rem; + state = USER; + } + break; + case USER_START: + region_start = start; + state = USER; + break; + case USER: + if (rem > size_in_pg) { + rem -= size_in_pg; + break; + } + ASSERT (rem == size); + break; + default: + NOT_REACHED (); + } + } + } + + // generate the user pool + init_pool(&user_pool, &free_start, region_start, end); + + // Iterate over the e820_entry. Setup the usable. + uint64_t usable_bound = (uint64_t) free_start; + struct pool *pool; + void *pool_end; + size_t page_idx, page_cnt; + + for (i = 0; i < mb_info->mmap_len / sizeof (struct e820_entry); i++) { + struct e820_entry *entry = &entries[i]; + if (entry->type == ACPI_RECLAIMABLE || entry->type == USABLE) { + uint64_t start = (uint64_t) + ptov (APPEND_HILO (entry->mem_hi, entry->mem_lo)); + uint64_t size = APPEND_HILO (entry->len_hi, entry->len_lo); + uint64_t end = start + size; + + // TODO: add 0x1000 ~ 0x200000, This is not a matter for now. + // All the pages are unuable + if (end < usable_bound) + continue; + + start = (uint64_t) + pg_round_up (start >= usable_bound ? start : usable_bound); +split: + if (page_from_pool (&kernel_pool, (void *) start)) + pool = &kernel_pool; + else if (page_from_pool (&user_pool, (void *) start)) + pool = &user_pool; + else + NOT_REACHED (); + + pool_end = pool->base + bitmap_size (pool->used_map) * PGSIZE; + page_idx = pg_no (start) - pg_no (pool->base); + if ((uint64_t) pool_end < end) { + page_cnt = ((uint64_t) pool_end - start) / PGSIZE; + bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false); + start = (uint64_t) pool_end; + goto split; + } else { + page_cnt = ((uint64_t) end - start) / PGSIZE; + bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false); + } + } + } +} + +/* Initializes the page allocator and get the memory size */ +uint64_t +palloc_init (void) { + /* End of the kernel as recorded by the linker. + See kernel.lds.S. */ + extern char _end; + struct area base_mem = { .size = 0 }; + struct area ext_mem = { .size = 0 }; + + resolve_area_info (&base_mem, &ext_mem); + printf ("Pintos booting with: \n"); + printf ("\tbase_mem: 0x%llx ~ 0x%llx (Usable: %'llu kB)\n", + base_mem.start, base_mem.end, base_mem.size / 1024); + printf ("\text_mem: 0x%llx ~ 0x%llx (Usable: %'llu kB)\n", + ext_mem.start, ext_mem.end, ext_mem.size / 1024); + populate_pools (&base_mem, &ext_mem); + return ext_mem.end; +} + +/* Obtains and returns a group of PAGE_CNT contiguous free pages. + If PAL_USER is set, the pages are obtained from the user pool, + otherwise from the kernel pool. If PAL_ZERO is set in FLAGS, + then the pages are filled with zeros. If too few pages are + available, returns a null pointer, unless PAL_ASSERT is set in + FLAGS, in which case the kernel panics. */ +void * +palloc_get_multiple (enum palloc_flags flags, size_t page_cnt) { + struct pool *pool = flags & PAL_USER ? &user_pool : &kernel_pool; + + lock_acquire (&pool->lock); + size_t page_idx = bitmap_scan_and_flip (pool->used_map, 0, page_cnt, false); + lock_release (&pool->lock); + void *pages; + + if (page_idx != BITMAP_ERROR) + pages = pool->base + PGSIZE * page_idx; + else + pages = NULL; + + if (pages) { + if (flags & PAL_ZERO) + memset (pages, 0, PGSIZE * page_cnt); + } else { + if (flags & PAL_ASSERT) + PANIC ("palloc_get: out of pages"); + } + + return pages; +} + +/* Obtains a single free page and returns its kernel virtual + address. + If PAL_USER is set, the page is obtained from the user pool, + otherwise from the kernel pool. If PAL_ZERO is set in FLAGS, + then the page is filled with zeros. If no pages are + available, returns a null pointer, unless PAL_ASSERT is set in + FLAGS, in which case the kernel panics. */ +void * +palloc_get_page (enum palloc_flags flags) { + return palloc_get_multiple (flags, 1); +} + +/* Frees the PAGE_CNT pages starting at PAGES. */ +void +palloc_free_multiple (void *pages, size_t page_cnt) { + struct pool *pool; + size_t page_idx; + + ASSERT (pg_ofs (pages) == 0); + if (pages == NULL || page_cnt == 0) + return; + + if (page_from_pool (&kernel_pool, pages)) + pool = &kernel_pool; + else if (page_from_pool (&user_pool, pages)) + pool = &user_pool; + else + NOT_REACHED (); + + page_idx = pg_no (pages) - pg_no (pool->base); + +#ifndef NDEBUG + memset (pages, 0xcc, PGSIZE * page_cnt); +#endif + lock_acquire (&pool->lock); + ASSERT (bitmap_all (pool->used_map, page_idx, page_cnt)); + bitmap_set_multiple (pool->used_map, page_idx, page_cnt, false); + lock_release (&pool->lock); +} + +/* Frees the page at PAGE. */ +void +palloc_free_page (void *page) { + palloc_free_multiple (page, 1); +} + +/* Initializes pool P as starting at START and ending at END */ +static void +init_pool (struct pool *p, void **bm_base, uint64_t start, uint64_t end) { + /* We'll put the pool's used_map at its base. + Calculate the space needed for the bitmap + and subtract it from the pool's size. */ + uint64_t pgcnt = (end - start) / PGSIZE; + size_t bm_pages = DIV_ROUND_UP (bitmap_buf_size (pgcnt), PGSIZE) * PGSIZE; + + lock_init(&p->lock); + p->used_map = bitmap_create_in_buf (pgcnt, *bm_base, bm_pages); + p->base = (void *) start; + + // Mark all to unusable. + bitmap_set_all(p->used_map, true); + + *bm_base += bm_pages; +} + +/* Returns true if PAGE was allocated from POOL, + false otherwise. */ +static bool +page_from_pool (const struct pool *pool, void *page) { + size_t page_no = pg_no (page); + size_t start_page = pg_no (pool->base); + size_t end_page = start_page + bitmap_size (pool->used_map); + return page_no >= start_page && page_no < end_page; +} diff --git a/threads/start.S b/threads/start.S new file mode 100644 index 0000000..470498d --- /dev/null +++ b/threads/start.S @@ -0,0 +1,152 @@ +#include "threads/loader.h" +#define LONG_MODE (1 << 29) +#define CR0_PE 0x00000001 +#define CR0_PG (1 << 31) +#define CR4_PAE 0x20 +#define PTE_P 0x1 +#define PTE_W 0x2 +#define EFER_MSR 0xC0000080 +#define EFER_LME (1 << 8) +#define EFER_SCE (1 << 0) +#define RELOC(x) (x - LOADER_KERN_BASE) +.section .entry + +.globl _start +_start = RELOC(bootstrap) + +.globl bootstrap +.func bootstrap +.code32 +#### bootstrap to the 64bit code. +bootstrap: + pushf + pop %eax + mov %ecx, %eax + xor $0x200000, %eax + push %eax + popf + cmp %eax, %ebx + jz no_long_mode # Check cpuid instruction exist. + xor %eax, %eax + cpuid # query cpuid 1. + cmp $1, %eax + jb no_long_mode + test $LONG_MODE, %edx +#### Enable Physical Address Extension + movl %cr4, %eax + orl $CR4_PAE, %eax + movl %eax, %cr4 + + +#### Create page directory and page table and +#### set page directory base register (cr3). +setup_page_table: +# 1. fill boot_pml4e with zeros + lea (RELOC(boot_pml4e)), %edi + xor %eax, %eax + mov $0x400, %ecx + rep stosl (%edi) + +# 2. set pdpts + lea (RELOC(boot_pml4e)), %edi + lea (RELOC(boot_pdpt1)), %ebx + orl $(PTE_P | PTE_W), %ebx + mov %ebx, (%edi) # pdpt1 + lea (RELOC(boot_pdpt2)), %ebx + orl $(PTE_P | PTE_W), %ebx + mov %ebx, 8(%edi) # pdpt2 + +# 3. set pdpes + lea (RELOC(boot_pdpt1)), %edi + lea (RELOC(boot_pde1)), %ebx + orl $(PTE_P | PTE_W), %ebx + mov %ebx, (%edi) + + lea (RELOC(boot_pdpt2)), %edi + lea (RELOC(boot_pde2)), %ebx + orl $(PTE_P | PTE_W), %ebx + mov %ebx, (%edi) + +# 4. setup pdes + mov $128, %ecx + lea (RELOC(boot_pde1)), %ebx + lea (RELOC(boot_pde2)), %edx + add $256, %edx + mov $(PTE_P | PTE_W | 0x180), %eax + +fill_pdes: + mov %eax, (%ebx) + mov %eax, (%edx) + add $8, %ebx + add $8, %edx + add $0x200000, %eax + dec %ecx + cmp $0, %ecx + jne fill_pdes + +# 5. Load page directory base register (cr3). + lea (RELOC(boot_pml4e)), %eax + mov %eax, %cr3 + +#### Enable the long mode using MSR (Model Specific Register) +#### Enable syscall (EFER_SCE) + mov $EFER_MSR, %ecx + rdmsr + orl $(EFER_LME | EFER_SCE), %eax + wrmsr + +#### Enable paging + mov %cr0, %eax + or $(CR0_PE|CR0_PG), %eax + mov %eax, %cr0 + +#### Jump to the long mode + lea (RELOC(gdt_desc64)), %eax + lgdt (%eax) + mov $(entry_64 - LOADER_KERN_BASE), %eax + push $SEL_KCSEG + push %eax + lret +.endfunc + +no_long_mode: + jmp no_long_mode + +.p2align 2 +gdt64: + .quad 0 # NULL SEGMENT + .quad 0x00af9a000000ffff # CODE SEGMENT64 + .quad 0x00af92000000ffff # DATA SEGMENT64 +gdt_desc64: + .word 0x17 + .quad RELOC(gdt64) + +.p2align 12 +.globl boot_pml4e +.globl boot_pdpt1 +.globl boot_pdpt2 +.globl boot_pde1 +.globl boot_pde2 + +boot_pml4e: + .space 0x1000 +boot_pdpt1: + .space 0x1000 +boot_pdpt2: + .space 0x1000 +boot_pde1: + .space 0x1000 +boot_pde2: + .space 0x1000 + +.section .text +.code64 +.globl entry_64 +.func entry_64 +entry_64: + #### We will use 0 ~ 0x1000 as boot stack. + xor %rbp, %rbp + movabs $(LOADER_KERN_BASE + 0x1000), %rsp + movabs $main, %rax + call *%rax +.endfunc diff --git a/threads/synch.c b/threads/synch.c new file mode 100644 index 0000000..8ca3230 --- /dev/null +++ b/threads/synch.c @@ -0,0 +1,323 @@ +/* This file is derived from source code for the Nachos + instructional operating system. The Nachos copyright notice + is reproduced in full below. */ + +/* Copyright (c) 1992-1996 The Regents of the University of California. + All rights reserved. + + Permission to use, copy, modify, and distribute this software + and its documentation for any purpose, without fee, and + without written agreement is hereby granted, provided that the + above copyright notice and the following two paragraphs appear + in all copies of this software. + + IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO + ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE + AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA + HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY + WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" + BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO + PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR + MODIFICATIONS. + */ + +#include "threads/synch.h" +#include +#include +#include "threads/interrupt.h" +#include "threads/thread.h" + +/* Initializes semaphore SEMA to VALUE. A semaphore is a + nonnegative integer along with two atomic operators for + manipulating it: + + - down or "P": wait for the value to become positive, then + decrement it. + + - up or "V": increment the value (and wake up one waiting + thread, if any). */ +void +sema_init (struct semaphore *sema, unsigned value) { + ASSERT (sema != NULL); + + sema->value = value; + list_init (&sema->waiters); +} + +/* Down or "P" operation on a semaphore. Waits for SEMA's value + to become positive and then atomically decrements it. + + This function may sleep, so it must not be called within an + interrupt handler. This function may be called with + interrupts disabled, but if it sleeps then the next scheduled + thread will probably turn interrupts back on. This is + sema_down function. */ +void +sema_down (struct semaphore *sema) { + enum intr_level old_level; + + ASSERT (sema != NULL); + ASSERT (!intr_context ()); + + old_level = intr_disable (); + while (sema->value == 0) { + list_push_back (&sema->waiters, &thread_current ()->elem); + thread_block (); + } + sema->value--; + intr_set_level (old_level); +} + +/* Down or "P" operation on a semaphore, but only if the + semaphore is not already 0. Returns true if the semaphore is + decremented, false otherwise. + + This function may be called from an interrupt handler. */ +bool +sema_try_down (struct semaphore *sema) { + enum intr_level old_level; + bool success; + + ASSERT (sema != NULL); + + old_level = intr_disable (); + if (sema->value > 0) + { + sema->value--; + success = true; + } + else + success = false; + intr_set_level (old_level); + + return success; +} + +/* Up or "V" operation on a semaphore. Increments SEMA's value + and wakes up one thread of those waiting for SEMA, if any. + + This function may be called from an interrupt handler. */ +void +sema_up (struct semaphore *sema) { + enum intr_level old_level; + + ASSERT (sema != NULL); + + old_level = intr_disable (); + if (!list_empty (&sema->waiters)) + thread_unblock (list_entry (list_pop_front (&sema->waiters), + struct thread, elem)); + sema->value++; + intr_set_level (old_level); +} + +static void sema_test_helper (void *sema_); + +/* Self-test for semaphores that makes control "ping-pong" + between a pair of threads. Insert calls to printf() to see + what's going on. */ +void +sema_self_test (void) { + struct semaphore sema[2]; + int i; + + printf ("Testing semaphores..."); + sema_init (&sema[0], 0); + sema_init (&sema[1], 0); + thread_create ("sema-test", PRI_DEFAULT, sema_test_helper, &sema); + for (i = 0; i < 10; i++) + { + sema_up (&sema[0]); + sema_down (&sema[1]); + } + printf ("done.\n"); +} + +/* Thread function used by sema_self_test(). */ +static void +sema_test_helper (void *sema_) { + struct semaphore *sema = sema_; + int i; + + for (i = 0; i < 10; i++) + { + sema_down (&sema[0]); + sema_up (&sema[1]); + } +} + +/* Initializes LOCK. A lock can be held by at most a single + thread at any given time. Our locks are not "recursive", that + is, it is an error for the thread currently holding a lock to + try to acquire that lock. + + A lock is a specialization of a semaphore with an initial + value of 1. The difference between a lock and such a + semaphore is twofold. First, a semaphore can have a value + greater than 1, but a lock can only be owned by a single + thread at a time. Second, a semaphore does not have an owner, + meaning that one thread can "down" the semaphore and then + another one "up" it, but with a lock the same thread must both + acquire and release it. When these restrictions prove + onerous, it's a good sign that a semaphore should be used, + instead of a lock. */ +void +lock_init (struct lock *lock) { + ASSERT (lock != NULL); + + lock->holder = NULL; + sema_init (&lock->semaphore, 1); +} + +/* Acquires LOCK, sleeping until it becomes available if + necessary. The lock must not already be held by the current + thread. + + This function may sleep, so it must not be called within an + interrupt handler. This function may be called with + interrupts disabled, but interrupts will be turned back on if + we need to sleep. */ +void +lock_acquire (struct lock *lock) { + ASSERT (lock != NULL); + ASSERT (!intr_context ()); + ASSERT (!lock_held_by_current_thread (lock)); + + sema_down (&lock->semaphore); + lock->holder = thread_current (); +} + +/* Tries to acquires LOCK and returns true if successful or false + on failure. The lock must not already be held by the current + thread. + + This function will not sleep, so it may be called within an + interrupt handler. */ +bool +lock_try_acquire (struct lock *lock) { + bool success; + + ASSERT (lock != NULL); + ASSERT (!lock_held_by_current_thread (lock)); + + success = sema_try_down (&lock->semaphore); + if (success) + lock->holder = thread_current (); + return success; +} + +/* Releases LOCK, which must be owned by the current thread. + This is lock_release function. + + An interrupt handler cannot acquire a lock, so it does not + make sense to try to release a lock within an interrupt + handler. */ +void +lock_release (struct lock *lock) { + ASSERT (lock != NULL); + ASSERT (lock_held_by_current_thread (lock)); + + lock->holder = NULL; + sema_up (&lock->semaphore); +} + +/* Returns true if the current thread holds LOCK, false + otherwise. (Note that testing whether some other thread holds + a lock would be racy.) */ +bool +lock_held_by_current_thread (const struct lock *lock) { + ASSERT (lock != NULL); + + return lock->holder == thread_current (); +} + +/* One semaphore in a list. */ +struct semaphore_elem { + struct list_elem elem; /* List element. */ + struct semaphore semaphore; /* This semaphore. */ +}; + +/* Initializes condition variable COND. A condition variable + allows one piece of code to signal a condition and cooperating + code to receive the signal and act upon it. */ +void +cond_init (struct condition *cond) { + ASSERT (cond != NULL); + + list_init (&cond->waiters); +} + +/* Atomically releases LOCK and waits for COND to be signaled by + some other piece of code. After COND is signaled, LOCK is + reacquired before returning. LOCK must be held before calling + this function. + + The monitor implemented by this function is "Mesa" style, not + "Hoare" style, that is, sending and receiving a signal are not + an atomic operation. Thus, typically the caller must recheck + the condition after the wait completes and, if necessary, wait + again. + + A given condition variable is associated with only a single + lock, but one lock may be associated with any number of + condition variables. That is, there is a one-to-many mapping + from locks to condition variables. + + This function may sleep, so it must not be called within an + interrupt handler. This function may be called with + interrupts disabled, but interrupts will be turned back on if + we need to sleep. */ +void +cond_wait (struct condition *cond, struct lock *lock) { + struct semaphore_elem waiter; + + ASSERT (cond != NULL); + ASSERT (lock != NULL); + ASSERT (!intr_context ()); + ASSERT (lock_held_by_current_thread (lock)); + + sema_init (&waiter.semaphore, 0); + list_push_back (&cond->waiters, &waiter.elem); + lock_release (lock); + sema_down (&waiter.semaphore); + lock_acquire (lock); +} + +/* If any threads are waiting on COND (protected by LOCK), then + this function signals one of them to wake up from its wait. + LOCK must be held before calling this function. + + An interrupt handler cannot acquire a lock, so it does not + make sense to try to signal a condition variable within an + interrupt handler. */ +void +cond_signal (struct condition *cond, struct lock *lock UNUSED) { + ASSERT (cond != NULL); + ASSERT (lock != NULL); + ASSERT (!intr_context ()); + ASSERT (lock_held_by_current_thread (lock)); + + if (!list_empty (&cond->waiters)) + sema_up (&list_entry (list_pop_front (&cond->waiters), + struct semaphore_elem, elem)->semaphore); +} + +/* Wakes up all threads, if any, waiting on COND (protected by + LOCK). LOCK must be held before calling this function. + + An interrupt handler cannot acquire a lock, so it does not + make sense to try to signal a condition variable within an + interrupt handler. */ +void +cond_broadcast (struct condition *cond, struct lock *lock) { + ASSERT (cond != NULL); + ASSERT (lock != NULL); + + while (!list_empty (&cond->waiters)) + cond_signal (cond, lock); +} diff --git a/threads/targets.mk b/threads/targets.mk new file mode 100644 index 0000000..82abda4 --- /dev/null +++ b/threads/targets.mk @@ -0,0 +1,9 @@ +threads_SRC = threads/init.c # Main program. +threads_SRC += threads/thread.c # Thread management core. +threads_SRC += threads/interrupt.c # Interrupt core. +threads_SRC += threads/intr-stubs.S # Interrupt stubs. +threads_SRC += threads/synch.c # Synchronization. +threads_SRC += threads/palloc.c # Page allocator. +threads_SRC += threads/malloc.c # Subpage allocator. +threads_SRC += threads/start.S # Startup code. +threads_SRC += threads/mmu.c # Memory management unit related things. diff --git a/threads/thread.c b/threads/thread.c new file mode 100644 index 0000000..c38b57d --- /dev/null +++ b/threads/thread.c @@ -0,0 +1,590 @@ +#include "threads/thread.h" +#include +#include +#include +#include +#include +#include "threads/flags.h" +#include "threads/interrupt.h" +#include "threads/intr-stubs.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/vaddr.h" +#include "intrinsic.h" +#ifdef USERPROG +#include "userprog/process.h" +#endif + +/* Random value for struct thread's `magic' member. + Used to detect stack overflow. See the big comment at the top + of thread.h for details. */ +#define THREAD_MAGIC 0xcd6abf4b + +/* Random value for basic thread + Do not modify this value. */ +#define THREAD_BASIC 0xd42df210 + +/* List of processes in THREAD_READY state, that is, processes + that are ready to run but not actually running. */ +static struct list ready_list; + +/* Idle thread. */ +static struct thread *idle_thread; + +/* Initial thread, the thread running init.c:main(). */ +static struct thread *initial_thread; + +/* Lock used by allocate_tid(). */ +static struct lock tid_lock; + +/* Thread destruction requests */ +static struct list destruction_req; + +/* Statistics. */ +static long long idle_ticks; /* # of timer ticks spent idle. */ +static long long kernel_ticks; /* # of timer ticks in kernel threads. */ +static long long user_ticks; /* # of timer ticks in user programs. */ + +/* Scheduling. */ +#define TIME_SLICE 4 /* # of timer ticks to give each thread. */ +static unsigned thread_ticks; /* # of timer ticks since last yield. */ + +/* If false (default), use round-robin scheduler. + If true, use multi-level feedback queue scheduler. + Controlled by kernel command-line option "-o mlfqs". */ +bool thread_mlfqs; + +static void kernel_thread (thread_func *, void *aux); + +static void idle (void *aux UNUSED); +static struct thread *next_thread_to_run (void); +static void init_thread (struct thread *, const char *name, int priority); +static void do_schedule(int status); +static void schedule (void); +static tid_t allocate_tid (void); + +/* Returns true if T appears to point to a valid thread. */ +#define is_thread(t) ((t) != NULL && (t)->magic == THREAD_MAGIC) + +/* Returns the running thread. + * Read the CPU's stack pointer `rsp', and then round that + * down to the start of a page. Since `struct thread' is + * always at the beginning of a page and the stack pointer is + * somewhere in the middle, this locates the curent thread. */ +#define running_thread() ((struct thread *) (pg_round_down (rrsp ()))) + + +// Global descriptor table for the thread_start. +// Because the gdt will be setup after the thread_init, we should +// setup temporal gdt first. +static uint64_t gdt[3] = { 0, 0x00af9a000000ffff, 0x00cf92000000ffff }; + +/* Initializes the threading system by transforming the code + that's currently running into a thread. This can't work in + general and it is possible in this case only because loader.S + was careful to put the bottom of the stack at a page boundary. + + Also initializes the run queue and the tid lock. + + After calling this function, be sure to initialize the page + allocator before trying to create any threads with + thread_create(). + + It is not safe to call thread_current() until this function + finishes. */ +void +thread_init (void) { + ASSERT (intr_get_level () == INTR_OFF); + + /* Reload the temporal gdt for the kernel + * This gdt does not include the user context. + * The kernel will rebuild the gdt with user context, in gdt_init (). */ + struct desc_ptr gdt_ds = { + .size = sizeof (gdt) - 1, + .address = (uint64_t) gdt + }; + lgdt (&gdt_ds); + + /* Init the globla thread context */ + lock_init (&tid_lock); + list_init (&ready_list); + list_init (&destruction_req); + + /* Set up a thread structure for the running thread. */ + initial_thread = running_thread (); + init_thread (initial_thread, "main", PRI_DEFAULT); + initial_thread->status = THREAD_RUNNING; + initial_thread->tid = allocate_tid (); +} + +/* Starts preemptive thread scheduling by enabling interrupts. + Also creates the idle thread. */ +void +thread_start (void) { + /* Create the idle thread. */ + struct semaphore idle_started; + sema_init (&idle_started, 0); + thread_create ("idle", PRI_MIN, idle, &idle_started); + + /* Start preemptive thread scheduling. */ + intr_enable (); + + /* Wait for the idle thread to initialize idle_thread. */ + sema_down (&idle_started); +} + +/* Called by the timer interrupt handler at each timer tick. + Thus, this function runs in an external interrupt context. */ +void +thread_tick (void) { + struct thread *t = thread_current (); + + /* Update statistics. */ + if (t == idle_thread) + idle_ticks++; +#ifdef USERPROG + else if (t->pml4 != NULL) + user_ticks++; +#endif + else + kernel_ticks++; + + /* Enforce preemption. */ + if (++thread_ticks >= TIME_SLICE) + intr_yield_on_return (); +} + +/* Prints thread statistics. */ +void +thread_print_stats (void) { + printf ("Thread: %lld idle ticks, %lld kernel ticks, %lld user ticks\n", + idle_ticks, kernel_ticks, user_ticks); +} + +/* Creates a new kernel thread named NAME with the given initial + PRIORITY, which executes FUNCTION passing AUX as the argument, + and adds it to the ready queue. Returns the thread identifier + for the new thread, or TID_ERROR if creation fails. + + If thread_start() has been called, then the new thread may be + scheduled before thread_create() returns. It could even exit + before thread_create() returns. Contrariwise, the original + thread may run for any amount of time before the new thread is + scheduled. Use a semaphore or some other form of + synchronization if you need to ensure ordering. + + The code provided sets the new thread's `priority' member to + PRIORITY, but no actual priority scheduling is implemented. + Priority scheduling is the goal of Problem 1-3. */ +tid_t +thread_create (const char *name, int priority, + thread_func *function, void *aux) { + struct thread *t; + tid_t tid; + + ASSERT (function != NULL); + + /* Allocate thread. */ + t = palloc_get_page (PAL_ZERO); + if (t == NULL) + return TID_ERROR; + + /* Initialize thread. */ + init_thread (t, name, priority); + tid = t->tid = allocate_tid (); + + /* Call the kernel_thread if it scheduled. + * Note) rdi is 1st argument, and rsi is 2nd argument. */ + t->tf.rip = (uintptr_t) kernel_thread; + t->tf.R.rdi = (uint64_t) function; + t->tf.R.rsi = (uint64_t) aux; + t->tf.ds = SEL_KDSEG; + t->tf.es = SEL_KDSEG; + t->tf.ss = SEL_KDSEG; + t->tf.cs = SEL_KCSEG; + t->tf.eflags = FLAG_IF; + + /* Add to run queue. */ + thread_unblock (t); + + return tid; +} + +/* Puts the current thread to sleep. It will not be scheduled + again until awoken by thread_unblock(). + + This function must be called with interrupts turned off. It + is usually a better idea to use one of the synchronization + primitives in synch.h. */ +void +thread_block (void) { + ASSERT (!intr_context ()); + ASSERT (intr_get_level () == INTR_OFF); + thread_current ()->status = THREAD_BLOCKED; + schedule (); +} + +/* Transitions a blocked thread T to the ready-to-run state. + This is an error if T is not blocked. (Use thread_yield() to + make the running thread ready.) + + This function does not preempt the running thread. This can + be important: if the caller had disabled interrupts itself, + it may expect that it can atomically unblock a thread and + update other data. */ +void +thread_unblock (struct thread *t) { + enum intr_level old_level; + + ASSERT (is_thread (t)); + + old_level = intr_disable (); + ASSERT (t->status == THREAD_BLOCKED); + list_push_back (&ready_list, &t->elem); + t->status = THREAD_READY; + intr_set_level (old_level); +} + +/* Returns the name of the running thread. */ +const char * +thread_name (void) { + return thread_current ()->name; +} + +/* Returns the running thread. + This is running_thread() plus a couple of sanity checks. + See the big comment at the top of thread.h for details. */ +struct thread * +thread_current (void) { + struct thread *t = running_thread (); + + /* Make sure T is really a thread. + If either of these assertions fire, then your thread may + have overflowed its stack. Each thread has less than 4 kB + of stack, so a few big automatic arrays or moderate + recursion can cause stack overflow. */ + ASSERT (is_thread (t)); + ASSERT (t->status == THREAD_RUNNING); + + return t; +} + +/* Returns the running thread's tid. */ +tid_t +thread_tid (void) { + return thread_current ()->tid; +} + +/* Deschedules the current thread and destroys it. Never + returns to the caller. */ +void +thread_exit (void) { + ASSERT (!intr_context ()); + +#ifdef USERPROG + process_cleanup (); +#endif + + /* Just set our status to dying and schedule another process. + We will be destroyed during the call to schedule_tail(). */ + intr_disable (); + do_schedule (THREAD_DYING); + NOT_REACHED (); +} + +/* Yields the CPU. The current thread is not put to sleep and + may be scheduled again immediately at the scheduler's whim. */ +void +thread_yield (void) { + struct thread *curr = thread_current (); + enum intr_level old_level; + + ASSERT (!intr_context ()); + + old_level = intr_disable (); + if (curr != idle_thread) + list_push_back (&ready_list, &curr->elem); + do_schedule (THREAD_READY); + intr_set_level (old_level); +} + +/* Sets the current thread's priority to NEW_PRIORITY. */ +void +thread_set_priority (int new_priority) { + thread_current ()->priority = new_priority; +} + +/* Returns the current thread's priority. */ +int +thread_get_priority (void) { + return thread_current ()->priority; +} + +/* Sets the current thread's nice value to NICE. */ +void +thread_set_nice (int nice UNUSED) { + /* TODO: Your implementation goes here */ +} + +/* Returns the current thread's nice value. */ +int +thread_get_nice (void) { + /* TODO: Your implementation goes here */ + return 0; +} + +/* Returns 100 times the system load average. */ +int +thread_get_load_avg (void) { + /* TODO: Your implementation goes here */ + return 0; +} + +/* Returns 100 times the current thread's recent_cpu value. */ +int +thread_get_recent_cpu (void) { + /* TODO: Your implementation goes here */ + return 0; +} + +/* Idle thread. Executes when no other thread is ready to run. + + The idle thread is initially put on the ready list by + thread_start(). It will be scheduled once initially, at which + point it initializes idle_thread, "up"s the semaphore passed + to it to enable thread_start() to continue, and immediately + blocks. After that, the idle thread never appears in the + ready list. It is returned by next_thread_to_run() as a + special case when the ready list is empty. */ +static void +idle (void *idle_started_ UNUSED) { + struct semaphore *idle_started = idle_started_; + + idle_thread = thread_current (); + sema_up (idle_started); + + for (;;) { + /* Let someone else run. */ + intr_disable (); + thread_block (); + + /* Re-enable interrupts and wait for the next one. + + The `sti' instruction disables interrupts until the + completion of the next instruction, so these two + instructions are executed atomically. This atomicity is + important; otherwise, an interrupt could be handled + between re-enabling interrupts and waiting for the next + one to occur, wasting as much as one clock tick worth of + time. + + See [IA32-v2a] "HLT", [IA32-v2b] "STI", and [IA32-v3a] + 7.11.1 "HLT Instruction". */ + asm volatile ("sti; hlt" : : : "memory"); + } +} + +/* Function used as the basis for a kernel thread. */ +static void +kernel_thread (thread_func *function, void *aux) { + ASSERT (function != NULL); + + intr_enable (); /* The scheduler runs with interrupts off. */ + function (aux); /* Execute the thread function. */ + thread_exit (); /* If function() returns, kill the thread. */ +} + + +/* Does basic initialization of T as a blocked thread named + NAME. */ +static void +init_thread (struct thread *t, const char *name, int priority) { + ASSERT (t != NULL); + ASSERT (PRI_MIN <= priority && priority <= PRI_MAX); + ASSERT (name != NULL); + + memset (t, 0, sizeof *t); + t->status = THREAD_BLOCKED; + strlcpy (t->name, name, sizeof t->name); + t->tf.rsp = (uint64_t) t + PGSIZE - sizeof (void *); + t->priority = priority; + t->magic = THREAD_MAGIC; +} + +/* Chooses and returns the next thread to be scheduled. Should + return a thread from the run queue, unless the run queue is + empty. (If the running thread can continue running, then it + will be in the run queue.) If the run queue is empty, return + idle_thread. */ +static struct thread * +next_thread_to_run (void) { + if (list_empty (&ready_list)) + return idle_thread; + else + return list_entry (list_pop_front (&ready_list), struct thread, elem); +} + +/* Use iretq to launch the thread */ +void +do_iret (struct intr_frame *tf) { + __asm __volatile( + "movq %0, %%rsp\n" + "movq 0(%%rsp),%%r15\n" + "movq 8(%%rsp),%%r14\n" + "movq 16(%%rsp),%%r13\n" + "movq 24(%%rsp),%%r12\n" + "movq 32(%%rsp),%%r11\n" + "movq 40(%%rsp),%%r10\n" + "movq 48(%%rsp),%%r9\n" + "movq 56(%%rsp),%%r8\n" + "movq 64(%%rsp),%%rsi\n" + "movq 72(%%rsp),%%rdi\n" + "movq 80(%%rsp),%%rbp\n" + "movq 88(%%rsp),%%rdx\n" + "movq 96(%%rsp),%%rcx\n" + "movq 104(%%rsp),%%rbx\n" + "movq 112(%%rsp),%%rax\n" + "addq $120,%%rsp\n" + "movw 8(%%rsp),%%ds\n" + "movw (%%rsp),%%es\n" + "addq $32, %%rsp\n" + "iretq" + : : "g" ((uint64_t) tf) : "memory"); +} + +/* Switching the thread by activating the new thread's page + tables, and, if the previous thread is dying, destroying it. + + At this function's invocation, we just switched from thread + PREV, the new thread is already running, and interrupts are + still disabled. + + It's not safe to call printf() until the thread switch is + complete. In practice that means that printf()s should be + added at the end of the function. */ +static void +thread_launch (struct thread *th) { + uint64_t tf_cur = (uint64_t) &running_thread ()->tf; + uint64_t tf = (uint64_t) &th->tf; + ASSERT (intr_get_level () == INTR_OFF); + + /* The main switching logic. + * We first restore the whole execution context into the intr_frame + * and then switching to the next thread by calling do_iret. + * Note that, we SHOULD NOT use any stack from here + * until switching is done. */ + __asm __volatile ( + /* Store registers that will be used. */ + "push %%rax\n" + "push %%rbx\n" + "push %%rcx\n" + /* Fetch input once */ + "movq %0, %%rax\n" + "movq %1, %%rcx\n" + "movq %%r15, 0(%%rax)\n" + "movq %%r14, 8(%%rax)\n" + "movq %%r13, 16(%%rax)\n" + "movq %%r12, 24(%%rax)\n" + "movq %%r11, 32(%%rax)\n" + "movq %%r10, 40(%%rax)\n" + "movq %%r9, 48(%%rax)\n" + "movq %%r8, 56(%%rax)\n" + "movq %%rsi, 64(%%rax)\n" + "movq %%rdi, 72(%%rax)\n" + "movq %%rbp, 80(%%rax)\n" + "movq %%rdx, 88(%%rax)\n" + "pop %%rbx\n" // Saved rcx + "movq %%rbx, 96(%%rax)\n" + "pop %%rbx\n" // Saved rbx + "movq %%rbx, 104(%%rax)\n" + "pop %%rbx\n" // Saved rax + "movq %%rbx, 112(%%rax)\n" + "addq $120, %%rax\n" + "movw %%es, (%%rax)\n" + "movw %%ds, 8(%%rax)\n" + "addq $32, %%rax\n" + "call __next\n" // read the current rip. + "__next:\n" + "pop %%rbx\n" + "addq $(out_iret - __next), %%rbx\n" + "movq %%rbx, 0(%%rax)\n" // rip + "movw %%cs, 8(%%rax)\n" // cs + "pushfq\n" + "popq %%rbx\n" + "mov %%rbx, 16(%%rax)\n" // eflags + "mov %%rsp, 24(%%rax)\n" // rsp + "movw %%ss, 32(%%rax)\n" + "mov %%rcx, %%rdi\n" + "call do_iret\n" + "out_iret:\n" + : : "g"(tf_cur), "g" (tf) : "memory" + ); +} + +/* Schedules a new process. At entry, interrupts must be off. + * This function modify current thread's status to status and then + * finds another thread to run and switches to it. + * It's not safe to call printf() in the schedule(). */ +static void +do_schedule(int status) { + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (thread_current() ->status == THREAD_RUNNING); + while (!list_empty (&destruction_req)) { + struct thread *victim = + list_entry (list_pop_front (&destruction_req), struct thread, elem); + palloc_free_page(victim); + } + thread_current ()->status = status; + schedule (); +} + +static void +schedule (void) { + struct thread *curr = running_thread (); + struct thread *next = next_thread_to_run (); + + ASSERT (intr_get_level () == INTR_OFF); + ASSERT (curr->status != THREAD_RUNNING); + ASSERT (is_thread (next)); + /* Mark us as running. */ + next->status = THREAD_RUNNING; + + /* Start new time slice. */ + thread_ticks = 0; + +#ifdef USERPROG + /* Activate the new address space. */ + process_activate (next); +#endif + + if (curr != next) { + /* If the thread we switched from is dying, destroy its struct + thread. This must happen late so that thread_exit() doesn't + pull out the rug under itself. + We just queuing the page free reqeust here because the page is + currently used bye the stack. + The real destruction logic will be called at the beginning of the + schedule(). */ + if (curr && curr->status == THREAD_DYING && curr != initial_thread) { + ASSERT (curr != next); + list_push_back (&destruction_req, &curr->elem); + } + + /* Before switching the thread, we first save the information + * of current running. */ + thread_launch (next); + } +} + +/* Returns a tid to use for a new thread. */ +static tid_t +allocate_tid (void) { + static tid_t next_tid = 1; + tid_t tid; + + lock_acquire (&tid_lock); + tid = next_tid++; + lock_release (&tid_lock); + + return tid; +} diff --git a/utils/backtrace b/utils/backtrace new file mode 100755 index 0000000..01683d6 --- /dev/null +++ b/utils/backtrace @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +import subprocess +import os + + +def usage(fname): + print('usage: {} addr ...'.format(fname)) + exit(-1) + + +def resolve_kernel(): + for p in ['./kernel.o', './build/kernel.o']: + if os.path.exists(p): + return p + print('Neither "kernel.o" nor "build/kernel.o" exists') + exit(-1) + + +def resolve_loc(addrs): + out = subprocess.check_output( + ['addr2line', '-e', resolve_kernel(), '-f'] + addrs) + lines = out.split('\n')[:-1] + for idx in range(0, len(lines), 2): + fname = lines[idx] + path = lines[idx+1].split("../")[-1] + if fname == '??': + print("0x{:016x}: (unknown)".format( + int(addrs[idx/2], 16), fname, path)) + else: + print("0x{:016x}: {} ({})".format( + int(addrs[idx/2], 16), fname, path)) + + +def main(argv): + if len(argv) < 2 or "-h" in argv or "--help" in argv: + usage() + resolve_loc(argv[1:]) + + +if __name__ == '__main__': + import sys + main(sys.argv) diff --git a/utils/pintos b/utils/pintos new file mode 100755 index 0000000..bcbddb7 --- /dev/null +++ b/utils/pintos @@ -0,0 +1,172 @@ +#!/usr/bin/env python3 + +import struct +import sys +import os +import tempfile +import subprocess + + +def die(errmsg): + print(errmsg) + exit(1) + + +def align(s, size): + if len(s) % size != 0: + s += bytes('\0' * (size - len(s) % size), 'utf-8') + return s + + +def get_temp_dsk_name(): + with tempfile.NamedTemporaryFile(mode='wb') as disk_copy: + return disk_copy.name + '.dsk' + + +class Pintos(object): + def __init__(self, no_vga=True, serial=False, args=[], hostfns=[], + gdb=False, fs='fs.dsk', timeout=0): + self.mem = 256 + self.no_vga = no_vga + self.args = args + self.gdb = gdb + self.proc = None + self.timeout = timeout + self.host_fns = hostfns + self.bdevs = {'os': 'os.dsk', 'fs': fs, 'swap': 'swap.dsk'} + + def __scan_dir(self): + new = {} + for k, v in self.bdevs.items(): + if not os.path.exists(v): + try: + size = int(v) + new[k] = get_temp_dsk_name() + with open(new[k], 'wb') as f: + f.write(bytes('\0' * (0xfc000 * size), 'utf-8')) + except Exception: + if k == 'os': + die('os.dsk cannot be temporal.') + else: + new[k] = v + return new + + def __prepare_scratch_files(self): + puts = [] + self.bdevs['scratch'] = get_temp_dsk_name() + disk = open(self.bdevs['scratch'], 'wb') + for fname in self.host_fns: + host = fname[0] + puts.append(fname[1] if len(fname) > 1 else host) + with open(host, 'rb') as hf: + data = align(hf.read(), 512) + disk.write(bytes("PUT\0", 'utf-8') + + struct.pack(" 128: + die("command line exceeds 128 bytes") + + with tempfile.NamedTemporaryFile(mode='wb') as disk_copy: + name = disk_copy.name + '.dsk' + + with open('os.dsk', 'rb') as f: + data = f.read() + + with open(name, 'wb+') as f: + f.write(data[:0x17a] + + struct.pack(" sub { usage (0); }) + or exit 1; +usage (1) if @ARGV != 2; + +my ($disk, $mb) = @ARGV; +die "$disk: already exists\n" if -e $disk; +die "\"$mb\" is not a valid size in megabytes\n" + if $mb <= 0 || $mb > 1024 || $mb !~ /^\d+(\.\d+)?|\.\d+/; + +my ($cyl_cnt) = ceil ($mb * 2); +my ($cyl_bytes) = 512 * 16 * 63; +my ($bytes) = $cyl_bytes * $cyl_cnt; + +open (DISK, '>', $disk) or die "$disk: create: $!\n"; +sysseek (DISK, $bytes - 1, SEEK_SET) or die "$disk: seek: $!\n"; +syswrite (DISK, "\0", 1) == 1 or die "$disk: write: $!\n"; +close (DISK) or die "$disk: close: $!\n"; + +sub usage { + print <<'EOF'; +pintos-mkdisk, a utility for creating Pintos virtual disks +Usage: pintos DISKFILE MB +where DISKFILE is the file to use for the disk + and MB is the disk size in (approximate) megabytes. +Options: + -h, --help Display this help message. +EOF + exit (@_); +}