From 8452fdeef1f79ccf3d5dd79c67c25b060625995c Mon Sep 17 00:00:00 2001 From: Minkyu Jung Date: Sat, 21 Mar 2020 17:26:30 +0900 Subject: [PATCH] Template for project1 --- LICENSE | 190 +++ Make.config | 36 + Makefile | 29 + Makefile.build | 64 + Makefile.kernel | 20 + Makefile.userprog | 52 + activate | 11 + devices/disk.c | 529 ++++++ devices/input.c | 48 + devices/intq.c | 105 ++ devices/kbd.c | 186 +++ devices/serial.c | 215 +++ devices/targets.mk | 7 + devices/timer.c | 186 +++ devices/vga.c | 156 ++ examples/Makefile | 34 + examples/bubsort.c | 38 + examples/cat.c | 34 + examples/cmp.c | 68 + examples/cp.c | 55 + examples/echo.c | 14 + examples/halt.c | 14 + examples/hex-dump.c | 35 + examples/insult.c | 369 +++++ examples/lineup.c | 46 + examples/ls.c | 90 + examples/matmult.c | 57 + examples/mcat.c | 45 + examples/mcp.c | 68 + examples/mkdir.c | 24 + examples/pwd.c | 152 ++ examples/recursor.c | 34 + examples/rm.c | 21 + examples/shell.c | 104 ++ filesys/Make.vars | 13 + filesys/Makefile | 1 + filesys/directory.c | 216 +++ filesys/file.c | 148 ++ filesys/filesys.c | 98 ++ filesys/free-map.c | 77 + filesys/fsutil.c | 189 +++ filesys/inode.c | 313 ++++ filesys/targets.mk | 6 + include/devices/disk.h | 26 + include/devices/input.h | 12 + include/devices/intq.h | 42 + include/devices/kbd.h | 9 + include/devices/serial.h | 11 + include/devices/timer.h | 23 + include/devices/vga.h | 6 + include/filesys/directory.h | 30 + include/filesys/file.h | 29 + include/filesys/filesys.h | 20 + include/filesys/free-map.h | 17 + include/filesys/fsutil.h | 10 + include/filesys/inode.h | 23 + include/filesys/off_t.h | 15 + include/intrinsic.h | 131 ++ include/lib/ctype.h | 28 + include/lib/debug.h | 38 + include/lib/inttypes.h | 48 + include/lib/kernel/bitmap.h | 51 + include/lib/kernel/console.h | 8 + include/lib/kernel/hash.h | 100 ++ include/lib/kernel/list.h | 163 ++ include/lib/kernel/stdio.h | 6 + include/lib/limits.h | 34 + include/lib/random.h | 10 + include/lib/round.h | 18 + include/lib/stdarg.h | 14 + include/lib/stdbool.h | 9 + include/lib/stddef.h | 12 + include/lib/stdint.h | 51 + include/lib/stdio.h | 39 + include/lib/stdlib.h | 22 + include/lib/string.h | 35 + include/lib/syscall-nr.h | 34 + include/lib/user/stdio.h | 7 + include/lib/user/syscall.h | 49 + include/threads/flags.h | 12 + include/threads/init.h | 20 + include/threads/interrupt.h | 78 + include/threads/intr-stubs.h | 16 + include/threads/io.h | 161 ++ include/threads/loader.h | 39 + include/threads/malloc.h | 13 + include/threads/mmu.h | 32 + include/threads/palloc.h | 23 + include/threads/pte.h | 45 + include/threads/synch.h | 48 + include/threads/thread.h | 138 ++ include/threads/vaddr.h | 59 + lib/arithmetic.c | 171 ++ lib/debug.c | 30 + lib/kernel/bitmap.c | 338 ++++ lib/kernel/console.c | 177 ++ lib/kernel/debug.c | 44 + lib/kernel/hash.c | 394 +++++ lib/kernel/list.c | 489 ++++++ lib/kernel/targets.mk | 5 + lib/random.c | 77 + lib/stdio.c | 594 +++++++ lib/stdlib.c | 208 +++ lib/string.c | 347 ++++ lib/targets.mk | 6 + lib/user/console.c | 86 + lib/user/debug.c | 24 + lib/user/entry.c | 9 + lib/user/syscall.c | 132 ++ lib/user/user.lds | 60 + tests/Algorithm/Diff.pm | 1713 ++++++++++++++++++++ tests/Make.tests | 76 + tests/arc4.c | 53 + tests/arc4.h | 17 + tests/arc4.pm | 29 + tests/cksum.c | 92 ++ tests/cksum.h | 8 + tests/cksum.pm | 87 + tests/internal/list.c | 174 ++ tests/internal/stdio.c | 208 +++ tests/internal/stdlib.c | 114 ++ tests/lib.c | 196 +++ tests/lib.h | 50 + tests/lib.pm | 19 + tests/main.c | 15 + tests/main.h | 6 + tests/make-grade | 152 ++ tests/random.pm | 27 + tests/tests.pm | 622 +++++++ tests/threads/Grading | 6 + tests/threads/Make.tests | 53 + tests/threads/Rubric.alarm | 8 + tests/threads/Rubric.mlfqs | 14 + tests/threads/Rubric.priority | 15 + tests/threads/alarm-multiple.ck | 4 + tests/threads/alarm-negative.c | 15 + tests/threads/alarm-negative.ck | 10 + tests/threads/alarm-priority.c | 58 + tests/threads/alarm-priority.ck | 19 + tests/threads/alarm-simultaneous.c | 94 ++ tests/threads/alarm-simultaneous.ck | 27 + tests/threads/alarm-single.ck | 4 + tests/threads/alarm-wait.c | 152 ++ tests/threads/alarm-zero.c | 15 + tests/threads/alarm-zero.ck | 10 + tests/threads/alarm.pm | 32 + tests/threads/mlfqs-block.c | 64 + tests/threads/mlfqs-block.ck | 17 + tests/threads/mlfqs-fair-2.ck | 7 + tests/threads/mlfqs-fair-20.ck | 7 + tests/threads/mlfqs-fair.c | 124 ++ tests/threads/mlfqs-load-1.c | 60 + tests/threads/mlfqs-load-1.ck | 15 + tests/threads/mlfqs-load-60.c | 155 ++ tests/threads/mlfqs-load-60.ck | 36 + tests/threads/mlfqs-load-avg.c | 167 ++ tests/threads/mlfqs-load-avg.ck | 36 + tests/threads/mlfqs-nice-10.ck | 7 + tests/threads/mlfqs-nice-2.ck | 7 + tests/threads/mlfqs-recent-1.c | 144 ++ tests/threads/mlfqs-recent-1.ck | 31 + tests/threads/mlfqs.pm | 146 ++ tests/threads/priority-change.c | 31 + tests/threads/priority-change.ck | 14 + tests/threads/priority-condvar.c | 53 + tests/threads/priority-condvar.ck | 39 + tests/threads/priority-donate-chain.c | 114 ++ tests/threads/priority-donate-chain.ck | 46 + tests/threads/priority-donate-lower.c | 51 + tests/threads/priority-donate-lower.ck | 16 + tests/threads/priority-donate-multiple.c | 77 + tests/threads/priority-donate-multiple.ck | 19 + tests/threads/priority-donate-multiple2.c | 90 + tests/threads/priority-donate-multiple2.ck | 19 + tests/threads/priority-donate-nest.c | 94 ++ tests/threads/priority-donate-nest.ck | 19 + tests/threads/priority-donate-one.c | 65 + tests/threads/priority-donate-one.ck | 17 + tests/threads/priority-donate-sema.c | 82 + tests/threads/priority-donate-sema.ck | 16 + tests/threads/priority-fifo.c | 99 ++ tests/threads/priority-fifo.ck | 63 + tests/threads/priority-preempt.c | 41 + tests/threads/priority-preempt.ck | 16 + tests/threads/priority-sema.c | 45 + tests/threads/priority-sema.ck | 29 + tests/threads/tests.c | 102 ++ tests/threads/tests.h | 41 + threads/Make.vars | 6 + threads/Makefile | 1 + threads/init.c | 351 ++++ threads/interrupt.c | 405 +++++ threads/intr-stubs.S | 200 +++ threads/kernel.lds.S | 35 + threads/loader.S | 313 ++++ threads/malloc.c | 268 +++ threads/mmu.c | 273 ++++ threads/palloc.c | 359 ++++ threads/start.S | 152 ++ threads/synch.c | 323 ++++ threads/targets.mk | 9 + threads/thread.c | 590 +++++++ utils/backtrace | 42 + utils/pintos | 172 ++ utils/pintos-mkdisk | 37 + 205 files changed, 19303 insertions(+) create mode 100644 LICENSE create mode 100644 Make.config create mode 100644 Makefile create mode 100644 Makefile.build create mode 100644 Makefile.kernel create mode 100644 Makefile.userprog create mode 100755 activate create mode 100644 devices/disk.c create mode 100644 devices/input.c create mode 100644 devices/intq.c create mode 100644 devices/kbd.c create mode 100644 devices/serial.c create mode 100644 devices/targets.mk create mode 100644 devices/timer.c create mode 100644 devices/vga.c create mode 100644 examples/Makefile create mode 100644 examples/bubsort.c create mode 100644 examples/cat.c create mode 100644 examples/cmp.c create mode 100644 examples/cp.c create mode 100644 examples/echo.c create mode 100644 examples/halt.c create mode 100644 examples/hex-dump.c create mode 100644 examples/insult.c create mode 100644 examples/lineup.c create mode 100644 examples/ls.c create mode 100644 examples/matmult.c create mode 100644 examples/mcat.c create mode 100644 examples/mcp.c create mode 100644 examples/mkdir.c create mode 100644 examples/pwd.c create mode 100644 examples/recursor.c create mode 100644 examples/rm.c create mode 100644 examples/shell.c create mode 100644 filesys/Make.vars create mode 100644 filesys/Makefile create mode 100644 filesys/directory.c create mode 100644 filesys/file.c create mode 100644 filesys/filesys.c create mode 100644 filesys/free-map.c create mode 100644 filesys/fsutil.c create mode 100644 filesys/inode.c create mode 100644 filesys/targets.mk create mode 100644 include/devices/disk.h create mode 100644 include/devices/input.h create mode 100644 include/devices/intq.h create mode 100644 include/devices/kbd.h create mode 100644 include/devices/serial.h create mode 100644 include/devices/timer.h create mode 100644 include/devices/vga.h create mode 100644 include/filesys/directory.h create mode 100644 include/filesys/file.h create mode 100644 include/filesys/filesys.h create mode 100644 include/filesys/free-map.h create mode 100644 include/filesys/fsutil.h create mode 100644 include/filesys/inode.h create mode 100644 include/filesys/off_t.h create mode 100644 include/intrinsic.h create mode 100644 include/lib/ctype.h create mode 100644 include/lib/debug.h create mode 100644 include/lib/inttypes.h create mode 100644 include/lib/kernel/bitmap.h create mode 100644 include/lib/kernel/console.h create mode 100644 include/lib/kernel/hash.h create mode 100644 include/lib/kernel/list.h create mode 100644 include/lib/kernel/stdio.h create mode 100644 include/lib/limits.h create mode 100644 include/lib/random.h create mode 100644 include/lib/round.h create mode 100644 include/lib/stdarg.h create mode 100644 include/lib/stdbool.h create mode 100644 include/lib/stddef.h create mode 100644 include/lib/stdint.h create mode 100644 include/lib/stdio.h create mode 100644 include/lib/stdlib.h create mode 100644 include/lib/string.h create mode 100644 include/lib/syscall-nr.h create mode 100644 include/lib/user/stdio.h create mode 100644 include/lib/user/syscall.h create mode 100644 include/threads/flags.h create mode 100644 include/threads/init.h create mode 100644 include/threads/interrupt.h create mode 100644 include/threads/intr-stubs.h create mode 100644 include/threads/io.h create mode 100644 include/threads/loader.h create mode 100644 include/threads/malloc.h create mode 100644 include/threads/mmu.h create mode 100644 include/threads/palloc.h create mode 100644 include/threads/pte.h create mode 100644 include/threads/synch.h create mode 100644 include/threads/thread.h create mode 100644 include/threads/vaddr.h create mode 100644 lib/arithmetic.c create mode 100644 lib/debug.c create mode 100644 lib/kernel/bitmap.c create mode 100644 lib/kernel/console.c create mode 100644 lib/kernel/debug.c create mode 100644 lib/kernel/hash.c create mode 100644 lib/kernel/list.c create mode 100644 lib/kernel/targets.mk create mode 100644 lib/random.c create mode 100644 lib/stdio.c create mode 100644 lib/stdlib.c create mode 100644 lib/string.c create mode 100644 lib/targets.mk create mode 100644 lib/user/console.c create mode 100644 lib/user/debug.c create mode 100644 lib/user/entry.c create mode 100644 lib/user/syscall.c create mode 100644 lib/user/user.lds create mode 100644 tests/Algorithm/Diff.pm create mode 100644 tests/Make.tests create mode 100644 tests/arc4.c create mode 100644 tests/arc4.h create mode 100644 tests/arc4.pm create mode 100644 tests/cksum.c create mode 100644 tests/cksum.h create mode 100644 tests/cksum.pm create mode 100644 tests/internal/list.c create mode 100644 tests/internal/stdio.c create mode 100644 tests/internal/stdlib.c create mode 100644 tests/lib.c create mode 100644 tests/lib.h create mode 100644 tests/lib.pm create mode 100644 tests/main.c create mode 100644 tests/main.h create mode 100755 tests/make-grade create mode 100644 tests/random.pm create mode 100644 tests/tests.pm create mode 100644 tests/threads/Grading create mode 100644 tests/threads/Make.tests create mode 100644 tests/threads/Rubric.alarm create mode 100644 tests/threads/Rubric.mlfqs create mode 100644 tests/threads/Rubric.priority create mode 100644 tests/threads/alarm-multiple.ck create mode 100644 tests/threads/alarm-negative.c create mode 100644 tests/threads/alarm-negative.ck create mode 100644 tests/threads/alarm-priority.c create mode 100644 tests/threads/alarm-priority.ck create mode 100644 tests/threads/alarm-simultaneous.c create mode 100644 tests/threads/alarm-simultaneous.ck create mode 100644 tests/threads/alarm-single.ck create mode 100644 tests/threads/alarm-wait.c create mode 100644 tests/threads/alarm-zero.c create mode 100644 tests/threads/alarm-zero.ck create mode 100644 tests/threads/alarm.pm create mode 100644 tests/threads/mlfqs-block.c create mode 100644 tests/threads/mlfqs-block.ck create mode 100644 tests/threads/mlfqs-fair-2.ck create mode 100644 tests/threads/mlfqs-fair-20.ck create mode 100644 tests/threads/mlfqs-fair.c create mode 100644 tests/threads/mlfqs-load-1.c create mode 100644 tests/threads/mlfqs-load-1.ck create mode 100644 tests/threads/mlfqs-load-60.c create mode 100644 tests/threads/mlfqs-load-60.ck create mode 100644 tests/threads/mlfqs-load-avg.c create mode 100644 tests/threads/mlfqs-load-avg.ck create mode 100644 tests/threads/mlfqs-nice-10.ck create mode 100644 tests/threads/mlfqs-nice-2.ck create mode 100644 tests/threads/mlfqs-recent-1.c create mode 100644 tests/threads/mlfqs-recent-1.ck create mode 100644 tests/threads/mlfqs.pm create mode 100644 tests/threads/priority-change.c create mode 100644 tests/threads/priority-change.ck create mode 100644 tests/threads/priority-condvar.c create mode 100644 tests/threads/priority-condvar.ck create mode 100644 tests/threads/priority-donate-chain.c create mode 100644 tests/threads/priority-donate-chain.ck create mode 100644 tests/threads/priority-donate-lower.c create mode 100644 tests/threads/priority-donate-lower.ck create mode 100644 tests/threads/priority-donate-multiple.c create mode 100644 tests/threads/priority-donate-multiple.ck create mode 100644 tests/threads/priority-donate-multiple2.c create mode 100644 tests/threads/priority-donate-multiple2.ck create mode 100644 tests/threads/priority-donate-nest.c create mode 100644 tests/threads/priority-donate-nest.ck create mode 100644 tests/threads/priority-donate-one.c create mode 100644 tests/threads/priority-donate-one.ck create mode 100644 tests/threads/priority-donate-sema.c create mode 100644 tests/threads/priority-donate-sema.ck create mode 100644 tests/threads/priority-fifo.c create mode 100644 tests/threads/priority-fifo.ck create mode 100644 tests/threads/priority-preempt.c create mode 100644 tests/threads/priority-preempt.ck create mode 100644 tests/threads/priority-sema.c create mode 100644 tests/threads/priority-sema.ck create mode 100644 tests/threads/tests.c create mode 100644 tests/threads/tests.h create mode 100644 threads/Make.vars create mode 100644 threads/Makefile create mode 100644 threads/init.c create mode 100644 threads/interrupt.c create mode 100644 threads/intr-stubs.S create mode 100644 threads/kernel.lds.S create mode 100644 threads/loader.S create mode 100644 threads/malloc.c create mode 100644 threads/mmu.c create mode 100644 threads/palloc.c create mode 100644 threads/start.S create mode 100644 threads/synch.c create mode 100644 threads/targets.mk create mode 100644 threads/thread.c create mode 100755 utils/backtrace create mode 100755 utils/pintos create mode 100755 utils/pintos-mkdisk 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 (@_); +}