commit 6f405265a54b8991f3b7923dbd10b6c712d43422 Author: Yorick Barbanneau Date: Mon Oct 11 22:27:00 2021 +0200 Initial version diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..0892ced --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,18 @@ +Copyright (c) 1992-1993 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. diff --git a/README b/README new file mode 100644 index 0000000..41ec75c --- /dev/null +++ b/README @@ -0,0 +1,365 @@ +This README file describes the Nachos release. Comments, questions, +and bug reports are always welcome, and can be directed to +nachos@cs.berkeley.edu (for now, an alias to just me, Tom Anderson), or +to the alt.os.nachos newsgroup. + +Nachos is instructional software for teaching undergraduate, and potentially +graduate, level operating systems courses. The Nachos distribution +comes with: + + an overview paper + simple baseline code for a working operating system + a simulator for a generic personal computer/workstation + sample assignments + a C++ primer (Nachos is written in an easy-to-learn subset of C++, + and the primer helps teach C programmers our subset) + +The assignments illustrate and explore all areas of modern operating +systems, including threads and concurrency, multiprogramming, +system calls, virtual memory, software-loaded TLB's, file systems, +network protocols, remote procedure call, and distributed systems. + +The most up to date version of nachos is linked to the file called, +nachos.tar.Z. On Jan. 20, 1993, this version was nachos-3.1.tar.Z, +but it will be periodically updated as bugs are fixed and features added. +REMEMBER TO TURN BINARY MODE ON WHEN RETRIEVING .Z FILES. + +To get started, you should: + 1. use ftp to fetch the nachos.tar.Z file (turning on binary mode first) + 2. uncompress nachos.tar.Z + 3. tar -xf nachos.tar + 4. lpr nachos.ps -- print out the paper describing nachos + 5. lpr doc/*.ps -- print out the sample assignments + 6. cd code; make print -- print out the nachos source code + 7. cd code/c++example; lpr *.ps *.h *.cc -- print out C++ primer + 8. if you have no DEC MIPS workstations available at your site, you + will need to install the gcc cross-compiler on your system. See + the instructions at the end of this file. + 9. edit code/Makefile.dep to specify host machine type + if cross-compiling (cf. step 8) you also need to: + edit code/test/Makefile and code/bin/Makefile + 10. cd code; make all -- compile nachos source code + +Version 3 has been used for a semester at Berkeley by over a +hundred students, so most of the bugs are out of the system. +However, there are likely to be some remaining problems; if you +find these, please send e-mail to nachos@cs.berkeley.edu (particularly +if you have a fix :-). + +At present, Nachos runs on several platforms, including: + DEC MIPS, running Ultrix + SUN SPARCstations (only tested on SunOS, not Solaris, though) + HP PA-RISC, running HP-UX + 386 boxes, running 386BSD UNIX or FreeBSD + +Notably we do not currently support: + PC Windows + Macintosh + non-SPARC SUN workstations +However, PC and Macintosh support is under development. The main change +that you need to make to support another platform is an implementation +of the low-level machine-dependent context switch code, in threads/switch.s. +Several example architectures are now supported in switch.s. + +The basic Nachos system was written for a MIPS workstation. It has +been ported to other platforms, but as of now, there are a few gotchas. +The Nachos kernel and machine simulator run directly on the host machine, +but user-level programs running on top of Nachos are simulated instruction-by- +instruction. The simulator assumes MIPS object code, in little endian format. +It would take much more work to complete the port and change the CPU +simulator to simulate other instruction sets (although this is under +investigation). Keeping the MIPS CPU causes a few problems: + +1) You have to generate user-level code for the simulated machine. +If you have a heterogeneous environment with some MIPS and non-MIPS +workstations, this isn't so hard -- students only need to compile a few +small user programs. But if you only have non-MIPS machines, you need to +get gcc to cross-compile to the DEC MIPS. Gcc only recently has been fixed +to support this, and the instructions for how to do this are listed below. +If you are unable to get the cross-compiler to work, do not despair. +The distribution comes with a few simple user programs (pre-compiled to +MIPS object code) that students can use to test out Nachos kernel services. + +2) The Nachos kernel runs runs native mode while the user programs +runs on the simulated CPU. This is a little weird on the non-MIPS workstations +because the user programs are using little endian (typically) and the kernel is +using big endian. Some information (such as the argv[] array) that +is passed between the kernel and the user though user memory must be +byte swapped. (Unfortunately, this isn't as easy to fix as simply +cross-compiling to the SGI MIPS, which is big endian; in a few places, +the simulation assumes little endian format. We're working on fixing this.) + +The end of this file contains the procedure for constructing a cross-compiler +to the MIPS, using the gcc toolkit. + +Version 3.1 Release Notes +-------------------------- + +Primarily, fixed up problems with cross-compiled environment. + + +Version 3 Release Notes +----------------------- + +Note that version 2 is still available, in + ftp.cs.berkeley.edu:ucb/nachos/version2 + + +There are several major changes for version 3, relative to earlier versions: + +1. Extensive comments. All procedures and data structures now have + commented explanations. Hopefully, this will help make it easier for + students (and professors) to read and understand the baseline system. + In writing the comments, I realized that we continue to lack an "overall" + roadmap to the system; Nachos deals with conceptually hard issues + in a bunch of places in the code, and I think students would find a roadmap + helpful. I am plotting how to do this; for now, my apologies for + anything that seems unduly complex and opaque. Any suggestions + for places that need better explanations are welcome. + +2. Modifications to improve portability, along with ports to several more + platforms. All machine dependencies are now isolated into only a few + locations (primarily, switch.h/switch.s, and sysdep.h/sysdep.cc), making + it much easier to port Nachos to new platforms. This is evidenced by + the fact that the HP PA-RISC and 386UNIX ports were each completed in a + few days worth of concentrated effort. There is now a common code base, + so the separate code base for SPARCs in Version 2 is no longer needed. + +3. The directory structure has been simplified and made more generic. + Instead of directories named for the assignments that I give, I have + named them after topic areas: threads, userprog, vm, filesys, and network. + Each represents a single assignment, but there is a large amount + of flexibility now in choosing the order to cover these topics. + Here is the dependency graph: + + threads -> userprog -> vm + -> filesys + -> network + + In other words, all other assignments rely on you covering threads first, + but the next assignment after that could be either multiprogramming, + the file system, or network support. The only other constraint is that + the virtual memory stuff relies on the user programming assignment + being completed [NOTE however that we provide no code for the virtual + memory assignment, so it could be easily folded into the userprog + assignment.] + + Also, the userprog and vm assignments rely on there being a file system + to fetch executables and to serve as backing store for virtual memory pages. + A "stub" version of the file system is provided to allow these assignments + to be done first; the stub version is not needed if file systems + are covered before user programming and virtual memory. + +4. More extensive options with respect to the sample assignments. + I have now three semesters of experience in teaching with Nachos. + My assignments have varied slightly from semester to semester, and + I have now compiled all of these versions into the sample assignments + [with comments as to which portions I assigned in any given semester]. + The expectation is that you will subset the portion that you find most + interesting; if you have suggestions for what I might include in the + sample assignments, I would be happy to hear them. Hopefully, from + this point on, any changes to the sample assignments will only be to add + further options. + + Over the long term, it seems to me we will each need to vary the + assignments, to prevent widespread sharing of solution sets. + +5. Support for a software-loaded Translation Lookaside Buffer. This + can be disabled (turning the machine simulation back to using simple + linear page tables) for those who want to avoid the added complexity, + but it is a feature of many modern architectures, and I think it + is a good illustration of caching issues. This is the only substantive + change for this version. + + One advantage is that it allows a *lot* more flexibility in the + VM assignment -- for instance, a student could build a flat one-level + page table, segmentation plus paging, an inverted page table, etc. + This is all without modifying the hardware emulation. Also, this could + also lead to issues such as shared memory segments between address spaces, + which couldn't be supported in the current model. + + One consequence is that there are now a new object code format for + Nachos user programs. The standard UNIX format, COFF, is way too + complicated. I have a simplified format, NOFF (Nachos Object Format), + which simply identifies the code, data, and bss segments. By default, + these segments are concatenated together (as in earlier versions of + Nachos), beginning at location 0, but with the software loaded TLB, + you have the flexibility to do something smarter. + + The converter from COFF to NOFF has been ported to run on all of the + supported machines. + + +Future plans: + +1. Known bugs + a. Nachos has a memory leak that causes it to increase its + virtual memory size over time, even if Nachos is not doing anything. + +2. Planned ports (other suggestions welcome): + a. M/S Windows (somewhere between Jan and June 94) + b. Macintosh (ditto) + c. DEC Alpha (as soon as it gets a reliable g++) + +3. Nachos user's guide and roadmap (not under development yet, so + definite target date. Maybe end of summer 94) + This would come in two parts -- first, a student guide that + would walk students through the baseline code, explaining how + the system works, and also to explain a bit of the underlying machine + emulation. At Berkeley, we devote about an hour per week in section + to going through the code, but it would be helpful (particularly for + those schools without discussion sections) to have this written down. + I've found in all three semesters I've taught the course that students really + do end up repeating many of the same questions. + + The second part would be an instructor's guide -- how do you + get Nachos up and running on various systems, how the internals + of the machine emulation work, how much time each of the assignments + takes, etc. + + The result would replace the existing sample assignments with + something more helpful. + (The downside is that some parts of Nachos build on other parts, + so I have to be clear about these dependencies.) + +4. New development -- this is in semi-priority order. + +a. Modify the network simulation to be performance accurate, by + using Chandy-Misra conservative simulation techniques to + keep the clocks on each simulated Nachos machine in sync. + I have a prototype implementation of this, so this isn't all that + difficult. I'll make sure to leave an option to disable this, to go back + to the way the simulation works now, for backward compatibility. + +b. Modify the file system to do write ahead logging for reliability. + I talk about transactions in my class, and having example code + would be really useful, at least for me. Again, I have a prototype + implementation of this, and I'll make sure that it can be disabled. + +c. Write an RPC stub generator (actually, simplify the one used in Mach, + and convert it to generate Nachos network messages). I think + the students would get a lot out of seeing a working RPC system, + and I think I can do this in a way that would be simple enough + for most students to easily understand. As it stands, I have + the feeling most of my students don't understand the mechanics of + setting up an RPC connection, which at present, I can only describe + verbally. + + At first, I'm likely to do only a C-to-C stub generator, rather + than a C++ stub generator. Although the latter would obviously fit + into Nachos better, it's also harder! + +d. Modify Nachos to insert interrupts at arbitrary points in the code. + Currently, interrupts (such as timer expiring) only occur when + Nachos is executing user-level code, or when the Nachos kernel calls + the enable interrupt routine. A different (better?) approach would + be to check for interrupts on every procedure entry within the Nachos + kernel; we could do this by modifying the compiler-inserted "mcount" + routine for performance profiling. + +Again, comments on how to improve Nachos are always welcome. + +Tom Anderson +tea@cs.berkeley.edu +nachos@cs.berkeley.edu + + + +Building a gcc cross-compiler +----------------------------- +The gcc distribution has fairly good documentation on how to do this, +but since I walked through it, I figured I would just give you a recipe. +The following works from the SPARC to the DEC MIPS; if you want a +cross-compiler to a different platform (eg, the HP Snakes), you'll need +to just alter this procedure slightly. + +NOTE: we don't need the full cross-compiled environment. In particular, +Nachos user programs include none of the standard UNIX library or system +call stubs, and it assumes its own crt.s (assembly language assist for +starting a program running). This makes this significantly simpler, +and it vastly reduces the size of (and overall simplifies) the resulting +object code. + +# To build a cross-compiler using the gnu tools: + +# grab the tools via anonymous ftp +% ftp prep.ai.mit.edu + + ftp> cd /pub/gnu +# REMEMBER TO SET BINARY MODE + ftp> binary +# get gcc, binutils, gas -- these or later versions + ftp> get gcc-2.4.5.tar.gz + ftp> get binutils-2.2.1.tar.gz + ftp> get gas-2.1.1.tar.gz + ftp> quit + +# gunzip decompresses the tar files +# you can also get gunzip from prep.ai.mit.edu +% gunzip * + +# where the executables are to go, usually /usr/local +% setenv gccLocal /usr/local + +# de-tar the files +% tar -xf gas-2.1.1.tar +% tar -xf binutils-2.2.1.tar +% tar -xf gcc-2.4.5.tar +% mkdir tar +% mv *.tar tar + +# build gas and binutils first +% cd gas* + +% ./configure --host=sparc-sun-sunos4.1.3 --target=decstation-ultrix + --prefix $gccLocal + +% make + +% make install + +% cd ../bin* + +% ./configure --host=sparc-sun-sunos4.1.3 --target=decstation-ultrix + --prefix $gccLocal + +% make + +% make install + +% cd ../gcc* + +% ./configure --host=sparc-sun-sunos4.1.3 --target=decstation-ultrix + --with-gnu-as --with-gnu-ld --prefix $gccLocal + --local-prefix $gccLocal + +# Afer building the cross-compiler, the Makefile is going to try to +# use it to build a set of libraries, and a couple test cases. +# Unfortunately, the libraries depend on UNIX headers (such as stdio.h). +# +# Since Nachos user programs don't need these headers (they'd be +# wrong anyway, since Nachos doesn't support the standard UNIX syscall +# interface), we need to fake out the Makefile. + +# create a dummy lib files, to keep make happy +% ar r libgcc.a /dev/null +% ar r libgcc2.a /dev/null + +# Delete the following lines from the Makefile +# ENQUIRE = enquire +# CROSS_TEST = cross-test +% vi Makefile + +% make LANGUAGES=c + +# at this point you may get an error building libgcc2; ignore it and proceed. + +% make install LANGUAGES=c + +# at this point, the cross-compiler and subsidiary tools +# are now installed, in $gccLocal/decstation-ultrix/bin + +# one last thing -- +# you need to edit nachos/code/test/Makefile to use the +# gcc cross-compilation tools you have just built, instead of using +# normal gcc. diff --git a/bin/nachos_arch b/bin/nachos_arch new file mode 100755 index 0000000..05bbe9d --- /dev/null +++ b/bin/nachos_arch @@ -0,0 +1,65 @@ +#!/bin/sh +# +# PM2 HIGH-PERF/ISOMALLOC +# High Performance Parallel Multithreaded Machine +# version 3.0 +# +# Gabriel Antoniu, Olivier Aumage, Luc Bouge, Vincent Danjean, +# Christian Perez, Jean-Francois Mehaut, Raymond Namyst +# +# Laboratoire de l'Informatique du Parallelisme +# UMR 5668 CNRS-INRIA +# Ecole Normale Superieure de Lyon +# +# External Contributors: +# Yves Denneulin (LMC - Grenoble), +# Benoit Planquelle (LIFL - Lille) +# +# 1998 All Rights Reserved +# +# +# NOTICE +# +# Permission to use, copy, modify, and distribute this software and +# its documentation for any purpose and without fee is hereby granted +# provided that the above copyright notice appear in all copies and +# that both the copyright notice and this permission notice appear in +# supporting documentation. +# +# Neither the institutions (Ecole Normale Superieure de Lyon, +# Laboratoire de L'informatique du Parallelisme, Universite des +# Sciences et Technologies de Lille, Laboratoire d'Informatique +# Fondamentale de Lille), nor the Authors make any representations +# about the suitability of this software for any purpose. This +# software is provided ``as is'' without express or implied warranty. +# + +if [ -f /bin/uname -o -f /usr/bin/uname ]; then + if [ -f /bin/uname ]; then + os="`/bin/uname -s`" + ht="`/bin/uname -m`" + else + os="`/usr/bin/uname -s`" + ht="`/usr/bin/uname -m`" + fi + + case "$os,$ht" in + SunOS,sun*) PM2_ARCH=SPARC_ARCH ;; + *,i86pc) PM2_ARCH=X86_ARCH ;; + *,i[3456]86) PM2_ARCH=X86_ARCH ;; + BSD/OS,i[3456]86) PM2_ARCH=X86_ARCH ;; + FreeBSD,i386 ) PM2_ARCH=X86_ARCH ;; + *,x86_64) PM2_ARCH=X86_64_ARCH ;; + AIX*,*) PM2_ARCH=RS6K_ARCH ;; + *,alpha) PM2_ARCH=ALPHA_ARCH ;; + *,mips) PM2_ARCH=MIPS_ARCH ;; + *,IP*) PM2_ARCH=MIPS_ARCH ;; + *,ppc) PM2_ARCH=PPC_ARCH ;; + *,Power*) PM2_ARCH=PPC_ARCH ;; + *) PM2_ARCH=UNKNOWN_ARCH ;; + esac +fi + +echo $PM2_ARCH +exit + diff --git a/bin/nachos_sys b/bin/nachos_sys new file mode 100755 index 0000000..50b29a5 --- /dev/null +++ b/bin/nachos_sys @@ -0,0 +1,62 @@ +#!/bin/sh +# +# PM2 HIGH-PERF/ISOMALLOC +# High Performance Parallel Multithreaded Machine +# version 3.0 +# +# Gabriel Antoniu, Olivier Aumage, Luc Bouge, Vincent Danjean, +# Christian Perez, Jean-Francois Mehaut, Raymond Namyst +# +# Laboratoire de l'Informatique du Parallelisme +# UMR 5668 CNRS-INRIA +# Ecole Normale Superieure de Lyon +# +# External Contributors: +# Yves Denneulin (LMC - Grenoble), +# Benoit Planquelle (LIFL - Lille) +# +# 1998 All Rights Reserved +# +# +# NOTICE +# +# Permission to use, copy, modify, and distribute this software and +# its documentation for any purpose and without fee is hereby granted +# provided that the above copyright notice appear in all copies and +# that both the copyright notice and this permission notice appear in +# supporting documentation. +# +# Neither the institutions (Ecole Normale Superieure de Lyon, +# Laboratoire de L'informatique du Parallelisme, Universite des +# Sciences et Technologies de Lille, Laboratoire d'Informatique +# Fondamentale de Lille), nor the Authors make any representations +# about the suitability of this software for any purpose. This +# software is provided ``as is'' without express or implied warranty. +# + +# +# determine the machine type from scratch +# +if [ -f /bin/uname -o -f /usr/bin/uname ]; then + if [ -f /bin/uname ]; then + os="`/bin/uname -s`" + ht="`/bin/uname -m`" + else + os="`/usr/bin/uname -s`" + ht="`/usr/bin/uname -m`" + fi + + case "$os,$ht" in + SunOS,*) PM2_SYS=SOLARIS_SYS ;; + AIX*,*) PM2_SYS=AIX_SYS ;; + IRIX*,*) PM2_SYS=IRIX_SYS ;; + OSF*,*) PM2_SYS=OSF_SYS ;; + Linux,*) PM2_SYS=LINUX_SYS ;; + FreeBSD,*) PM2_SYS=FREEBSD_SYS ;; + Darwin,*) PM2_SYS=MAC_OS_SYS ;; + esac +fi + +echo $PM2_SYS +exit + diff --git a/code/Makefile b/code/Makefile new file mode 100644 index 0000000..22dcafb --- /dev/null +++ b/code/Makefile @@ -0,0 +1,62 @@ +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +MAKE := make + + + +DIRS := threads userprog +# filesys network vm + +.PHONY: all clean depend print + +all: + @set -e; \ + for i in $(DIRS); do \ + $(MAKE) -C $$i nachos; \ + done ; \ + for i in bin test; do \ + $(MAKE) -C $$i all; \ + done + +clean: + @set -e; \ + for i in $(DIRS) bin test; do \ + $(MAKE) -C $$i $@; \ + done + +depend: + @set -e; \ + for i in $(DIRS); do \ + $(MAKE) -C $$i $@; \ + done + +## LPR := echo +LPR := enscript --columns=2 --borders -r --landscape \ + --media=A4 --verbose --pretty-print --toc + +print: + -for dir in machine threads userprog filesys network vm test; \ + do \ + ls -f \ + $$dir/Makefile* \ + $${dir}/*.h \ + $${dir}/*.cc $${dir}/*.S \ + $${dir}/*.c; \ + done > list + $(LPR) `cat list` + +INDENT := indent --indent-level4 +DIRS_INDENT := threads userprog test + +indent: + -for dir in machine $(DIRS_INDENT) test; do \ + ls -f \ + $${dir}/*.h \ + $${dir}/*.cc \ + $${dir}/*.c; \ + done > list + for file in `cat list`; do \ + echo $${file}; $(INDENT) $${file}; \ + done diff --git a/code/Makefile.common b/code/Makefile.common new file mode 100644 index 0000000..edc397b --- /dev/null +++ b/code/Makefile.common @@ -0,0 +1,145 @@ +# This is part of a GNU Makefile, included by the Makefiles in +# each of the subdirectories. +# +# This file includes all of the baseline code provided by Nachos. +# Whenever you add a .h or .cc file, put it in the appropriate +# _H,_C, or _O list. +# +# The dependency graph between assignments is: +# 1. THREADS before everything else +# 2. USERPROG must come before VM +# 3. USERPROG can come before or after FILESYS, but if USERPROG comes +# before (as in this distribution), then it must define FILESYS_STUB +# +# Other than that, you have complete flexibility. +# + +# You might want to play with the CFLAGS, but if you use -O it may +# break the thread system. You might want to use -fno-inline if +# you need to call some inline functions from the debugger. + +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +########################################################################### +# RN: Sets of objects file to be linked for each nachos flavor. This +# is the only section you may need to modify when adding files in nachos. +# +THREAD_O := main.o list.o scheduler.o synch.o synchlist.o \ + system.o thread.o utility.o threadtest.o interrupt.o \ + stats.o sysdep.o timer.o + +USERPROG_O := addrspace.o bitmap.o exception.o progtest.o console.o \ + machine.o mipssim.o translate.o + +VM_O := + +FILESYS_O := directory.o filehdr.o filesys.o fstest.o openfile.o \ + synchdisk.o disk.o + +NETWORK_O := nettest.o post.o network.o +# +########################################################################### + +S_OFILES := switch.o + +CFLAGS = -g -Wall -Wextra -Wshadow $(INCPATH) $(DEFINES) $(HOST) -DCHANGED +LDFLAGS = -g +ASFLAGS = -g + +# These definitions may change as the software is updated. +# Some of them are also system dependent +CPP= gcc -E -P +CC = g++ +LD = g++ +AS = gcc -c + +PROGRAM := nachos + +MAKEFILES := Makefile \ + ../Makefile.common \ + ../Makefile.dep + +OFILES := $(strip $(C_OFILES) $(S_OFILES)) + +# RN: Dependency files start with a '.' to avoid annoying completion +# confusion under shell, emacs, etc. + +C_DFILES := $(patsubst %.o,.%.d,$(C_OFILES)) +S_DFILES := $(patsubst %.o,.%.d,$(S_OFILES)) +DFILES := $(strip $(C_DFILES) $(S_DFILES)) + +# RN: Hummm... There's a lot to tell about the story of the two +# following lines. Essentially, it allows 'make' to implicitely search +# for source/header files in various directories. The original Nachos +# Makefile was not using this 'vpath' feature, although it was using +# the aforementioned implicit search policy! The problem came from +# the rule: +# $(C_OFILES): %.o: +# $(CC) blabla... +# which is correct if .o files have no extra dependency rules. In this +# case, 'make' finds the associated .cc file in the directories it +# knows of (because they were listed in some macros!). However, if we add +# a rule such as +# $(C_OFILES): toto +# then 'make' tries to compile 'toto' each time it rebuilds a ".o" +# file. As a result, the former rule was modified as follows +# $(C_OFILES): %.o: %.cc +# As you may guess, it did'nt work because 'make' was no longer using +# its @#!^# implicit policy in this case. Crazy, isn't it? Fortunately, +# there's a GNU extension that requires 'make' to use a "search path" for +# files that match a given pattern: this is the vpath directive... +# Well, I guess you should stop wondering why these two lines are here: you'd +# better concentrate on your exercise! ;-) + +vpath %.cc ../machine:../threads:../userprog:../filesys:../network:../vm:../ +vpath %.S ../machine:../threads:../userprog:../filesys:../network:../vm:../ + +# Must be the first rule +.PHONY: default +default: $(PROGRAM) + +$(OFILES): $(MAKEFILES) + +$(PROGRAM): $(OFILES) + $(LD) $(OFILES) $(LDFLAGS) -o $(PROGRAM) + +$(C_OFILES): %.o: %.cc + $(CC) $(CFLAGS) -c $< -o $@ + +switch.o: ../threads/switch.S + $(CPP) $(INCPATH) $(HOST) ../threads/switch.S > swtch.s + $(AS) $(ASFLAGS) -o switch.o swtch.s + rm swtch.s + +# RN: Rules to generate dependency files. We can no longer rely on a +# single '.depend' file if we want to avoid a global recompilation each +# time a file is modified! Thus, each %.cc file is associated with its own +# .%.d dependency file... + +.PHONY: depend +depend: $(DFILES) + +$(DFILES): $(MAKEFILES) + +$(C_DFILES): .%.d: %.cc + $(CC) $(CFLAGS) -MM $< | \ + sed -e 's|\(.*\)\.o:|.\1.d \1.o:|g' > $@ + +$(S_DFILES): .%.d: %.S + $(CC) $(CFLAGS) -MM $< | \ + sed -e 's|\(.*\)\.o:|.\1.d \1.o:|g' > $@ + +ifneq ($(MAKECMDGOALS),clean) +-include $(DFILES) +endif + +# Cleaning rules +.PHONY: clean +clean: + rm -f core nachos DISK *.o *.s .*.d coff2noff out disassemble + +.PHONY: distclean +distclean: clean + rm -f *\~ \#*\# diff --git a/code/Makefile.dep b/code/Makefile.dep new file mode 100644 index 0000000..55deb72 --- /dev/null +++ b/code/Makefile.dep @@ -0,0 +1,61 @@ +# This is part of a GNU-Makefile, to specify system-dependent +# parts of the Makefile enviroment. +# +# This gets included as part of the GNU-Makefile used in each of +# the subdirectories. +# +# Depending on your platform, you need to select the correct definition. +# Also, you need to edit the Makefile in the bin subdirectory. + +NACHOS_ROOT = ../../ +NACHOS_SYS := $(shell $(NACHOS_ROOT)/bin/nachos_sys) +NACHOS_ARCH := $(shell $(NACHOS_ROOT)/bin/nachos_arch) +NACHOS_VALGRIND := $(shell test -r /usr/include/valgrind/valgrind.h && echo -DHAVE_VALGRIND) + +# DEC MIPS, Ultrix +# HOST = -DHOST_MIPS + +ifeq ($(NACHOS_ARCH),SPARC_ARCH) +HOST = -DHOST_SPARC -DHOST_IS_BIG_ENDIAN +endif +ifeq ($(NACHOS_ARCH),X86_ARCH) +HOST = -DHOST_i386 +endif +ifeq ($(NACHOS_ARCH),X86_64_ARCH) +HOST = -DHOST_x86_64 +endif +ifeq ($(NACHOS_ARCH),PPC_ARCH) +HOST = -DHOST_PPC -DHOST_IS_BIG_ENDIAN +endif + +HOST += $(NACHOS_VALGRIND) + +ifeq ($(NACHOS_SYS),SOLARIS_SYS) +LDFLAGS += -lnsl -lsocket +HOST += -DSOLARIS +endif + +ifeq ($(NACHOS_SYS),LINUX_SYS) +HOST += -DLINUX +CFLAGS += -fsanitize=undefined +LDFLAGS += -fsanitize=undefined + +#CFLAGS += -fsanitize=address +#LDFLAGS += -fsanitize=address -lpthread +endif + +ifeq ($(NACHOS_SYS),MAC_OS_SYS) +HOST += -DMAC_OS +endif + +# HP PA-RISC, HP_UX +# HOST = -DHOST_SNAKE -DHOST_IS_BIG_ENDIAN + +# 386, 386BSD Unix, or NetBSD Unix (available via anon ftp +# from agate.berkeley.edu) +# HOST = -DHOST_i386 +# CPP=/usr/bin/cpp + +# slight variant for 386 FreeBSD +# HOST = -DHOST_i386 -DFreeBSD +# CPP=/usr/bin/cpp diff --git a/code/bin/Makefile b/code/bin/Makefile new file mode 100644 index 0000000..d61e67c --- /dev/null +++ b/code/bin/Makefile @@ -0,0 +1,49 @@ +# Use regular make for this Makefile +# +# Makefile for: +# coff2noff -- converts a normal MIPS executable into a Nachos executable +# disassemble -- disassembles a normal MIPS executable +# +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +NACHOS_ROOT = ../../ +NACHOS_SYS := $(shell $(NACHOS_ROOT)/bin/nachos_sys) +NACHOS_ARCH := $(shell $(NACHOS_ROOT)/bin/nachos_arch) + +# If the host is big endian (SPARC, SNAKE, etc): +# change to (disassemble and coff2flat don't support big endian yet): +# CFLAGS= -I./ -I../threads -DHOST_IS_BIG_ENDIAN +# all: coff2noff + +CC=gcc +CFLAGS=-I./ -I../threads -Wall -Wextra -Wshadow +LD=gcc + +ifeq ($(NACHOS_ARCH),SPARC_ARCH) +CFLAGS += -DHOST_IS_BIG_ENDIAN +endif + +ifeq ($(NACHOS_ARCH),PPC_ARCH) +CFLAGS += -DHOST_IS_BIG_ENDIAN +endif + +all: coff2noff + +# converts a COFF file to Nachos object format +coff2noff: coff2noff.o + $(LD) coff2noff.o -o coff2noff + +# converts a COFF file to a flat address space (for Nachos version 2) +coff2flat: coff2flat.o + $(LD) coff2flat.o -o coff2flat + +# dis-assembles a COFF file +disassemble: out.o opstrings.o + $(LD) out.o opstrings.o -o disassemble + +# Cleaning rule +.PHONY: clean +clean: + rm -f core nachos DISK *.o *.s .*.d coff2noff out disassemble diff --git a/code/bin/coff.h b/code/bin/coff.h new file mode 100644 index 0000000..0c46dde --- /dev/null +++ b/code/bin/coff.h @@ -0,0 +1,49 @@ +/* coff.h + * Data structures that describe the MIPS COFF format. + */ + +struct filehdr { + unsigned short f_magic; /* magic number */ + unsigned short f_nscns; /* number of sections */ + int f_timdat; /* time & date stamp */ + int f_symptr; /* file pointer to symbolic header */ + int f_nsyms; /* sizeof(symbolic hdr) */ + unsigned short f_opthdr; /* sizeof(optional hdr) */ + unsigned short f_flags; /* flags */ + }; + +#define MIPSELMAGIC 0x0162 + +#define OMAGIC 0407 +#define SOMAGIC 0x0701 + +typedef struct aouthdr { + short magic; /* see above */ + short vstamp; /* version stamp */ + int tsize; /* text size in bytes, padded to DW bdry*/ + int dsize; /* initialized data " " */ + int bsize; /* uninitialized data " " */ + int entry; /* entry pt. */ + int text_start; /* base of text used for this file */ + int data_start; /* base of data used for this file */ + int bss_start; /* base of bss used for this file */ + int gprmask; /* general purpose register mask */ + int cprmask[4]; /* co-processor register masks */ + int gp_value; /* the gp value used for this object */ + } AOUTHDR; +#define AOUTHSZ sizeof(AOUTHDR) + + +struct scnhdr { + char s_name[8]; /* section name */ + int s_paddr; /* physical address, aliased s_nlib */ + int s_vaddr; /* virtual address */ + int s_size; /* section size */ + int s_scnptr; /* file ptr to raw data for section */ + int s_relptr; /* file ptr to relocation */ + int s_lnnoptr; /* file ptr to gp histogram */ + unsigned short s_nreloc; /* number of relocation entries */ + unsigned short s_nlnno; /* number of gp histogram entries */ + int s_flags; /* flags */ + }; + diff --git a/code/bin/coff2flat.c b/code/bin/coff2flat.c new file mode 100644 index 0000000..5b47228 --- /dev/null +++ b/code/bin/coff2flat.c @@ -0,0 +1,126 @@ +/* + Copyright (c) 1992 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +/* This program reads in a COFF format file, and outputs a flat file -- + * the flat file can then be copied directly to virtual memory and executed. + * In other words, the various pieces of the object code are loaded at + * the appropriate offset in the flat file. + * + * Assumes coff file compiled with -N -T 0 to make sure it's not shared text. + */ + +#define MAIN +#include "copyright.h" +#undef MAIN + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* NOTE -- once you have implemented large files, it's ok to make this bigger! */ +#define StackSize 1024 /* in bytes */ +#define ReadStruct(f,s) Read(f,&s,sizeof(s)) + +extern char *malloc(); + +/* read and check for error */ +void Read(int fd, void *buf, int nBytes) +{ + if (read(fd, buf, nBytes) != nBytes) { + fprintf(stderr, "File is too short\n"); + exit(1); + } +} + +/* write and check for error */ +void Write(int fd, void *buf, int nBytes) +{ + if (write(fd, buf, nBytes) != nBytes) { + fprintf(stderr, "Unable to write file\n"); + exit(1); + } +} + +/* do the real work */ +main (int argc, char **argv) +{ + int fdIn, fdOut, numsections, i, top, tmp; + struct filehdr fileh; + struct aouthdr systemh; + struct scnhdr *sections; + char *buffer; + + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + +/* open the object file (input) */ + fdIn = open(argv[1], O_RDONLY, 0); + if (fdIn == -1) { + perror(argv[1]); + exit(1); + } + +/* open the flat file (output) */ + fdOut = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0666); + if (fdIn == -1) { + perror(argv[2]); + exit(1); + } + +/* Read in the file header and check the magic number. */ + ReadStruct(fdIn,fileh); + if (fileh.f_magic != MIPSELMAGIC) { + fprintf(stderr, "File is not a MIPSEL COFF file\n"); + exit(1); + } + +/* Read in the system header and check the magic number */ + ReadStruct(fdIn,systemh); + if (systemh.magic != OMAGIC) { + fprintf(stderr, "File is not a OMAGIC file\n"); + exit(1); + } + +/* Read in the section headers. */ + numsections = fileh.f_nscns; + sections = (struct scnhdr *)malloc(fileh.f_nscns * sizeof(struct scnhdr)); + Read(fdIn, sections, fileh.f_nscns * sizeof(struct scnhdr)); + + /* Copy the segments in */ + printf("Loading %d sections:\n", fileh.f_nscns); + for (top = 0, i = 0; i < fileh.f_nscns; i++) { + printf("\t\"%s\", filepos 0x%x, mempos 0x%x, size 0x%x\n", + sections[i].s_name, sections[i].s_scnptr, + sections[i].s_paddr, sections[i].s_size); + if ((sections[i].s_paddr + sections[i].s_size) > top) + top = sections[i].s_paddr + sections[i].s_size; + if (strcmp(sections[i].s_name, ".bss") && /* no need to copy if .bss */ + strcmp(sections[i].s_name, ".sbss")) { + lseek(fdIn, sections[i].s_scnptr, 0); + buffer = malloc(sections[i].s_size); + Read(fdIn, buffer, sections[i].s_size); + Write(fdOut, buffer, sections[i].s_size); + free(buffer); + } + } +/* put a blank word at the end, so we know where the end is! */ + printf("Adding stack of size: %d\n", StackSize); + lseek(fdOut, top + StackSize - 4, 0); + tmp = 0; + Write(fdOut, &tmp, 4); + + close(fdIn); + close(fdOut); +} diff --git a/code/bin/coff2noff.c b/code/bin/coff2noff.c new file mode 100644 index 0000000..d6f8950 --- /dev/null +++ b/code/bin/coff2noff.c @@ -0,0 +1,236 @@ +/* coff2noff.c + * + * This program reads in a COFF format file, and outputs a NOFF format file. + * The NOFF format is essentially just a simpler version of the COFF file, + * recording where each segment is in the NOFF file, and where it is to + * go in the virtual address space. + * + * Assumes coff file is linked with either + * gld with -N -Ttext 0 + * ld with -N -T 0 + * to make sure the object file has no shared text. + * + * Also assumes that the COFF file has at most 3 segments: + * .text -- read-only executable instructions + * .data -- initialized data + * .bss/.sbss -- uninitialized data (should be zero'd on program startup) + * + * Copyright (c) 1992-1993 The Regents of the University of California. + * All rights reserved. See copyright.h for copyright notice and limitation + * of liability and disclaimer of warranty provisions. + */ + +#define MAIN +#include "copyright.h" +#undef MAIN + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coff.h" +#include "noff.h" + +/* Routines for converting words and short words to and from the + * simulated machine's format of little endian. These end up + * being NOPs when the host machine is little endian. + */ + +unsigned int +WordToHost(unsigned int word) { +#ifdef HOST_IS_BIG_ENDIAN + register unsigned long result; + result = (word >> 24) & 0x000000ff; + result |= (word >> 8) & 0x0000ff00; + result |= (word << 8) & 0x00ff0000; + result |= (word << 24) & 0xff000000; + return result; +#else + return word; +#endif /* HOST_IS_BIG_ENDIAN */ +} + +unsigned short +ShortToHost(unsigned short shortword) { +#if HOST_IS_BIG_ENDIAN + register unsigned short result; + result = (shortword << 8) & 0xff00; + result |= (shortword >> 8) & 0x00ff; + return result; +#else + return shortword; +#endif /* HOST_IS_BIG_ENDIAN */ +} + +#define ReadStruct(f,s) Read(f,&s,sizeof(s)) + +char *noffFileName = NULL; + +/* read and check for error */ +void Read(int fd, void *buf, int nBytes) +{ + if (read(fd, buf, nBytes) != nBytes) { + fprintf(stderr, "File is too short\n"); + unlink(noffFileName); + exit(1); + } +} + +/* write and check for error */ +void Write(int fd, void *buf, int nBytes) +{ + if (write(fd, buf, nBytes) != nBytes) { + fprintf(stderr, "Unable to write file\n"); + unlink(noffFileName); + exit(1); + } +} + +int main (int argc, char **argv) +{ + int fdIn, fdOut, numsections, i, inNoffFile; + struct filehdr fileh; + struct aouthdr systemh; + struct scnhdr *sections; + char *buffer; + NoffHeader noffH; + + if (argc < 2) { + fprintf(stderr, "%s\n", copyright); + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + +/* open the COFF file (input) */ + fdIn = open(argv[1], O_RDONLY, 0); + if (fdIn == -1) { + perror(argv[1]); + exit(1); + } + +/* open the NOFF file (output) */ + fdOut = open(argv[2], O_WRONLY|O_CREAT|O_TRUNC , 0666); + if (fdIn == -1) { + perror(argv[2]); + exit(1); + } + noffFileName = argv[2]; + +/* Read in the file header and check the magic number. */ + ReadStruct(fdIn,fileh); + fileh.f_magic = ShortToHost(fileh.f_magic); + fileh.f_nscns = ShortToHost(fileh.f_nscns); + if (fileh.f_magic != MIPSELMAGIC) { + fprintf(stderr, "File is not a MIPSEL COFF file\n"); + unlink(noffFileName); + exit(1); + } + +/* Read in the system header and check the magic number */ + ReadStruct(fdIn,systemh); + systemh.magic = ShortToHost(systemh.magic); + if (systemh.magic != OMAGIC) { + fprintf(stderr, "File is not a OMAGIC file\n"); + unlink(noffFileName); + exit(1); + } + +/* Read in the section headers. */ + numsections = fileh.f_nscns; + printf("numsections %d \n",numsections); + sections = (struct scnhdr *)malloc(numsections * sizeof(struct scnhdr)); + Read(fdIn, sections, numsections * sizeof(struct scnhdr)); + + for (i = 0; i < numsections; i++) { + sections[i].s_paddr = WordToHost(sections[i].s_paddr); + sections[i].s_size = WordToHost(sections[i].s_size); + sections[i].s_scnptr = WordToHost(sections[i].s_scnptr); + sections[i].s_name[7] = 0; + } + + /* initialize the NOFF header, in case not all the segments are defined + * in the COFF file + */ + noffH.noffMagic = NOFFMAGIC; + noffH.code.size = 0; + noffH.initData.size = 0; + noffH.uninitData.size = 0; + + /* Copy the segments in */ + inNoffFile = sizeof(NoffHeader); + lseek(fdOut, inNoffFile, SEEK_SET); + printf("Loading %d sections:\n", numsections); + for (i = 0; i < numsections; i++) { + printf("\t\"%s\", filepos 0x%x, mempos 0x%x, size 0x%x\n", + sections[i].s_name, sections[i].s_scnptr, + sections[i].s_paddr, sections[i].s_size); + if (sections[i].s_size == 0) { + /* do nothing! */ + } else if (!strcmp(sections[i].s_name, ".text")) { + noffH.code.virtualAddr = sections[i].s_paddr; + noffH.code.inFileAddr = inNoffFile; + noffH.code.size = sections[i].s_size; + lseek(fdIn, sections[i].s_scnptr, SEEK_SET); + buffer = malloc(sections[i].s_size); + Read(fdIn, buffer, sections[i].s_size); + Write(fdOut, buffer, sections[i].s_size); + free(buffer); + inNoffFile += sections[i].s_size; + } else if (!strcmp(sections[i].s_name, ".data") + || !strcmp(sections[i].s_name, ".rdata")) { + /* need to check if we have both .data and .rdata + * -- make sure one or the other is empty! */ + if (noffH.initData.size != 0) { + fprintf(stderr, "Can't handle both data and rdata\n"); + unlink(noffFileName); + exit(1); + } + noffH.initData.virtualAddr = sections[i].s_paddr; + noffH.initData.inFileAddr = inNoffFile; + noffH.initData.size = sections[i].s_size; + lseek(fdIn, sections[i].s_scnptr, SEEK_SET); + buffer = malloc(sections[i].s_size); + Read(fdIn, buffer, sections[i].s_size); + Write(fdOut, buffer, sections[i].s_size); + free(buffer); + inNoffFile += sections[i].s_size; + } else if (!strcmp(sections[i].s_name, ".bss") || + !strcmp(sections[i].s_name, ".sbss")) { + /* need to check if we have both .bss and .sbss -- make sure they + * are contiguous + */ + if (noffH.uninitData.size != 0) { + if (sections[i].s_paddr > (noffH.uninitData.virtualAddr + + noffH.uninitData.size)) { + fprintf(stderr, "Can't handle both bss and sbss\n"); + unlink(noffFileName); + exit(1); + } + noffH.uninitData.size += sections[i].s_size; + } else { + noffH.uninitData.virtualAddr = sections[i].s_paddr; + noffH.uninitData.inFileAddr = -1; /* bss is just zeros */ + noffH.uninitData.size = sections[i].s_size; + } + /* we don't need to copy the uninitialized data! */ + } else if (!strcmp(sections[i].s_name, ".drop")) { + /* Drop */ + } else if (!strncmp(sections[i].s_name, ".debug", 6)) { + /* Drop debug */ + } else { + fprintf(stderr, "Unknown segment type: %s\n", sections[i].s_name); + unlink(noffFileName); + exit(1); + } + } + lseek(fdOut, 0, SEEK_SET); + Write(fdOut, &noffH, sizeof(NoffHeader)); + close(fdIn); + close(fdOut); + exit(0); +} diff --git a/code/bin/d.c b/code/bin/d.c new file mode 100644 index 0000000..98dbe76 --- /dev/null +++ b/code/bin/d.c @@ -0,0 +1,219 @@ +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" +#include "instr.h" +#include "encode.h" + +#define NULL 0 + +int sptr; +int longdis = 1; + +extern char *normalops[], *specialops[]; + + +char *regstrings[] = +{ +"0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", +"r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", +"r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "gp", "sp", +"r30", "r31" +}; + +#define R(i) regstrings[i] + + +dump_ascii(instruction, pc) +int instruction, pc; +{ + int addr; + char *s; + int opcode; + + if ( longdis ) printf("%08x: %08x ", pc, instruction); + printf("\t"); + opcode = (unsigned) instruction >> 26; + if ( instruction == I_NOP) { + printf("nop"); + } + else if ( opcode == I_SPECIAL ) + { + opcode = instruction & 0x3f; + printf("%s\t", specialops[opcode]); + + switch( opcode ) + { + + /* rd,rt,shamt */ + case I_SLL: + case I_SRL: + case I_SRA: + printf("%s,%s,0x%x", + R(rd(instruction)), + R(rt(instruction)), + shamt(instruction)); + break; + + /* rd,rt,rs */ + case I_SLLV: + case I_SRLV: + case I_SRAV: + printf("%s,%s,%s", + R(rd(instruction)), + R(rt(instruction)), + R(rs(instruction))); + break; + + /* rs */ + case I_JR: + case I_JALR: + case I_MFLO: + case I_MTLO: + printf("%s", R(rs(instruction))); + break; + + case I_SYSCALL: + case I_BREAK: + break; + + /* rd */ + case I_MFHI: + case I_MTHI: + printf("%s", R(rd(instruction))); + break; + + /* rs,rt */ + case I_MULT: + case I_MULTU: + case I_DIV: + case I_DIVU: + printf("%s,%s", + R(rs(instruction)), + R(rt(instruction))); + break; + + /* rd,rs,rt */ + case I_ADD: + case I_ADDU: + case I_SUB: + case I_SUBU: + case I_AND: + case I_OR: + case I_XOR: + case I_NOR: + case I_SLT: + case I_SLTU: + printf("%s,%s,%s", + R(rd(instruction)), + R(rs(instruction)), + R(rt(instruction))); + break; + + } + } + else if ( opcode == I_BCOND ) + { + switch ( rt(instruction) ) /* this field encodes the op */ + { + case I_BLTZ: + printf("bltz"); + break; + case I_BGEZ: + printf("bgez"); + break; + case I_BLTZAL: + printf("bltzal"); + break; + case I_BGEZAL: + printf("bgezal"); + break; + default : + printf("BCOND"); + } + printf("\t%s,%08x", + R(rs(instruction)), + off16(instruction)+pc+4); + } + else + { + printf("%s\t", normalops[opcode]); + + switch ( opcode ) + { + /* 26-bit_target */ + case I_J: + case I_JAL: + printf("%08x", + top4(pc)|off26(instruction)); + break; + + /* rs,rt,16-bit_offset */ + case I_BEQ: + case I_BNE: + printf("%s,%s,%08x", + R(rt(instruction)), + R(rs(instruction)), + off16(instruction)+pc+4); + break; + + /* rt,rs,immediate */ + case I_ADDI: + case I_ADDIU: + case I_SLTI: + case I_SLTIU: + case I_ANDI: + case I_ORI: + case I_XORI: + printf("%s,%s,0x%x", + R(rt(instruction)), + R(rs(instruction)), + immed(instruction)); + break; + + /* rt, immed */ + case I_LUI: + printf("%s,0x%x", + R(rt(instruction)), + immed(instruction)); + break; + + /* coprocessor garbage */ + case I_COP0: + case I_COP1: + case I_COP2: + case I_COP3: + break; + + /* rt,offset(rs) */ + case I_LB: + case I_LH: + case I_LWL: + case I_LW: + case I_LBU: + case I_LHU: + case I_LWR: + case I_SB: + case I_SH: + case I_SWL: + case I_SW: + case I_SWR: + case I_LWC0: + case I_LWC1: + case I_LWC2: + case I_LWC3 : + case I_SWC0: + case I_SWC1: + case I_SWC2: + case I_SWC3: + printf("%s,0x%x(%s)", + R(rt(instruction)), + immed(instruction), + R(rs(instruction))); + break; + } + } +} diff --git a/code/bin/disasm.c b/code/bin/disasm.c new file mode 100644 index 0000000..8466352 --- /dev/null +++ b/code/bin/disasm.c @@ -0,0 +1,172 @@ +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" + +/* MIPS instruction disassembler */ + +#include +#include +#include +#include +#include +#include "int.h" + +static FILE *fp; +static LDFILE *ldptr; +static SCNHDR texthead, rdatahead, datahead, sdatahead, sbsshead, bsshead; + +static char filename[1000] = "a.out"; /* default a.out file */ +static char self[256]; /* name of invoking program */ + +char mem[MEMSIZE]; /* main memory. use malloc later */ +int TRACE, Traptrace, Regtrace; +int NROWS=64, ASSOC=1, LINESIZE=4, RAND=0, LRD=0; +int pc; + +extern char *strcpy(); + +main(argc, argv) +int argc; +char *argv[]; +{ + register char *s; + char *fakeargv[3]; + + strcpy(self, argv[0]); + while ( argc > 1 && argv[1][0] == '-' ) + { + --argc; ++argv; + for ( s=argv[0]+1; *s != '\0'; ++s ) + switch ( *s ) + { + } + } + + if (argc >= 2) + strcpy(filename, argv[1]); + fp = fopen(filename, "r"); + if (fp == NULL) + { + fprintf(stderr, "%s: Could not open '%s'\n", self, filename); + exit(0); + } + fclose(fp); + load_program(filename); + if ( argv[1] == NULL ) + { + fakeargv[1] = "a.out"; + fakeargv[2] = NULL; + argv = fakeargv; + ++argc; + } + disasm(memoffset, argc-1, argv+1); /* where things normally start */ +} + +#define LOADSECTION(head) load_section(&head); + +load_section(hd) +register SCNHDR *hd; +{ + register int pc, i; + if ( hd->s_scnptr != 0 ) { + /* printf("loading %s\n", hd->s_name); */ + pc = hd->s_vaddr; + FSEEK(ldptr, hd->s_scnptr, 0); + for ( i=0; is_size; ++i ) { + if (pc-memoffset >= MEMSIZE) + { printf("MEMSIZE too small. Fix and recompile.\n"); + exit(1); } + *(char *) ((mem-memoffset)+pc++) = getc(fp); + } + } +} + +load_program(filename) +char *filename; +{ + ldptr = ldopen(filename, NULL); + if ( ldptr == NULL ) + { + fprintf(stderr, "%s: Load read error on %s\n", self, filename); + exit(0); + } + if ( TYPE(ldptr) != 0x162 ) + { + fprintf(stderr, + "big-endian object file (little-endian interp)\n"); + exit(0); + } + + if ( ldnshread(ldptr, ".text", &texthead) != 1 ) + printf("text section header missing\n"); + else + LOADSECTION(texthead) + + if ( ldnshread(ldptr, ".rdata", &rdatahead) != 1 ) + printf("rdata section header missing\n"); + else + LOADSECTION(rdatahead) + + if ( ldnshread(ldptr, ".data", &datahead) != 1 ) + printf("data section header missing\n"); + else + LOADSECTION(datahead) + + if ( ldnshread(ldptr, ".sdata", &sdatahead) != 1 ) + printf("sdata section header missing\n"); + else + LOADSECTION(sdatahead) + + if ( ldnshread(ldptr, ".sbss", &sbsshead) != 1 ) + printf("sbss section header missing\n"); + else + LOADSECTION(sbsshead) + + if ( ldnshread(ldptr, ".bss", &bsshead) != 1 ) + printf("bss section header missing\n"); + else + LOADSECTION(bsshead) + + + /* BSS is already zeroed (statically-allocated mem) */ + /* this version ignores relocation info */ +} + + +int *m_alloc(n) +int n; +{ + extern char *malloc(); + + return (int *) (int) malloc((unsigned) n); +} + +disasm(startpc, argc, argv) +int startpc, argc; +char *argv[]; +{ + int i; + + pc = memoffset; + for ( i=0; i +#include "instr.h" +#include "encode.h" +#include "int.h" + +#define FAST 0 +#define true 1 +#define false 0 + + +extern char mem[]; +extern int TRACE, Regtrace; + +/* Machine registers */ +int Reg[32]; /* GPR's */ +int HI, LO; /* mul/div machine registers */ + +/* statistics gathering places */ +int numjmpls; +int arch1cycles; + +/* Condition-code calculations */ +#define b31(z) (((z) >>31 )&0x1) /* extract bit 31 */ + +/* code looks funny but is fast thanx to MIPS! */ +#define cc_add(rr, op1, op2) \ + N = (rr < 0); \ + Z = (rr == 0); \ + C = ((unsigned) rr < (unsigned) op2); \ + V = ((op1^op2) >= 0 && (op1^rr) < 0); + +#define cc_sub(rr, op1, op2) \ + N = (rr < 0); \ + Z = (rr == 0); \ + V = b31((op1 & ~op2 & ~rr) | (~op1 & op2 & rr)); \ + C = ((unsigned) op1 < (unsigned) op2); + + /* C = b31((~op1 & op2) | (rr & (~op1 | op2))); /* */ + +#define cc_logic(rr) \ + N = (rr < 0); \ + Z = (rr == 0); \ + V = 0; \ + C = 0; + +#define cc_mulscc(rr, op1, op2) \ + N = (rr < 0); \ + Z = (rr == 0); \ + V = b31((op1 & op2 & ~rr) | (~op1 & ~op2 & rr)); \ + C = b31((op1 & op2) | (~rr & (op1 | op2))); + + +runprogram(startpc, argc, argv) +int startpc, argc; +char *argv[]; +{ + int aci, ai, j; + register int instr, pc, xpc, npc; + register int i; /* temporary for local stuff */ + register int icount; + extern char *strcpy(); + + icount = 0; + pc = startpc; npc = pc + 4; + i = MEMSIZE - 1024 + memoffset; /* Initial SP value */ + Reg[29] = i; /* Initialize SP */ + /* setup argc and argv stuff (icky!) */ + store(i, argc); + aci = i + 4; + ai = aci + 32; + for ( j=0; j>26) & 0x0000003f) + { + case I_SPECIAL: + { + switch ( instr & 0x0000003f ) + { + + case I_SLL: + Reg[rd(instr)] = Reg[rt(instr)] << shamt(instr); + break; + case I_SRL: + Reg[rd(instr)] = + (unsigned) Reg[rt(instr)] >> shamt(instr); + break; + case I_SRA: + Reg[rd(instr)] = Reg[rt(instr)] >> shamt(instr); + break; + case I_SLLV: + Reg[rd(instr)] = Reg[rt(instr)] << Reg[rs(instr)]; + break; + case I_SRLV: + Reg[rd(instr)] = + (unsigned) Reg[rt(instr)] >> Reg[rs(instr)]; + break; + case I_SRAV: + Reg[rd(instr)] = Reg[rt(instr)] >> Reg[rs(instr)]; + break; + case I_JR: + npc = Reg[rs(instr)]; + break; + case I_JALR: + npc = Reg[rs(instr)]; + Reg[rd(instr)] = xpc + 8; + break; + + case I_SYSCALL: system_trap(); break; + case I_BREAK: system_break(); break; + + case I_MFHI: Reg[rd(instr)] = HI; break; + case I_MTHI: HI = Reg[rs(instr)]; break; + case I_MFLO: Reg[rd(instr)] = LO; break; + case I_MTLO: LO = Reg[rs(instr)]; break; + + case I_MULT: + { + int t1, t2; + int t1l, t1h, t2l, t2h; + int neg; + + t1 = Reg[rs(instr)]; + t2 = Reg[rt(instr)]; + neg = 0; + if ( t1 < 0 ) { t1 = -t1 ; neg = !neg; } + if ( t2 < 0 ) { t2 = -t2 ; neg = !neg; } + LO = t1 * t2; + t1l = t1 & 0xffff; + t1h = (t1 >> 16) & 0xffff; + t2l = t2 & 0xffff; + t2h = (t2 >> 16) & 0xffff; + HI = t1h*t2h+((t1h*t2l)>>16)+((t2h*t1l)>>16); + if ( neg ) + { + LO = ~LO; HI = ~HI; LO = LO + 1; + if ( LO == 0 ) HI = HI + 1; + } + } + break; + case I_MULTU: + { + int t1, t2; + int t1l, t1h, t2l, t2h; + + t1 = Reg[rs(instr)]; + t2 = Reg[rt(instr)]; + t1l = t1 & 0xffff; + t1h = (t1 >> 16) & 0xffff; + t2l = t2 & 0xffff; + t2h = (t2 >> 16) & 0xffff; + LO = t1*t2; + HI = t1h*t2h+((t1h*t2l)>>16)+((t2h*t1l)>>16); + }break; + case I_DIV: + LO = Reg[rs(instr)] / Reg[rt(instr)]; + HI = Reg[rs(instr)] % Reg[rt(instr)]; + break; + case I_DIVU: + LO = + (unsigned)Reg[rs(instr)] / (unsigned)Reg[rt(instr)]; + HI = + (unsigned)Reg[rs(instr)] % (unsigned)Reg[rt(instr)]; + break; + + case I_ADD: + case I_ADDU: + Reg[rd(instr)] = Reg[rs(instr)] + Reg[rt(instr)]; + break; + case I_SUB: + case I_SUBU: + Reg[rd(instr)] = Reg[rs(instr)] - Reg[rt(instr)]; + break; + case I_AND: + Reg[rd(instr)] = Reg[rs(instr)] & Reg[rt(instr)]; + break; + case I_OR: + Reg[rd(instr)] = Reg[rs(instr)] | Reg[rt(instr)]; + break; + case I_XOR: + Reg[rd(instr)] = Reg[rs(instr)] ^ Reg[rt(instr)]; + break; + case I_NOR: + Reg[rd(instr)] = ~(Reg[rs(instr)] | Reg[rt(instr)]); + break; + + case I_SLT: + Reg[rd(instr)] = (Reg[rs(instr)] < Reg[rt(instr)]); + break; + case I_SLTU: + Reg[rd(instr)] = + ((unsigned) Reg[rs(instr)] + < (unsigned) Reg[rt(instr)]); + break; + default: u(); break; + } + } break; + + case I_BCOND: + { + switch ( rt(instr) ) /* this field encodes the op */ + { + case I_BLTZ: + if ( Reg[rs(instr)] < 0 ) + npc = xpc + 4 + (immed(instr)<<2); + break; + case I_BGEZ: + if ( Reg[rs(instr)] >= 0 ) + npc = xpc + 4 + (immed(instr)<<2); + break; + + case I_BLTZAL: + Reg[31] = xpc + 8; + if ( Reg[rs(instr)] < 0 ) + npc = xpc + 4 + (immed(instr)<<2); + break; + case I_BGEZAL: + Reg[31] = xpc + 8; + if ( Reg[rs(instr)] >= 0 ) + npc = xpc + 4 + (immed(instr)<<2); + break; + default: u(); break; + } + + } break; + + case I_J: + npc = (xpc & 0xf0000000) | ((instr & 0x03ffffff) << 2); + break; + case I_JAL: + Reg[31] = xpc + 8; + npc = (xpc & 0xf0000000) | ((instr & 0x03ffffff) << 2); + break; + case I_BEQ: + if ( Reg[rs(instr)] == Reg[rt(instr)] ) + npc = xpc + 4 + (immed(instr) << 2); + break; + case I_BNE: + if ( Reg[rs(instr)] != Reg[rt(instr)] ) + npc = xpc + 4 + (immed(instr) << 2); + break; + case I_BLEZ: + if ( Reg[rs(instr)] <= 0 ) + npc = xpc + 4 + (immed(instr) << 2); + break; + case I_BGTZ: + if ( Reg[rs(instr)] > 0 ) + npc = xpc + 4 + (immed(instr) << 2); + break; + case I_ADDI: + Reg[rt(instr)] = Reg[rs(instr)] + immed(instr); + break; + case I_ADDIU: + Reg[rt(instr)] = Reg[rs(instr)] + immed(instr); + break; + case I_SLTI: + Reg[rt(instr)] = (Reg[rs(instr)] < immed(instr)); + break; + case I_SLTIU: + Reg[rt(instr)] = + ((unsigned) Reg[rs(instr)] < (unsigned) immed(instr)); + break; + case I_ANDI: + Reg[rt(instr)] = Reg[rs(instr)] & immed(instr); + break; + case I_ORI: + Reg[rt(instr)] = Reg[rs(instr)] | immed(instr); + break; + case I_XORI: + Reg[rt(instr)] = Reg[rs(instr)] ^ immed(instr); + break; + case I_LUI: + Reg[rt(instr)] = instr << 16; + break; + + case I_LB: + Reg[rt(instr)] = cfetch(Reg[rs(instr)] + immed(instr)); + break; + case I_LH: + Reg[rt(instr)] = sfetch(Reg[rs(instr)] + immed(instr)); + break; + case I_LWL: + i = Reg[rs(instr)] + immed(instr); + Reg[rt(instr)] &= (-1 >> 8*((-i) & 0x03)); + Reg[rt(instr)] |= ((fetch(i & 0xfffffffc)) << 8*(i & 0x03)); + break; + case I_LW: + Reg[rt(instr)] = fetch(Reg[rs(instr)] + immed(instr)); + break; + case I_LBU: + Reg[rt(instr)] = ucfetch(Reg[rs(instr)] + immed(instr)); + break; + case I_LHU: + Reg[rt(instr)] = usfetch(Reg[rs(instr)] + immed(instr)); + break; + case I_LWR: + i = Reg[rs(instr)] + immed(instr); + Reg[rt(instr)] &= (-1 << 8*(i & 0x03)); + if ( (i & 0x03)== 0 ) + Reg[rt(instr)] = 0; + Reg[rt(instr)] |= + ((fetch(i & 0xfffffffc)) >> 8*((-i) & 0x03)); + break; + + case I_SB: + cstore(Reg[rs(instr)] + immed(instr), Reg[rt(instr)]); + break; + case I_SH: + sstore(Reg[rs(instr)] + immed(instr), Reg[rt(instr)]); + break; + case I_SWL: + fprintf(stderr, "sorry, no SWL yet.\n"); + u(); + break; + case I_SW: + store(Reg[rs(instr)] + immed(instr), Reg[rt(instr)]); + break; + + case I_SWR: + fprintf(stderr, "sorry, no SWR yet.\n"); + u(); + break; + + case I_LWC0: case I_LWC1: + case I_LWC2: case I_LWC3: + case I_SWC0: case I_SWC1: + case I_SWC2: case I_SWC3: + case I_COP0: case I_COP1: + case I_COP2: case I_COP3: + fprintf(stderr, "Sorry, no coprocessors.\n"); + exit(2); + break; + + default: u(); break; + } + } + +#ifdef DEBUG +/* +printf(" %d(%x) = %d(%x) op %d(%x)\n", Reg[rd], Reg[rd], op1, op1, op2, op2); +/* */ +#endif +#if !FAST + if ( TRACE ) + { + dump_ascii(instr, xpc); printf("\n"); /* */ + if ( Regtrace ) dump_reg(); + } +#endif + } +} + + + +u() /* unimplemented */ +{ + printf("Unimplemented Instruction\n"); exit(2); +} + +ny() +{ + printf("This opcode not implemeted yet.\n"); exit(2); +} + + +/* debug aids */ + +RS(i) +int i; +{ + return rs(i); +} + +RT(i) +int i; +{ + return rt(i); +} + +RD(i) +int i; +{ + return rd(i); +} + +IM(i) +int i; +{ + return immed(i); +} + + + +dump_reg() +{ + int j; + + printf(" 0:"); for ( j=0; j<8; ++j ) printf(" %08x", Reg[j]); + printf("\n"); + printf(" 8:"); for ( ; j<16; ++j ) printf(" %08x", Reg[j]); + printf("\n"); + printf("16:"); for ( ; j<24; ++j ) printf(" %08x", Reg[j]); + printf("\n"); + printf("24:"); for ( ; j<32; ++j ) printf(" %08x", Reg[j]); + printf("\n"); + +} + + + +/* + 0 -> 0 + 1 -> 1 + 2 -> 1 + 3 -> 2 + 4 -> 2 + 5 -> 2 + 6 -> 2 + 7 -> 3 + 8 -> 3 + 9 -> 3 ... + Treats all ints as unsigned numbers. +*/ +ilog2(i) +int i; +{ + int j, l; + + if ( i == 0 ) return 0; + j = 0; + l = 1; + if ( (j=(i&0xffff0000)) != 0 ) { i = j; l += 16; } + if ( (j=(i&0xff00ff00)) != 0 ) { i = j; l += 8; } + if ( (j=(i&0xf0f0f0f0)) != 0 ) { i = j; l += 4; } + if ( (j=(i&0xcccccccc)) != 0 ) { i = j; l += 2; } + if ( (j=(i&0xaaaaaaaa)) != 0 ) { i = j; l += 1; } + return l; +} + + + +#define NH 32 +#define NNN 33 + +static int hists[NH][NNN]; +int hoflo[NH], htotal[NH]; + +void henters(n, hist) +int n, hist; +{ + if ( 0 <= n && n < NNN ) ++hists[hist][n]; else ++hoflo[hist]; + ++htotal[hist]; +} + +hprint() +{ + int h, i; + double I; + + for ( h=0; h<=NH; ++h ) if ( htotal[h] > 0 ) + { + printf("\nhisto %d:\n", h); + I = 0.0; + for ( i=0; i> 11) & 0x1f) +#define rt(i) (((i) >> 16) & 0x1f) +#define rs(i) (((i) >> 21) & 0x1f) +#define shamt(i) (((i) >> 6) & 0x1f) +#define immed(i) (((i) & 0x8000) ? (i)|(-0x8000) : (i)&0x7fff) + +#define off26(i) (((i)&((1<<26)-1))<<2) +#define top4(i) (((i)&(~((1<<28)-1)))) +#define off16(i) (immed(i)<<2) + +#define extend(i, hibitmask) (((i)&(hibitmask)) ? ((i)|(-(hibitmask))) : (i)) diff --git a/code/bin/int.h b/code/bin/int.h new file mode 100644 index 0000000..6c5d06a --- /dev/null +++ b/code/bin/int.h @@ -0,0 +1,31 @@ + +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" + + +#define MEMSIZE (1<<24) +#define memoffset 0x10000000 + +/* centralized memory-access primitives */ +#define amark(x) x +#define imark(x) x + +#define ifetch(addr) (*(int *)(int) (&(mem-memoffset)[imark(addr)])) +#define fetch(addr) (*(int *)(int) (&(mem-memoffset)[amark(addr)])) +#define sfetch(addr) (*(short *)(int) (&(mem-memoffset)[amark(addr)])) +#define usfetch(addr) (*(unsigned short *)(int)(&(mem-memoffset)[amark(addr)])) +#define cfetch(addr) (*(char *)(int) (&(mem-memoffset)[amark(addr)])) +#define ucfetch(addr) (*(unsigned char *)(int)(&(mem-memoffset)[amark(addr)])) + +#define store(addr, i) \ + ((*(int *)(int) (&(mem-memoffset)[amark(addr)]) = (i))) +#define sstore(addr, i) \ + ((*(short *)(int) (&(mem-memoffset)[amark(addr)]) = (i))) +#define cstore(addr, i) \ + (((mem-memoffset)[amark(addr)] = (i))) + diff --git a/code/bin/main.c b/code/bin/main.c new file mode 100644 index 0000000..a184e0b --- /dev/null +++ b/code/bin/main.c @@ -0,0 +1,167 @@ +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" + +/* MIPS instruction interpreter */ + +#include +#include +#include +#include +#include +#include "int.h" + +static FILE *fp; +static LDFILE *ldptr; +static SCNHDR texthead, rdatahead, datahead, sdatahead, sbsshead, bsshead; + +static char filename[1000] = "a.out"; /* default a.out file */ +static char self[256]; /* name of invoking program */ + +char mem[MEMSIZE]; /* main memory. use malloc later */ +int TRACE, Traptrace, Regtrace; +int NROWS=64, ASSOC=1, LINESIZE=4, RAND=0, LRD=0; + +extern char *strcpy(); + +main(argc, argv) +int argc; +char *argv[]; +{ + register char *s; + char *fakeargv[3]; + + strcpy(self, argv[0]); + while ( argc > 1 && argv[1][0] == '-' ) + { + --argc; ++argv; + for ( s=argv[0]+1; *s != '\0'; ++s ) + switch ( *s ) + { + case 't': TRACE = 1; break; + case 'T': Traptrace = 1; break; + case 'r': Regtrace = 1; break; + case 'm': + NROWS = atoi(*++argv); + ASSOC = atoi(*++argv); + LINESIZE = atoi(*++argv); + RAND = ((*++argv)[0] == 'r'); + LRD = ((*argv)[0] == 'l') + && ((*argv)[1] == 'r') + && ((*argv)[2] == 'd'); + argc -= 4; + break; + } + } + + if (argc >= 2) + strcpy(filename, argv[1]); + fp = fopen(filename, "r"); + if (fp == NULL) + { + fprintf(stderr, "%s: Could not open '%s'\n", self, filename); + exit(0); + } + fclose(fp); + load_program(filename); + if ( argv[1] == NULL ) + { + fakeargv[1] = "a.out"; + fakeargv[2] = NULL; + argv = fakeargv; + ++argc; + } + runprogram(memoffset, argc-1, argv+1); /* where things normally start */ +} + +char *string(s) +char *s; +{ + char *p; + extern char *malloc(); + + p = malloc((unsigned) strlen(s)+1); + strcpy(p, s); + return p; +} + +load_program(filename) +char *filename; +{ + register int pc, i, j, strindex, stl; + char str[1111]; + int rc1, rc2; + + ldptr = ldopen(filename, NULL); + if ( ldptr == NULL ) + { + fprintf(stderr, "%s: Load read error on %s\n", self, filename); + exit(0); + } + if ( TYPE(ldptr) != 0x162 ) + { + fprintf(stderr, + "big-endian object file (little-endian interp)\n"); + exit(0); + } + +#define LOADSECTION(head) \ + if ( head.s_scnptr != 0 ) \ + { \ + /* printf("loading %s\n", head.s_name); /* */ \ + pc = head.s_vaddr; \ + FSEEK(ldptr, head.s_scnptr, 0); \ + for ( i=0; i= MEMSIZE) \ + { printf("MEMSIZE too small. Fix and recompile.\n"); \ + exit(1); } \ + } + + if ( ldnshread(ldptr, ".text", &texthead) != 1 ) + printf("text section header missing\n"); + else + LOADSECTION(texthead) + + if ( ldnshread(ldptr, ".rdata", &rdatahead) != 1 ) + printf("rdata section header missing\n"); + else + LOADSECTION(rdatahead) + + if ( ldnshread(ldptr, ".data", &datahead) != 1 ) + printf("data section header missing\n"); + else + LOADSECTION(datahead) + + if ( ldnshread(ldptr, ".sdata", &sdatahead) != 1 ) + printf("sdata section header missing\n"); + else + LOADSECTION(sdatahead) + + if ( ldnshread(ldptr, ".sbss", &sbsshead) != 1 ) + printf("sbss section header missing\n"); + else + LOADSECTION(sbsshead) + + if ( ldnshread(ldptr, ".bss", &bsshead) != 1 ) + printf("bss section header missing\n"); + else + LOADSECTION(bsshead) + + /* BSS is already zeroed (statically-allocated mem) */ + /* this version ignores relocation info */ +} + + +int *m_alloc(n) +int n; +{ + extern char *malloc(); + + return (int *) (int) malloc((unsigned) n); +} + diff --git a/code/bin/noff.h b/code/bin/noff.h new file mode 100644 index 0000000..63e6aeb --- /dev/null +++ b/code/bin/noff.h @@ -0,0 +1,28 @@ +/* noff.h + * Data structures defining the Nachos Object Code Format + * + * Basically, we only know about three types of segments: + * code (read-only), initialized data, and unitialized data + */ + +#ifndef _NOFF_H +#define _NOFF_H +#define NOFFMAGIC 0xbadfad /* magic number denoting Nachos + * object code file + */ + +typedef struct segment { + int virtualAddr; /* location of segment in virt addr space */ + int inFileAddr; /* location of segment in this file */ + int size; /* size of segment */ +} Segment; + +typedef struct noffHeader { + int noffMagic; /* should be NOFFMAGIC */ + Segment code; /* executable code segment */ + Segment initData; /* initialized data segment */ + Segment uninitData; /* uninitialized data segment -- + * should be zero'ed before use + */ +} NoffHeader; +#endif /* _NOFF_H */ diff --git a/code/bin/opstrings.c b/code/bin/opstrings.c new file mode 100644 index 0000000..6210254 --- /dev/null +++ b/code/bin/opstrings.c @@ -0,0 +1,142 @@ +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" + +char *normalops[] = { + "special", + "bcond", + "j", + "jal", + "beq", + "bne", + "blez", + "bgtz", + "addi", + "addiu", + "slti", + "sltiu", + "andi", + "ori", + "xori", + "lui", + "cop0", + "cop1", + "cop2", + "cop3", + "024", + "025", + "026", + "027", + "030", + "031", + "032", + "033", + "034", + "035", + "036", + "037", + "lb", + "lh", + "lwl", + "lw", + "lbu", + "lhu", + "lwr", + "047", + "sb", + "sh", + "swl", + "sw", + "054", + "055", + "swr", + "057", + "lwc0", + "lwc1", + "lwc2", + "lwc3", + "064", + "065", + "066", + "067", + "swc0", + "swc1", + "swc2", + "swc3", + "074", + "075", + "076", + "077" +}; + +char *specialops[] = { + "sll", + "001", + "srl", + "sra", + "sllv", + "005", + "srlv", + "srav", + "jr", + "jalr", + "012", + "013", + "syscall", + "break", + "016", + "017", + "mfhi", + "mthi", + "mflo", + "mtlo", + "024", + "025", + "026", + "027", + "mult", + "multu", + "div", + "divu", + "034", + "035", + "036", + "037", + "add", + "addu", + "sub", + "subu", + "and", + "or", + "xor", + "nor", + "050", + "051", + "slt", + "sltu", + "054", + "055", + "056", + "057", + "060", + "061", + "062", + "063", + "064", + "065", + "066", + "067", + "070", + "071", + "072", + "073", + "074", + "075", + "076", + "077", +}; + diff --git a/code/bin/out.c b/code/bin/out.c new file mode 100644 index 0000000..0815018 --- /dev/null +++ b/code/bin/out.c @@ -0,0 +1,256 @@ +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#define MAIN +#include "copyright.h" +#undef MAIN + +/* + * OUT.C + * Looking at a.out formats. + * + * First task: + * Look at mips COFF stuff: + * Print out the contents of a file and do the following: + * For data, print the value and give relocation information + * For code, disassemble and give relocation information + */ + +#include +#include +#include +#include +#include +#include + +#define read_struct(f,s) (fread(&s,sizeof(s),1,f)==1) + +#define MAXRELOCS 1000 + + +#define MAXDATA 10000 + +struct data { + long data[MAXDATA]; + struct reloc reloc[MAXRELOCS]; + int length; + int relocs; +}; + +#define MAXSCNS 10 +#define MAXSYMS 300 +#define MAXSSPACE 20000 + +struct filehdr filehdr; +struct aouthdr aouthdr; +struct scnhdr scnhdr[MAXSCNS]; +struct data section[MAXSCNS]; +HDRR symhdr; +EXTR symbols[MAXSYMS]; +char sspace[20000]; + +char *symbol_type[] = { + "Nil", "Global", "Static", "Param", "Local", "Label", "Proc", "Block", + "End", "Member", "Type", "File", "Register", "Forward", "StaticProc", + "Constant" }; + +char *storage_class[] = { + "Nil", "Text", "Data", "Bss", "Register", "Abs", "Undefined", "CdbLocal", + "Bits", "CdbSystem", "RegImage", "Info", "UserStruct", "SData", "SBss", + "RData", "Var", "Common", "SCommon", "VarRegister", "Variant", "SUndefined", + "Init" }; + +main(argc,argv) +int argc; +char *argv[]; +{ + char *filename = "a.out"; + FILE *f; + int i; + long l; +/* EXTR filesym; */ + char buf[100]; + + if (argc == 2) filename = argv[1]; + if ((f = fopen(filename,"r")) == NULL) { + printf("out: could not open %s\n",filename); + perror("out"); + exit(1); + } + if (!read_struct(f,filehdr) || + !read_struct(f,aouthdr) || + filehdr.f_magic != MIPSELMAGIC) { + printf("out: %s is not a MIPS Little-Endian COFF object file\n",filename); + exit(1); + } + if (filehdr.f_nscns > MAXSCNS) { + printf("out: Too many COFF sections.\n"); + exit(1); + } + for (i=0; i < filehdr.f_nscns; ++i) { + read_struct(f,scnhdr[i]); + if (scnhdr[i].s_size > MAXDATA*sizeof(long) && + scnhdr[i].s_scnptr != 0 || + scnhdr[i].s_nreloc > MAXRELOCS) { + printf("section %s is too big.\n",scnhdr[i].s_name); + exit(1); + } + } + for (i=0; i < filehdr.f_nscns; ++i) { + if (scnhdr[i].s_scnptr != 0) { + section[i].length = scnhdr[i].s_size/4; + fseek(f,scnhdr[i].s_scnptr,0); + fread(section[i].data,sizeof(long),section[i].length,f); + section[i].relocs = scnhdr[i].s_nreloc; + fseek(f,scnhdr[i].s_relptr,0); + fread(section[i].reloc,sizeof(struct reloc),section[i].relocs,f); + } else { + section[i].length = 0; + } + } + fseek(f,filehdr.f_symptr,0); + read_struct(f,symhdr); + if (symhdr.iextMax > MAXSYMS) { + printf("too many symbols to store.\n"); + } + fseek(f,symhdr.cbExtOffset,0); + for (i=0; i < MAXSYMS && i MAXSSPACE) { + printf("too large a string space.\n"); + exit(1); + } + fseek(f,symhdr.cbSsExtOffset,0); + fread(sspace,1,symhdr.issExtMax,f); + + for (i=0; isc == scUndefined) myprintf("\t "); + else myprintf("%08x ",sym->value); + myprintf("%s",symbol_type[sym->st]); + mytab(25); + myprintf("%s",storage_class[sym->sc]); + mytab(41); + myprintf("%s\n",&sspace[sym->iss]); + } + return 0; +} + +static column = 1; +static FILE *outfile = stdout; + +#include +/*VARARGS0*/ +myprintf(va_alist) +va_dcl +{ + va_list ap; + char *form; + char buf[100]; + + va_start(ap); + form = va_arg(ap,char *); + vsprintf(buf,form,ap); + va_end(ap); + + fputs(buf,outfile); + + for (form = buf; *form != '\0'; ++form) { + if (*form == '\n') column = 1; + else if (*form == '\t') column = ((column + 7)&~7)+1; + else column += 1; + } +} + +mytab(n) +int n; +{ + while (column < n) { + fputc(' ',outfile); + ++column; + } + return column == n; +} + +mysetfile(f) +FILE *f; +{ + outfile = f; +} + +#define printf myprintf +#include "d.c" + +print_section(i) +int i; +{ + int j,k; + int is_text; + long pc; + long word; + char *s; + + printf("Section: %s\t%d/%d\n",scnhdr[i].s_name, + scnhdr[i].s_size,section[i].relocs); + is_text = (strncmp(scnhdr[i].s_name,".text",5) == 0); + + for (j=0; j < section[i].length; ++j) { + pc = scnhdr[i].s_vaddr+j*4; + word = section[i].data[j]; + if (is_text) { + dump_ascii(word,pc); + } else { + printf("%08x: %08x ", pc,word); + s = (char *)&word; + for (k=0;k<4;++k) { + if (s[k] >= ' ' && s[k] < 127) printf("%c",s[k]); + else printf("."); + } + printf("\t%d",word); + } + print_reloc(pc,i,j); + } +} + +char *section_name[] = { + "(null)", ".text", ".rdata", ".data", ".sdata", ".sbss", ".bss", + ".init", ".lit8", ".lit4" +}; + +char *reloc_type[] = { + "abs", "16", "32", "26", "hi16", "lo16", "gpdata", "gplit" +}; + +print_reloc(vaddr,i,j) +int i,j; +{ + int k; + struct reloc *rp; + for (k=0; k < section[i].relocs; ++k) { + rp = §ion[i].reloc[k]; + if (vaddr == rp->r_vaddr) { + mytab(57); + if (rp->r_extern) { + if (rp->r_symndx >= MAXSYMS) { + printf("sym $%d",rp->r_symndx); + } else { + printf("\"%s\"",&sspace[symbols[rp->r_symndx].asym.iss]); + } + } else { + printf("%s",section_name[rp->r_symndx]); + } + printf(" %s",reloc_type[rp->r_type]); + break; + } + } + printf("\n"); +} diff --git a/code/bin/system.c b/code/bin/system.c new file mode 100644 index 0000000..d2d347c --- /dev/null +++ b/code/bin/system.c @@ -0,0 +1,143 @@ +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" +#include +#include +#include "int.h" + +extern int Reg[]; +extern char mem[]; +extern int Traptrace; + +char *u_to_int_addr(); + +/* handle system calls */ +system_break() +{ + if ( Traptrace ) + printf("**breakpoint "); + system_trap(); +} + +system_trap() +{ + int o0, o1, o2; /* user out register values */ + int syscallno; + extern long lseek(); + + if ( Traptrace ) + { + printf("**System call %d\n", Reg[2]); + dump_reg(); + } + +/* if (Reg[1] == 0) +/* { /* SYS_indir */ +/* syscallno = Reg[8]; /* out reg 0 */ +/* o0 = Reg[9]; +/* o1 = Reg[10]; +/* o2 = Reg[11]; +/* } +/* else /* */ + { + syscallno = Reg[2]; + o0 = Reg[4]; + o1 = Reg[5]; + o2 = Reg[6]; + } + + switch (syscallno) + { + case SYS_exit: /*1*/ + printstatistics(); + fflush(stdout); + exit(0); + break; + case SYS_read: /*3*/ + Reg[1] = + read(u_to_int_fd(o0), u_to_int_addr(o1), o2); + break; + case SYS_write: /*4*/ + Reg[1] = + write(u_to_int_fd(o0), u_to_int_addr(o1), o2); + break; + + case SYS_open: /*5*/ + Reg[1] = open(u_to_int_addr(o0), o1, o2); /* */ + break; + + case SYS_close: /*6*/ + Reg[1] = 0; /* hack */ + break; + + case 17: /* 17 */ + /* old sbreak. where did it go? */ + Reg[1] = ((o0 / 8192) + 1) * 8192; + break; + + case SYS_lseek: /*19*/ + Reg[1] = (int) lseek(u_to_int_fd(o0), (long) o1, o2); + break; + + case SYS_ioctl:/* 54 */ + { /* copied from sas -- I don't understand yet. */ + /* see dave weaver */ +#define IOCPARM_MASK 0x7f /* parameters must be < 128 bytes */ + int size = (o1 >> 16) & IOCPARM_MASK; + char ioctl_group = (o1 >> 8) & 0x00ff; + if ((ioctl_group == 't') && (size == 8)) + { + size = 6; + o1 = (o1 & ~((IOCPARM_MASK << 16))) + | (size << 16); + } + } + Reg[1] = ioctl(u_to_int_fd(o0),o1,u_to_int_addr(o2)); + Reg[1] = 0; /* hack */ + break; + + case SYS_fstat: /* 62 */ + Reg[1] = fstat(o1, o2); + break; + + case SYS_getpagesize: /* 64 */ + Reg[1] = getpagesize(); + break; + + default: + printf("Unknown System call %d\n", syscallno); + if ( ! Traptrace ) + dump_reg(); + exit(2); + break; + } + if ( Traptrace ) + { + printf("**Afterwards:\n"); + dump_reg(); + } +} + +char *u_to_int_addr(ptr) +int ptr; +{ /* convert a user pointer to the real address */ + /* used in the interpreter */ + + return ((char *) ((int) mem - memoffset + ptr)); +} + +u_to_int_fd(fd) +{ + if (fd > 2) + { + /* + printf("No general file descriptors yet\n"); + exit(2); + */ + } + return (fd); /* assume we can handle it for now */ +} diff --git a/code/filesys/Makefile b/code/filesys/Makefile new file mode 100644 index 0000000..8422ddc --- /dev/null +++ b/code/filesys/Makefile @@ -0,0 +1,23 @@ +# NOTE: this is a GNU Makefile. You must use "gmake" rather than "make". +# +# Makefile for the file system assignment +# Defines set up assuming multiprogramming and virtual memory done first. +# If not, use the "bare bones" defines below. +# +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +DEFINES =-DTHREADS -DUSER_PROGRAM -DVM -DFILESYS_NEEDED -DFILESYS +INCPATH = -I../filesys -I../bin -I../vm -I../userprog -I../threads -I../machine +C_OFILES = $(THREAD_O) $(USERPROG_O) $(VM_O) $(FILESYS_O) + +# bare bones version +# DEFINES =-DTHREADS -DFILESYS_NEEDED -DFILESYS +# INCPATH = -I../filesys -I../threads -I../machine +# C_OFILES = $(THREAD_O) $(FILESYS_O) + +include ../Makefile.common +include ../Makefile.dep + + diff --git a/code/filesys/directory.cc b/code/filesys/directory.cc new file mode 100644 index 0000000..9ea134f --- /dev/null +++ b/code/filesys/directory.cc @@ -0,0 +1,198 @@ +// directory.cc +// Routines to manage a directory of file names. +// +// The directory is a table of fixed length entries; each +// entry represents a single file, and contains the file name, +// and the location of the file header on disk. The fixed size +// of each directory entry means that we have the restriction +// of a fixed maximum size for file names. +// +// The constructor initializes an empty directory of a certain size; +// we use ReadFrom/WriteBack to fetch the contents of the directory +// from disk, and to write back any modifications back to disk. +// +// Also, this implementation has the restriction that the size +// of the directory cannot expand. In other words, once all the +// entries in the directory are used, no more files can be created. +// Fixing this is one of the parts to the assignment. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "utility.h" +#include "filehdr.h" +#include "directory.h" + +//---------------------------------------------------------------------- +// Directory::Directory +// Initialize a directory; initially, the directory is completely +// empty. If the disk is being formatted, an empty directory +// is all we need, but otherwise, we need to call FetchFrom in order +// to initialize it from disk. +// +// "size" is the number of entries in the directory +//---------------------------------------------------------------------- + +Directory::Directory(int size) +{ + table = new DirectoryEntry[size]; + tableSize = size; + for (int i = 0; i < tableSize; i++) + table[i].inUse = FALSE; +} + +//---------------------------------------------------------------------- +// Directory::~Directory +// De-allocate directory data structure. +//---------------------------------------------------------------------- + +Directory::~Directory() +{ + delete [] table; + table = NULL; +} + +//---------------------------------------------------------------------- +// Directory::FetchFrom +// Read the contents of the directory from disk. +// +// "file" -- file containing the directory contents +//---------------------------------------------------------------------- + +void +Directory::FetchFrom(OpenFile *file) +{ + (void) file->ReadAt(table, tableSize * sizeof(DirectoryEntry), 0); +} + +//---------------------------------------------------------------------- +// Directory::WriteBack +// Write any modifications to the directory back to disk +// +// "file" -- file to contain the new directory contents +//---------------------------------------------------------------------- + +void +Directory::WriteBack(OpenFile *file) +{ + (void) file->WriteAt(table, tableSize * sizeof(DirectoryEntry), 0); +} + +//---------------------------------------------------------------------- +// Directory::FindIndex +// Look up file name in directory, and return its location in the table of +// directory entries. Return -1 if the name isn't in the directory. +// +// "name" -- the file name to look up +//---------------------------------------------------------------------- + +int +Directory::FindIndex(const char *name) +{ + for (int i = 0; i < tableSize; i++) + if (table[i].inUse && !strncmp(table[i].name, name, FileNameMaxLen)) + return i; + return -1; // name not in directory +} + +//---------------------------------------------------------------------- +// Directory::Find +// Look up file name in directory, and return the disk sector number +// where the file's header is stored. Return -1 if the name isn't +// in the directory. +// +// "name" -- the file name to look up +//---------------------------------------------------------------------- + +int +Directory::Find(const char *name) +{ + int i = FindIndex(name); + + if (i != -1) + return table[i].sector; + return -1; +} + +//---------------------------------------------------------------------- +// Directory::Add +// Add a file into the directory. Return TRUE if successful; +// return FALSE if the file name is already in the directory, or if +// the directory is completely full, and has no more space for +// additional file names. +// +// "name" -- the name of the file being added +// "newSector" -- the disk sector containing the added file's header +//---------------------------------------------------------------------- + +bool +Directory::Add(const char *name, int newSector) +{ + if (FindIndex(name) != -1) + return FALSE; + + for (int i = 0; i < tableSize; i++) + if (!table[i].inUse) { + table[i].inUse = TRUE; + strncpy(table[i].name, name, FileNameMaxLen); + table[i].sector = newSector; + return TRUE; + } + return FALSE; // no space. Fix when we have extensible files. +} + +//---------------------------------------------------------------------- +// Directory::Remove +// Remove a file name from the directory. Return TRUE if successful; +// return FALSE if the file isn't in the directory. +// +// "name" -- the file name to be removed +//---------------------------------------------------------------------- + +bool +Directory::Remove(const char *name) +{ + int i = FindIndex(name); + + if (i == -1) + return FALSE; // name not in directory + table[i].inUse = FALSE; + return TRUE; +} + +//---------------------------------------------------------------------- +// Directory::List +// List all the file names in the directory. +//---------------------------------------------------------------------- + +void +Directory::List() +{ + for (int i = 0; i < tableSize; i++) + if (table[i].inUse) + printf("%s\n", table[i].name); +} + +//---------------------------------------------------------------------- +// Directory::Print +// List all the file names in the directory, their FileHeader locations, +// and the contents of each file. For debugging. +//---------------------------------------------------------------------- + +void +Directory::Print() +{ + FileHeader *hdr = new FileHeader; + + printf("Directory contents:\n"); + for (int i = 0; i < tableSize; i++) + if (table[i].inUse) { + printf("Name: %s, Sector: %d\n", table[i].name, table[i].sector); + hdr->FetchFrom(table[i].sector); + hdr->Print(); + } + printf("\n"); + delete hdr; +} diff --git a/code/filesys/directory.h b/code/filesys/directory.h new file mode 100644 index 0000000..3844604 --- /dev/null +++ b/code/filesys/directory.h @@ -0,0 +1,83 @@ +// directory.h +// Data structures to manage a UNIX-like directory of file names. +// +// A directory is a table of pairs: , +// giving the name of each file in the directory, and +// where to find its file header (the data structure describing +// where to find the file's data blocks) on disk. +// +// We assume mutual exclusion is provided by the caller. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#ifndef DIRECTORY_H +#define DIRECTORY_H + +#include "openfile.h" + +#define FileNameMaxLen 9 // for simplicity, we assume + // file names are <= 9 characters long + +// The following class defines a "directory entry", representing a file +// in the directory. Each entry gives the name of the file, and where +// the file's header is to be found on disk. +// +// Internal data structures kept public so that Directory operations can +// access them directly. + +class DirectoryEntry { + public: + bool inUse; // Is this directory entry in use? + int sector; // Location on disk to find the + // FileHeader for this file + char name[FileNameMaxLen + 1]; // Text name for file, with +1 for + // the trailing '\0' +}; + +// The following class defines a UNIX-like "directory". Each entry in +// the directory describes a file, and where to find it on disk. +// +// The directory data structure can be stored in memory, or on disk. +// When it is on disk, it is stored as a regular Nachos file. +// +// The constructor initializes a directory structure in memory; the +// FetchFrom/WriteBack operations shuffle the directory information +// from/to disk. + +class Directory:public dontcopythis { + public: + Directory(int size); // Initialize an empty directory + // with space for "size" files + ~Directory(); // De-allocate the directory + + void FetchFrom(OpenFile *file); // Init directory contents from disk + void WriteBack(OpenFile *file); // Write modifications to + // directory contents back to disk + + int Find(const char *name); // Find the sector number of the + // FileHeader for file: "name" + + bool Add(const char *name, int newSector); // Add a file name into the directory + + bool Remove(const char *name); // Remove a file from the directory + + void List(); // Print the names of all the files + // in the directory + void Print(); // Verbose print of the contents + // of the directory -- all the file + // names and their contents. + + private: + int tableSize; // Number of directory entries + DirectoryEntry *table; // Table of pairs: + // + + int FindIndex(const char *name); // Find the index into the directory + // table corresponding to "name" +}; + +#endif // DIRECTORY_H diff --git a/code/filesys/filehdr.cc b/code/filesys/filehdr.cc new file mode 100644 index 0000000..eeb6692 --- /dev/null +++ b/code/filesys/filehdr.cc @@ -0,0 +1,150 @@ +// filehdr.cc +// Routines for managing the disk file header (in UNIX, this +// would be called the i-node). +// +// The file header is used to locate where on disk the +// file's data is stored. We implement this as a fixed size +// table of pointers -- each entry in the table points to the +// disk sector containing that portion of the file data +// (in other words, there are no indirect or doubly indirect +// blocks). The table size is chosen so that the file header +// will be just big enough to fit in one disk sector, +// +// Unlike in a real system, we do not keep track of file permissions, +// ownership, last modification date, etc., in the file header. +// +// A file header can be initialized in two ways: +// for a new file, by modifying the in-memory data structure +// to point to the newly allocated data blocks +// for a file already on disk, by reading the file header from disk +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#include "system.h" +#include "filehdr.h" + +//---------------------------------------------------------------------- +// FileHeader::Allocate +// Initialize a fresh file header for a newly created file. +// Allocate data blocks for the file out of the map of free disk blocks. +// Return FALSE if there are not enough free blocks to accomodate +// the new file. +// +// "freeMap" is the bit map of free disk sectors +// "fileSize" is the bit map of free disk sectors +//---------------------------------------------------------------------- + +bool +FileHeader::Allocate(BitMap *freeMap, int fileSize) +{ + numBytes = fileSize; + numSectors = divRoundUp(fileSize, SectorSize); + if (freeMap->NumClear() < numSectors) + return FALSE; // not enough space + + for (int i = 0; i < numSectors; i++) + dataSectors[i] = freeMap->Find(); + return TRUE; +} + +//---------------------------------------------------------------------- +// FileHeader::Deallocate +// De-allocate all the space allocated for data blocks for this file. +// +// "freeMap" is the bit map of free disk sectors +//---------------------------------------------------------------------- + +void +FileHeader::Deallocate(BitMap *freeMap) +{ + for (int i = 0; i < numSectors; i++) { + ASSERT(freeMap->Test((int) dataSectors[i])); // ought to be marked! + freeMap->Clear((int) dataSectors[i]); + } +} + +//---------------------------------------------------------------------- +// FileHeader::FetchFrom +// Fetch contents of file header from disk. +// +// "sector" is the disk sector containing the file header +//---------------------------------------------------------------------- + +void +FileHeader::FetchFrom(int sector) +{ + synchDisk->ReadSector(sector, this); +} + +//---------------------------------------------------------------------- +// FileHeader::WriteBack +// Write the modified contents of the file header back to disk. +// +// "sector" is the disk sector to contain the file header +//---------------------------------------------------------------------- + +void +FileHeader::WriteBack(int sector) +{ + synchDisk->WriteSector(sector, this); +} + +//---------------------------------------------------------------------- +// FileHeader::ByteToSector +// Return which disk sector is storing a particular byte within the file. +// This is essentially a translation from a virtual address (the +// offset in the file) to a physical address (the sector where the +// data at the offset is stored). +// +// "offset" is the location within the file of the byte in question +//---------------------------------------------------------------------- + +int +FileHeader::ByteToSector(int offset) +{ + return(dataSectors[offset / SectorSize]); +} + +//---------------------------------------------------------------------- +// FileHeader::FileLength +// Return the number of bytes in the file. +//---------------------------------------------------------------------- + +int +FileHeader::FileLength() +{ + return numBytes; +} + +//---------------------------------------------------------------------- +// FileHeader::Print +// Print the contents of the file header, and the contents of all +// the data blocks pointed to by the file header. +//---------------------------------------------------------------------- + +void +FileHeader::Print() +{ + int i, j, k; + char *data = new char[SectorSize]; + + printf("FileHeader contents. File size: %d. File blocks:\n", numBytes); + for (i = 0; i < numSectors; i++) + printf("%d ", dataSectors[i]); + printf("\nFile contents:\n"); + for (i = k = 0; i < numSectors; i++) { + synchDisk->ReadSector(dataSectors[i], data); + for (j = 0; (j < SectorSize) && (k < numBytes); j++, k++) { + if ('\040' <= data[j] && data[j] <= '\176') // isprint(data[j]) + printf("%c", data[j]); + else + printf("\\%x", (unsigned char)data[j]); + } + printf("\n"); + } + delete [] data; +} diff --git a/code/filesys/filehdr.h b/code/filesys/filehdr.h new file mode 100644 index 0000000..b538386 --- /dev/null +++ b/code/filesys/filehdr.h @@ -0,0 +1,66 @@ +// filehdr.h +// Data structures for managing a disk file header. +// +// A file header describes where on disk to find the data in a file, +// along with other information about the file (for instance, its +// length, owner, etc.) +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#ifndef FILEHDR_H +#define FILEHDR_H + +#include "disk.h" +#include "bitmap.h" + +#define NumDirect ((SectorSize - 2 * sizeof(int)) / sizeof(int)) +#define MaxFileSize (NumDirect * SectorSize) + +// The following class defines the Nachos "file header" (in UNIX terms, +// the "i-node"), describing where on disk to find all of the data in the file. +// The file header is organized as a simple table of pointers to +// data blocks. +// +// The file header data structure can be stored in memory or on disk. +// When it is on disk, it is stored in a single sector -- this means +// that we assume the size of this data structure to be the same +// as one disk sector. Without indirect addressing, this +// limits the maximum file length to just under 4K bytes. +// +// There is no constructor; rather the file header can be initialized +// by allocating blocks for the file (if it is a new file), or by +// reading it from disk. + +class FileHeader { + public: + bool Allocate(BitMap *bitMap, int fileSize);// Initialize a file header, + // including allocating space + // on disk for the file data + void Deallocate(BitMap *bitMap); // De-allocate this file's + // data blocks + + void FetchFrom(int sectorNumber); // Initialize file header from disk + void WriteBack(int sectorNumber); // Write modifications to file header + // back to disk + + int ByteToSector(int offset); // Convert a byte offset into the file + // to the disk sector containing + // the byte + + int FileLength(); // Return the length of the file + // in bytes + + void Print(); // Print the contents of the file. + + private: + int numBytes; // Number of bytes in the file + int numSectors; // Number of data sectors in the file + int dataSectors[NumDirect]; // Disk sector numbers for each data + // block in the file +}; + +#endif // FILEHDR_H diff --git a/code/filesys/filesys.cc b/code/filesys/filesys.cc new file mode 100644 index 0000000..386d082 --- /dev/null +++ b/code/filesys/filesys.cc @@ -0,0 +1,342 @@ +// filesys.cc +// Routines to manage the overall operation of the file system. +// Implements routines to map from textual file names to files. +// +// Each file in the file system has: +// A file header, stored in a sector on disk +// (the size of the file header data structure is arranged +// to be precisely the size of 1 disk sector) +// A number of data blocks +// An entry in the file system directory +// +// The file system consists of several data structures: +// A bitmap of free disk sectors (cf. bitmap.h) +// A directory of file names and file headers +// +// Both the bitmap and the directory are represented as normal +// files. Their file headers are located in specific sectors +// (sector 0 and sector 1), so that the file system can find them +// on bootup. +// +// The file system assumes that the bitmap and directory files are +// kept "open" continuously while Nachos is running. +// +// For those operations (such as Create, Remove) that modify the +// directory and/or bitmap, if the operation succeeds, the changes +// are written immediately back to disk (the two files are kept +// open during all this time). If the operation fails, and we have +// modified part of the directory and/or bitmap, we simply discard +// the changed version, without writing it back to disk. +// +// Our implementation at this point has the following restrictions: +// +// there is no synchronization for concurrent accesses +// files have a fixed size, set when the file is created +// files cannot be bigger than about 3KB in size +// there is no hierarchical directory structure, and only a limited +// number of files can be added to the system +// there is no attempt to make the system robust to failures +// (if Nachos exits in the middle of an operation that modifies +// the file system, it may corrupt the disk) +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#include "disk.h" +#include "bitmap.h" +#include "directory.h" +#include "filehdr.h" +#include "filesys.h" + +// Sectors containing the file headers for the bitmap of free sectors, +// and the directory of files. These file headers are placed in well-known +// sectors, so that they can be located on boot-up. +#define FreeMapSector 0 +#define DirectorySector 1 + +// Initial file sizes for the bitmap and directory; until the file system +// supports extensible files, the directory size sets the maximum number +// of files that can be loaded onto the disk. +#define BitsInByte 8 +#define FreeMapFileSize (NumSectors / BitsInByte) +#define NumDirEntries 10 +#define DirectoryFileSize (sizeof(DirectoryEntry) * NumDirEntries) + +//---------------------------------------------------------------------- +// FileSystem::FileSystem +// Initialize the file system. If format = TRUE, the disk has +// nothing on it, and we need to initialize the disk to contain +// an empty directory, and a bitmap of free sectors (with almost but +// not all of the sectors marked as free). +// +// If format = FALSE, we just have to open the files +// representing the bitmap and the directory. +// +// "format" -- should we initialize the disk? +//---------------------------------------------------------------------- + +FileSystem::FileSystem(bool format) +{ + DEBUG('f', "Initializing the file system.\n"); + if (format) { + BitMap *freeMap = new BitMap(NumSectors); + Directory *directory = new Directory(NumDirEntries); + FileHeader *mapHdr = new FileHeader; + FileHeader *dirHdr = new FileHeader; + + DEBUG('f', "Formatting the file system.\n"); + + // First, allocate space for FileHeaders for the directory and bitmap + // (make sure no one else grabs these!) + freeMap->Mark(FreeMapSector); + freeMap->Mark(DirectorySector); + + // Second, allocate space for the data blocks containing the contents + // of the directory and bitmap files. There better be enough space! + + ASSERT(mapHdr->Allocate(freeMap, FreeMapFileSize)); + ASSERT(dirHdr->Allocate(freeMap, DirectoryFileSize)); + + // Flush the bitmap and directory FileHeaders back to disk + // We need to do this before we can "Open" the file, since open + // reads the file header off of disk (and currently the disk has garbage + // on it!). + + DEBUG('f', "Writing headers back to disk.\n"); + mapHdr->WriteBack(FreeMapSector); + dirHdr->WriteBack(DirectorySector); + + // OK to open the bitmap and directory files now + // The file system operations assume these two files are left open + // while Nachos is running. + + freeMapFile = new OpenFile(FreeMapSector); + directoryFile = new OpenFile(DirectorySector); + + // Once we have the files "open", we can write the initial version + // of each file back to disk. The directory at this point is completely + // empty; but the bitmap has been changed to reflect the fact that + // sectors on the disk have been allocated for the file headers and + // to hold the file data for the directory and bitmap. + + DEBUG('f', "Writing bitmap and directory back to disk.\n"); + freeMap->WriteBack(freeMapFile); // flush changes to disk + directory->WriteBack(directoryFile); + + if (DebugIsEnabled('f')) { + freeMap->Print(); + directory->Print(); + + delete freeMap; + delete directory; + delete mapHdr; + delete dirHdr; + } + } else { + // if we are not formatting the disk, just open the files representing + // the bitmap and directory; these are left open while Nachos is running + freeMapFile = new OpenFile(FreeMapSector); + directoryFile = new OpenFile(DirectorySector); + } +} + +//---------------------------------------------------------------------- +// FileSystem::Create +// Create a file in the Nachos file system (similar to UNIX create). +// Since we can't increase the size of files dynamically, we have +// to give Create the initial size of the file. +// +// The steps to create a file are: +// Make sure the file doesn't already exist +// Allocate a sector for the file header +// Allocate space on disk for the data blocks for the file +// Add the name to the directory +// Store the new file header on disk +// Flush the changes to the bitmap and the directory back to disk +// +// Return TRUE if everything goes ok, otherwise, return FALSE. +// +// Create fails if: +// file is already in directory +// no free space for file header +// no free entry for file in directory +// no free space for data blocks for the file +// +// Note that this implementation assumes there is no concurrent access +// to the file system! +// +// "name" -- name of file to be created +// "initialSize" -- size of file to be created +//---------------------------------------------------------------------- + +bool +FileSystem::Create(const char *name, int initialSize) +{ + Directory *directory; + BitMap *freeMap; + FileHeader *hdr; + int sector; + bool success; + + DEBUG('f', "Creating file %s, size %d\n", name, initialSize); + + directory = new Directory(NumDirEntries); + directory->FetchFrom(directoryFile); + + if (directory->Find(name) != -1) + success = FALSE; // file is already in directory + else { + freeMap = new BitMap(NumSectors); + freeMap->FetchFrom(freeMapFile); + sector = freeMap->Find(); // find a sector to hold the file header + if (sector == -1) + success = FALSE; // no free block for file header + else if (!directory->Add(name, sector)) + success = FALSE; // no space in directory + else { + hdr = new FileHeader; + if (!hdr->Allocate(freeMap, initialSize)) + success = FALSE; // no space on disk for data + else { + success = TRUE; + // everthing worked, flush all changes back to disk + hdr->WriteBack(sector); + directory->WriteBack(directoryFile); + freeMap->WriteBack(freeMapFile); + } + delete hdr; + } + delete freeMap; + } + delete directory; + return success; +} + +//---------------------------------------------------------------------- +// FileSystem::Open +// Open a file for reading and writing. +// To open a file: +// Find the location of the file's header, using the directory +// Bring the header into memory +// +// "name" -- the text name of the file to be opened +//---------------------------------------------------------------------- + +OpenFile * +FileSystem::Open(const char *name) +{ + Directory *directory = new Directory(NumDirEntries); + OpenFile *openFile = NULL; + int sector; + + DEBUG('f', "Opening file %s\n", name); + directory->FetchFrom(directoryFile); + sector = directory->Find(name); + if (sector >= 0) + openFile = new OpenFile(sector); // name was found in directory + delete directory; + return openFile; // return NULL if not found +} + +//---------------------------------------------------------------------- +// FileSystem::Remove +// Delete a file from the file system. This requires: +// Remove it from the directory +// Delete the space for its header +// Delete the space for its data blocks +// Write changes to directory, bitmap back to disk +// +// Return TRUE if the file was deleted, FALSE if the file wasn't +// in the file system. +// +// "name" -- the text name of the file to be removed +//---------------------------------------------------------------------- + +bool +FileSystem::Remove(const char *name) +{ + Directory *directory; + BitMap *freeMap; + FileHeader *fileHdr; + int sector; + + directory = new Directory(NumDirEntries); + directory->FetchFrom(directoryFile); + sector = directory->Find(name); + if (sector == -1) { + delete directory; + return FALSE; // file not found + } + fileHdr = new FileHeader; + fileHdr->FetchFrom(sector); + + freeMap = new BitMap(NumSectors); + freeMap->FetchFrom(freeMapFile); + + fileHdr->Deallocate(freeMap); // remove data blocks + freeMap->Clear(sector); // remove header block + directory->Remove(name); + + freeMap->WriteBack(freeMapFile); // flush to disk + directory->WriteBack(directoryFile); // flush to disk + delete fileHdr; + delete directory; + delete freeMap; + return TRUE; +} + +//---------------------------------------------------------------------- +// FileSystem::List +// List all the files in the file system directory. +//---------------------------------------------------------------------- + +void +FileSystem::List() +{ + Directory *directory = new Directory(NumDirEntries); + + directory->FetchFrom(directoryFile); + directory->List(); + delete directory; +} + +//---------------------------------------------------------------------- +// FileSystem::Print +// Print everything about the file system: +// the contents of the bitmap +// the contents of the directory +// for each file in the directory, +// the contents of the file header +// the data in the file +//---------------------------------------------------------------------- + +void +FileSystem::Print() +{ + FileHeader *bitHdr = new FileHeader; + FileHeader *dirHdr = new FileHeader; + BitMap *freeMap = new BitMap(NumSectors); + Directory *directory = new Directory(NumDirEntries); + + printf("Bit map file header:\n"); + bitHdr->FetchFrom(FreeMapSector); + bitHdr->Print(); + + printf("Directory file header:\n"); + dirHdr->FetchFrom(DirectorySector); + dirHdr->Print(); + + freeMap->FetchFrom(freeMapFile); + freeMap->Print(); + + directory->FetchFrom(directoryFile); + directory->Print(); + + delete bitHdr; + delete dirHdr; + delete freeMap; + delete directory; +} diff --git a/code/filesys/filesys.h b/code/filesys/filesys.h new file mode 100644 index 0000000..e6a539e --- /dev/null +++ b/code/filesys/filesys.h @@ -0,0 +1,98 @@ +// filesys.h +// Data structures to represent the Nachos file system. +// +// A file system is a set of files stored on disk, organized +// into directories. Operations on the file system have to +// do with "naming" -- creating, opening, and deleting files, +// given a textual file name. Operations on an individual +// "open" file (read, write, close) are to be found in the OpenFile +// class (openfile.h). +// +// We define two separate implementations of the file system. +// The "STUB" version just re-defines the Nachos file system +// operations as operations on the native UNIX file system on the machine +// running the Nachos simulation. This is provided in case the +// multiprogramming and virtual memory assignments (which make use +// of the file system) are done before the file system assignment. +// +// The other version is a "real" file system, built on top of +// a disk simulator. The disk is simulated using the native UNIX +// file system (in a file named "DISK"). +// +// In the "real" implementation, there are two key data structures used +// in the file system. There is a single "root" directory, listing +// all of the files in the file system; unlike UNIX, the baseline +// system does not provide a hierarchical directory structure. +// In addition, there is a bitmap for allocating +// disk sectors. Both the root directory and the bitmap are themselves +// stored as files in the Nachos file system -- this causes an interesting +// bootstrap problem when the simulated disk is initialized. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef FS_H +#define FS_H + +#include "copyright.h" +#include "openfile.h" + +#ifdef FILESYS_STUB // Temporarily implement file system calls as + // calls to UNIX, until the real file system + // implementation is available +class FileSystem:public dontcopythis { + public: + FileSystem(bool format) { (void) format; } + + bool Create(const char *name, int initialSize) { + int fileDescriptor = OpenForWrite(name); + (void) initialSize; + + if (fileDescriptor == -1) return FALSE; + Close(fileDescriptor); + return TRUE; + } + + OpenFile* Open(const char *name) { + int fileDescriptor = OpenForReadWrite(name, FALSE); + + if (fileDescriptor == -1) return NULL; + return new OpenFile(fileDescriptor); + } + + bool Remove(const char *name) { return Unlink(name) == 0; } + +}; + +#else // FILESYS +class FileSystem:public dontcopythis { + public: + FileSystem(bool format); // Initialize the file system. + // Must be called *after* "synchDisk" + // has been initialized. + // If "format", there is nothing on + // the disk, so initialize the directory + // and the bitmap of free blocks. + + bool Create(const char *name, int initialSize); + // Create a file (UNIX creat) + + OpenFile* Open(const char *name); // Open a file (UNIX open) + + bool Remove(const char *name); // Delete a file (UNIX unlink) + + void List(); // List all the files in the file system + + void Print(); // List all the files and their contents + + private: + OpenFile* freeMapFile; // Bit map of free disk blocks, + // represented as a file + OpenFile* directoryFile; // "Root" directory -- list of + // file names, represented as a file +}; + +#endif // FILESYS + +#endif // FS_H diff --git a/code/filesys/fstest.cc b/code/filesys/fstest.cc new file mode 100644 index 0000000..52e783b --- /dev/null +++ b/code/filesys/fstest.cc @@ -0,0 +1,185 @@ +// fstest.cc +// Simple test routines for the file system. +// +// We implement: +// Copy -- copy a file from UNIX to Nachos +// Print -- cat the contents of a Nachos file +// Perftest -- a stress test for the Nachos file system +// read and write a really large file in tiny chunks +// (won't work on baseline system!) +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#include "utility.h" +#include "filesys.h" +#include "system.h" +#include "thread.h" +#include "disk.h" +#include "stats.h" + +#define TransferSize 10 // make it small, just to be difficult + +//---------------------------------------------------------------------- +// Copy +// Copy the contents of the UNIX file "from" to the Nachos file "to" +//---------------------------------------------------------------------- + +void +Copy(char *from, char *to) +{ + FILE *fp; + OpenFile* openFile; + int amountRead, fileLength; + char *buffer; + +// Open UNIX file + if ((fp = fopen(from, "r")) == NULL) { + printf("Copy: couldn't open input file %s\n", from); + return; + } + +// Figure out length of UNIX file + fseek(fp, 0, 2); + fileLength = ftell(fp); + fseek(fp, 0, 0); + +// Create a Nachos file of the same length + DEBUG('f', "Copying file %s, size %d, to file %s\n", from, fileLength, to); + if (!fileSystem->Create(to, fileLength)) { // Create Nachos file + printf("Copy: couldn't create output file %s\n", to); + fclose(fp); + return; + } + + openFile = fileSystem->Open(to); + ASSERT(openFile != NULL); + +// Copy the data in TransferSize chunks + buffer = new char[TransferSize]; + while ((amountRead = fread(buffer, sizeof(char), TransferSize, fp)) > 0) + openFile->Write(buffer, amountRead); + delete [] buffer; + +// Close the UNIX and the Nachos files + delete openFile; + fclose(fp); +} + +//---------------------------------------------------------------------- +// Print +// Print the contents of the Nachos file "name". +//---------------------------------------------------------------------- + +void +Print(char *name) +{ + OpenFile *openFile; + int i, amountRead; + char *buffer; + + if ((openFile = fileSystem->Open(name)) == NULL) { + printf("Print: unable to open file %s\n", name); + return; + } + + buffer = new char[TransferSize]; + while ((amountRead = openFile->Read(buffer, TransferSize)) > 0) + for (i = 0; i < amountRead; i++) + printf("%c", buffer[i]); + delete [] buffer; + + delete openFile; // close the Nachos file + return; +} + +//---------------------------------------------------------------------- +// PerformanceTest +// Stress the Nachos file system by creating a large file, writing +// it out a bit at a time, reading it back a bit at a time, and then +// deleting the file. +// +// Implemented as three separate routines: +// FileWrite -- write the file +// FileRead -- read the file +// PerformanceTest -- overall control, and print out performance #'s +//---------------------------------------------------------------------- + +#define FileName "TestFile" +#define Contents "1234567890" +#define ContentSize strlen(Contents) +#define FileSize ((int)(ContentSize * 5000)) + +static void +FileWrite() +{ + OpenFile *openFile; + int i, numBytes; + + printf("Sequential write of %d byte file, in %d byte chunks\n", + FileSize, (int) ContentSize); + if (!fileSystem->Create(FileName, 0)) { + printf("Perf test: can't create %s\n", FileName); + return; + } + openFile = fileSystem->Open(FileName); + if (openFile == NULL) { + printf("Perf test: unable to open %s\n", FileName); + return; + } + for (i = 0; i < FileSize; i += ContentSize) { + numBytes = openFile->Write(Contents, ContentSize); + if (numBytes < 10) { + printf("Perf test: unable to write %s\n", FileName); + delete openFile; + return; + } + } + delete openFile; // close file +} + +static void +FileRead() +{ + OpenFile *openFile; + char *buffer = new char[ContentSize]; + int i, numBytes; + + printf("Sequential read of %d byte file, in %d byte chunks\n", + FileSize, (int) ContentSize); + + if ((openFile = fileSystem->Open(FileName)) == NULL) { + printf("Perf test: unable to open file %s\n", FileName); + delete [] buffer; + return; + } + for (i = 0; i < FileSize; i += ContentSize) { + numBytes = openFile->Read(buffer, ContentSize); + if ((numBytes < 10) || strncmp(buffer, Contents, ContentSize)) { + printf("Perf test: unable to read %s\n", FileName); + delete openFile; + delete [] buffer; + return; + } + } + delete [] buffer; + delete openFile; // close file +} + +void +PerformanceTest() +{ + printf("Starting file system performance test:\n"); + stats->Print(); + FileWrite(); + FileRead(); + if (!fileSystem->Remove(FileName)) { + printf("Perf test: unable to remove %s\n", FileName); + return; + } + stats->Print(); +} + diff --git a/code/filesys/openfile.cc b/code/filesys/openfile.cc new file mode 100644 index 0000000..318c8a6 --- /dev/null +++ b/code/filesys/openfile.cc @@ -0,0 +1,197 @@ +// openfile.cc +// Routines to manage an open Nachos file. As in UNIX, a +// file must be open before we can read or write to it. +// Once we're all done, we can close it (in Nachos, by deleting +// the OpenFile data structure). +// +// Also as in UNIX, for convenience, we keep the file header in +// memory while the file is open. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "filehdr.h" +#include "openfile.h" +#include "system.h" + +#include /* for bzero */ + +//---------------------------------------------------------------------- +// OpenFile::OpenFile +// Open a Nachos file for reading and writing. Bring the file header +// into memory while the file is open. +// +// "sector" -- the location on disk of the file header for this file +//---------------------------------------------------------------------- + +OpenFile::OpenFile(int sector) +{ + hdr = new FileHeader; + hdr->FetchFrom(sector); + seekPosition = 0; +} + +//---------------------------------------------------------------------- +// OpenFile::~OpenFile +// Close a Nachos file, de-allocating any in-memory data structures. +//---------------------------------------------------------------------- + +OpenFile::~OpenFile() +{ + delete hdr; + hdr = NULL; +} + +//---------------------------------------------------------------------- +// OpenFile::Seek +// Change the current location within the open file -- the point at +// which the next Read or Write will start from. +// +// "position" -- the location within the file for the next Read/Write +//---------------------------------------------------------------------- + +void +OpenFile::Seek(int position) +{ + seekPosition = position; +} + +//---------------------------------------------------------------------- +// OpenFile::Read/Write +// Read/write a portion of a file, starting from seekPosition. +// Return the number of bytes actually written or read, and as a +// side effect, increment the current position within the file. +// +// Implemented using the more primitive ReadAt/WriteAt. +// +// "into" -- the buffer to contain the data to be read from disk +// "from" -- the buffer containing the data to be written to disk +// "numBytes" -- the number of bytes to transfer +//---------------------------------------------------------------------- + +int +OpenFile::Read(void *into, int numBytes) +{ + int result = ReadAt(into, numBytes, seekPosition); + seekPosition += result; + return result; +} + +int +OpenFile::Write(const void *into, int numBytes) +{ + int result = WriteAt(into, numBytes, seekPosition); + seekPosition += result; + return result; +} + +//---------------------------------------------------------------------- +// OpenFile::ReadAt/WriteAt +// Read/write a portion of a file, starting at "position". +// Return the number of bytes actually written or read, but has +// no side effects (except that Write modifies the file, of course). +// +// There is no guarantee the request starts or ends on an even disk sector +// boundary; however the disk only knows how to read/write a whole disk +// sector at a time. Thus: +// +// For ReadAt: +// We read in all of the full or partial sectors that are part of the +// request, but we only copy the part we are interested in. +// For WriteAt: +// We must first read in any sectors that will be partially written, +// so that we don't overwrite the unmodified portion. We then copy +// in the data that will be modified, and write back all the full +// or partial sectors that are part of the request. +// +// "into" -- the buffer to contain the data to be read from disk +// "from" -- the buffer containing the data to be written to disk +// "numBytes" -- the number of bytes to transfer +// "position" -- the offset within the file of the first byte to be +// read/written +//---------------------------------------------------------------------- + +int +OpenFile::ReadAt(void *into, int numBytes, int position) +{ + int fileLength = hdr->FileLength(); + int i, firstSector, lastSector, numSectors; + char *buf; + + if ((numBytes <= 0) || (position >= fileLength)) + return 0; // check request + if ((position + numBytes) > fileLength) + numBytes = fileLength - position; + DEBUG('f', "Reading %d bytes at %d, from file of length %d.\n", + numBytes, position, fileLength); + + firstSector = divRoundDown(position, SectorSize); + lastSector = divRoundDown(position + numBytes - 1, SectorSize); + numSectors = 1 + lastSector - firstSector; + + // read in all the full and partial sectors that we need + buf = new char[numSectors * SectorSize]; + for (i = firstSector; i <= lastSector; i++) + synchDisk->ReadSector(hdr->ByteToSector(i * SectorSize), + &buf[(i - firstSector) * SectorSize]); + + // copy the part we want + bcopy(&buf[position - (firstSector * SectorSize)], into, numBytes); + delete [] buf; + return numBytes; +} + +int +OpenFile::WriteAt(const void *from, int numBytes, int position) +{ + int fileLength = hdr->FileLength(); + int i, firstSector, lastSector, numSectors; + bool firstAligned, lastAligned; + char *buf; + + if ((numBytes <= 0) || (position >= fileLength)) + return 0; // check request + if ((position + numBytes) > fileLength) + numBytes = fileLength - position; + DEBUG('f', "Writing %d bytes at %d, from file of length %d.\n", + numBytes, position, fileLength); + + firstSector = divRoundDown(position, SectorSize); + lastSector = divRoundDown(position + numBytes - 1, SectorSize); + numSectors = 1 + lastSector - firstSector; + + buf = new char[numSectors * SectorSize]; + + firstAligned = (position == (firstSector * SectorSize)); + lastAligned = ((position + numBytes) == ((lastSector + 1) * SectorSize)); + +// read in first and last sector, if they are to be partially modified + if (!firstAligned) + ReadAt(buf, SectorSize, firstSector * SectorSize); + if (!lastAligned && ((firstSector != lastSector) || firstAligned)) + ReadAt(&buf[(lastSector - firstSector) * SectorSize], + SectorSize, lastSector * SectorSize); + +// copy in the bytes we want to change + bcopy(from, &buf[position - (firstSector * SectorSize)], numBytes); + +// write modified sectors back + for (i = firstSector; i <= lastSector; i++) + synchDisk->WriteSector(hdr->ByteToSector(i * SectorSize), + &buf[(i - firstSector) * SectorSize]); + delete [] buf; + return numBytes; +} + +//---------------------------------------------------------------------- +// OpenFile::Length +// Return the number of bytes in the file. +//---------------------------------------------------------------------- + +int +OpenFile::Length() +{ + return hdr->FileLength(); +} diff --git a/code/filesys/openfile.h b/code/filesys/openfile.h new file mode 100644 index 0000000..2095cd4 --- /dev/null +++ b/code/filesys/openfile.h @@ -0,0 +1,97 @@ +// openfile.h +// Data structures for opening, closing, reading and writing to +// individual files. The operations supported are similar to +// the UNIX ones -- type 'man open' to the UNIX prompt. +// +// There are two implementations. One is a "STUB" that directly +// turns the file operations into the underlying UNIX operations. +// (cf. comment in filesys.h). +// +// The other is the "real" implementation, that turns these +// operations into read and write disk sector requests. +// In this baseline implementation of the file system, we don't +// worry about concurrent accesses to the file system +// by different threads -- this is part of the assignment. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef OPENFILE_H +#define OPENFILE_H + +#include "copyright.h" +#include "utility.h" + +#ifdef FILESYS_STUB // Temporarily implement calls to + // Nachos file system as calls to UNIX! + // See definitions listed under #else +class OpenFile:public dontcopythis { + public: + OpenFile(int f) { file = f; currentOffset = 0; } // open the file + ~OpenFile() { Close(file); file = -1; } // close the file + + int ReadAt(void *into, int numBytes, int position) { + Lseek(file, position, SEEK_SET); + return ReadPartial(file, into, numBytes); + } + int WriteAt(const void *from, int numBytes, int position) { + Lseek(file, position, SEEK_SET); + WriteFile(file, from, numBytes); + return numBytes; + } + int Read(void *into, int numBytes) { + int numRead = ReadAt(into, numBytes, currentOffset); + currentOffset += numRead; + return numRead; + } + int Write(const void *from, int numBytes) { + int numWritten = WriteAt(from, numBytes, currentOffset); + currentOffset += numWritten; + return numWritten; + } + + int Length() { Lseek(file, 0, SEEK_END); return Tell(file); } + + private: + int file; + int currentOffset; +}; + +#else // FILESYS +class FileHeader; + +class OpenFile:public dontcopythis { + public: + OpenFile(int sector); // Open a file whose header is located + // at "sector" on the disk + ~OpenFile(); // Close the file + + void Seek(int position); // Set the position from which to + // start reading/writing -- UNIX lseek + + int Read(void *into, int numBytes); // Read/write bytes from the file, + // starting at the implicit position. + // Return the # actually read/written, + // and increment position in file. + int Write(const void *from, int numBytes); + + int ReadAt(void *into, int numBytes, int position); + // Read/write bytes from the file, + // bypassing the implicit position. + // Return the # actually read/written. + int WriteAt(const void *from, int numBytes, int position); + + int Length(); // Return the number of bytes in the + // file (this interface is simpler + // than the UNIX idiom -- lseek to + // end of file, tell, lseek back + + private: + FileHeader *hdr; // Header for this file + int seekPosition; // Current position within the file +}; + +#endif // FILESYS + +#endif // OPENFILE_H diff --git a/code/filesys/synchdisk.cc b/code/filesys/synchdisk.cc new file mode 100644 index 0000000..99c2d45 --- /dev/null +++ b/code/filesys/synchdisk.cc @@ -0,0 +1,112 @@ +// synchdisk.cc +// Routines to synchronously access the disk. The physical disk +// is an asynchronous device (disk requests return immediately, and +// an interrupt happens later on). This is a layer on top of +// the disk providing a synchronous interface (requests wait until +// the request completes). +// +// Use a semaphore to synchronize the interrupt handlers with the +// pending requests. And, because the physical disk can only +// handle one operation at a time, use a lock to enforce mutual +// exclusion. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "synchdisk.h" + +//---------------------------------------------------------------------- +// DiskRequestDone +// Disk interrupt handler. Need this to be a C routine, because +// C++ can't handle pointers to member functions. +//---------------------------------------------------------------------- + +static void +DiskRequestDone (void *arg) +{ + SynchDisk* disk = (SynchDisk *)arg; + + disk->RequestDone(); +} + +//---------------------------------------------------------------------- +// SynchDisk::SynchDisk +// Initialize the synchronous interface to the physical disk, in turn +// initializing the physical disk. +// +// "name" -- UNIX file name to be used as storage for the disk data +// (usually, "DISK") +//---------------------------------------------------------------------- + +SynchDisk::SynchDisk(const char* name) +{ + semaphore = new Semaphore("synch disk", 0); + lock = new Lock("synch disk lock"); + disk = new Disk(name, DiskRequestDone, this); +} + +//---------------------------------------------------------------------- +// SynchDisk::~SynchDisk +// De-allocate data structures needed for the synchronous disk +// abstraction. +//---------------------------------------------------------------------- + +SynchDisk::~SynchDisk() +{ + delete disk; + disk = NULL; + delete lock; + lock = NULL; + delete semaphore; + semaphore = NULL; +} + +//---------------------------------------------------------------------- +// SynchDisk::ReadSector +// Read the contents of a disk sector into a buffer. Return only +// after the data has been read. +// +// "sectorNumber" -- the disk sector to read +// "data" -- the buffer to hold the contents of the disk sector +//---------------------------------------------------------------------- + +void +SynchDisk::ReadSector(int sectorNumber, void* data) +{ + lock->Acquire(); // only one disk I/O at a time + disk->ReadRequest(sectorNumber, data); + semaphore->P(); // wait for interrupt + lock->Release(); +} + +//---------------------------------------------------------------------- +// SynchDisk::WriteSector +// Write the contents of a buffer into a disk sector. Return only +// after the data has been written. +// +// "sectorNumber" -- the disk sector to be written +// "data" -- the new contents of the disk sector +//---------------------------------------------------------------------- + +void +SynchDisk::WriteSector(int sectorNumber, const void* data) +{ + lock->Acquire(); // only one disk I/O at a time + disk->WriteRequest(sectorNumber, data); + semaphore->P(); // wait for interrupt + lock->Release(); +} + +//---------------------------------------------------------------------- +// SynchDisk::RequestDone +// Disk interrupt handler. Wake up any thread waiting for the disk +// request to finish. +//---------------------------------------------------------------------- + +void +SynchDisk::RequestDone() +{ + semaphore->V(); +} diff --git a/code/filesys/synchdisk.h b/code/filesys/synchdisk.h new file mode 100644 index 0000000..b494772 --- /dev/null +++ b/code/filesys/synchdisk.h @@ -0,0 +1,53 @@ +// synchdisk.h +// Data structures to export a synchronous interface to the raw +// disk device. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#ifndef SYNCHDISK_H +#define SYNCHDISK_H + +#include "disk.h" +#include "synch.h" + +// The following class defines a "synchronous" disk abstraction. +// As with other I/O devices, the raw physical disk is an asynchronous device -- +// requests to read or write portions of the disk return immediately, +// and an interrupt occurs later to signal that the operation completed. +// (Also, the physical characteristics of the disk device assume that +// only one operation can be requested at a time). +// +// This class provides the abstraction that for any individual thread +// making a request, it waits around until the operation finishes before +// returning. +class SynchDisk:public dontcopythis { + public: + SynchDisk(const char* name); // Initialize a synchronous disk, + // by initializing the raw Disk. + ~SynchDisk(); // De-allocate the synch disk data + + void ReadSector(int sectorNumber, void* data); + // Read/write a disk sector, returning + // only once the data is actually read + // or written. These call + // Disk::ReadRequest/WriteRequest and + // then wait until the request is done. + void WriteSector(int sectorNumber, const void* data); + + void RequestDone(); // Called by the disk device interrupt + // handler, to signal that the + // current disk operation is complete. + + private: + Disk *disk; // Raw disk device + Semaphore *semaphore; // To synchronize requesting thread + // with the interrupt handler + Lock *lock; // Only one read/write request + // can be sent to the disk at a time +}; + +#endif // SYNCHDISK_H diff --git a/code/filesys/test/big b/code/filesys/test/big new file mode 100644 index 0000000..cc1377b --- /dev/null +++ b/code/filesys/test/big @@ -0,0 +1,16 @@ +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. diff --git a/code/filesys/test/medium b/code/filesys/test/medium new file mode 100644 index 0000000..001f5e5 --- /dev/null +++ b/code/filesys/test/medium @@ -0,0 +1,4 @@ +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. +This is the spring of our discontent. diff --git a/code/filesys/test/small b/code/filesys/test/small new file mode 100644 index 0000000..c2e8118 --- /dev/null +++ b/code/filesys/test/small @@ -0,0 +1 @@ +This is the spring of our discontent. diff --git a/code/machine/console.cc b/code/machine/console.cc new file mode 100644 index 0000000..7ed7068 --- /dev/null +++ b/code/machine/console.cc @@ -0,0 +1,234 @@ +// console.cc +// Routines to simulate a serial port to a console device. +// A console has input (a keyboard) and output (a display). +// These are each simulated by operations on UNIX files. +// The simulated device is asynchronous, +// so we have to invoke the interrupt handler (after a simulated +// delay), to signal that a byte has arrived and/or that a written +// byte has departed. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "console.h" +#include "system.h" +#include + +#define NOCHAR (-42) + +// Dummy functions because C++ is weird about pointers to member functions +static void ConsoleReadPoll(void *c) +{ Console *console = (Console *)c; console->CheckCharAvail(); } +static void ConsoleWriteDone(void *c) +{ Console *console = (Console *)c; console->WriteDone(); } + +//---------------------------------------------------------------------- +// Console::Console +// Initialize the simulation of a hardware console device. +// +// "readFile" -- UNIX file simulating the keyboard (NULL -> use stdin) +// "writeFile" -- UNIX file simulating the display (NULL -> use stdout) +// "readAvailHandler" is the interrupt handler called when a character arrives +// from the keyboard +// "writeDoneHandler" is the interrupt handler called when a character has +// been output, so that it is ok to request the next char be +// output +//---------------------------------------------------------------------- + +int Console::stdin_busy; + +Console::Console(const char *readFile, const char *writeFile, VoidFunctionPtr readAvailHandler, + VoidFunctionPtr writeDoneHandler, void *callArg) +{ + if (readFile == NULL) + { + ASSERT(!stdin_busy); + stdin_busy = 1; + readFileNo = 0; // keyboard = stdin + } + else + readFileNo = OpenForReadWrite(readFile, TRUE); // should be read-only + if (writeFile == NULL) + writeFileNo = 1; // display = stdout + else + writeFileNo = OpenForWrite(writeFile); + + // set up the stuff to emulate asynchronous interrupts + writeHandler = writeDoneHandler; + readHandler = readAvailHandler; + handlerArg = callArg; + putBusy = FALSE; + incoming = NOCHAR; + + // start polling for incoming packets + interrupt->Schedule(ConsoleReadPoll, this, ConsoleTime, ConsoleReadInt); +} + +//---------------------------------------------------------------------- +// Console::~Console +// Clean up console emulation +//---------------------------------------------------------------------- + +Console::~Console() +{ + if (readFileNo != 0) + Close(readFileNo); + else + stdin_busy = 0; + readFileNo = -1; + if (writeFileNo != 1) + Close(writeFileNo); + writeFileNo = -1; + + /* Wait for last interrupts to happen */ + while (readFileNo != -2) + currentThread->Yield(); + while (putBusy) + currentThread->Yield(); +} + +//---------------------------------------------------------------------- +// Console::CheckCharAvail() +// Periodically called to check if a character is available for +// input from the simulated keyboard (eg, has it been typed?). +// +// Only read it in if there is buffer space for it (if the previous +// character has been grabbed out of the buffer by the Nachos kernel). +// Invoke the "read" interrupt handler, once the character has been +// put into the buffer. +//---------------------------------------------------------------------- + +void +Console::CheckCharAvail() +{ + unsigned char c, d; + int n; + int cont = 1; + + if (readFileNo == -1) { + // Termination, don't schedule any other interrupt + readFileNo = -2; + n = 0; + cont = 0; + } else if ((incoming != NOCHAR) || !PollFile(readFileNo)) { + // do nothing if character is already buffered, or none to be read + n = 0; + } else { + // otherwise, read character and tell user about it + n = ReadPartial(readFileNo, &c, sizeof(c)); + if (n == 0) { + incoming = EOF; + (*readHandler)(handlerArg); + } else if (strcmp(nl_langinfo(CODESET),"UTF-8")) { + /* Not UTF-8, assume 8bit locale */ + incoming = c; + } else + /* UTF-8, decode */ + if (!(c & 0x80)) { + /* ASCII */ + incoming = c; + } else { + if ((c & 0xe0) != 0xc0) + /* continuation char or more than three bytes, drop */ + return; + if (c & 0x1c) + /* Not latin1, drop */ + return; + /* latin1 UTF-8 char, read second char */ + n = ReadPartial(readFileNo, &d, sizeof(d)); + if (n == 0) { + incoming = EOF; + (*readHandler)(handlerArg); + } else if ((d & 0xc0) != 0x80) { + /* Odd, drop */ + return; + } else { + incoming = (c & 0x03) << 6 | d; + } + } + } + + if (cont) + // schedule the next time to poll for a packet + interrupt->Schedule(ConsoleReadPoll, this, ConsoleTime, + ConsoleReadInt); + + if (n) { + stats->numConsoleCharsRead++; + (*readHandler)(handlerArg); + } + + +} + +//---------------------------------------------------------------------- +// Console::WriteDone() +// Internal routine called when it is time to invoke the interrupt +// handler to tell the Nachos kernel that the output character has +// completed. +//---------------------------------------------------------------------- + +void +Console::WriteDone() +{ + putBusy = FALSE; + stats->numConsoleCharsWritten++; + (*writeHandler)(handlerArg); +} + +//---------------------------------------------------------------------- +// Console::RX() +// Read a character from the input buffer, if there is any there. +// Either return the character, or EOF if none buffered or the end of the +// input file was reached. +//---------------------------------------------------------------------- + +int +Console::RX() +{ + int ch = incoming; + + // We should not be reading anything if no character was received yet + ASSERT(incoming != NOCHAR); + + incoming = NOCHAR; + return ch; +} + +//---------------------------------------------------------------------- +// Console::TX() +// Write a character to the simulated display, schedule an interrupt +// to occur in the future, and return. +//---------------------------------------------------------------------- + +void +Console::TX(int ch) +{ + unsigned char c; + + // Make sure that we are not already transferring a character + ASSERT(putBusy == FALSE); + + // Compensate when given a non-ascii latin1 character passed as signed char + if (ch < 0 && ch >= -128) + ch += 256; + + if (ch < 0x80 || strcmp(nl_langinfo(CODESET),"UTF-8")) { + /* Not UTF-8 or ASCII, assume 8bit locale */ + c = ch; + WriteFile(writeFileNo, &c, sizeof(c)); + } else if (ch < 0x100) { + /* Non-ASCII UTF-8, thus two bytes */ + c = ((ch & 0xc0) >> 6) | 0xc0; + WriteFile(writeFileNo, &c, sizeof(c)); + c = (ch & 0x3f) | 0x80; + WriteFile(writeFileNo, &c, sizeof(c)); + } /* Else not latin1, drop */ + putBusy = TRUE; + interrupt->Schedule(ConsoleWriteDone, this, ConsoleTime, + ConsoleWriteInt); +} diff --git a/code/machine/console.h b/code/machine/console.h new file mode 100644 index 0000000..45427db --- /dev/null +++ b/code/machine/console.h @@ -0,0 +1,85 @@ +// console.h +// Data structures to simulate the behavior of a terminal +// I/O device. A terminal has two parts -- a keyboard input, +// and a display output, each of which produces/accepts +// characters sequentially. +// +// The console hardware device is asynchronous. When a character is +// written to the device, the routine returns immediately, and an +// interrupt handler is called later when the I/O completes. +// For reads, an interrupt handler is called when a character arrives. +// +// The user of the device can specify the routines to be called when +// the read/write interrupts occur. There is a separate interrupt +// for read and write, and the device is "duplex" -- a character +// can be outgoing and incoming at the same time. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef CONSOLE_H +#define CONSOLE_H + +#include "copyright.h" +#include "utility.h" +#include + +// The following class defines a hardware console device. +// Input and output to the device is simulated by reading +// and writing to UNIX files ("readFile" and "writeFile"). +// +// Since the device is asynchronous, the interrupt handler "readAvailHandler" +// is called when a character has arrived, ready to be read by calling +// RX(). +// The interrupt handler "writeDone" is called when an output character written +// by calling TX() has been "put", so that the next character can be +// written. + +class Console:public dontcopythis { + public: + Console(const char *readFile, const char *writeFile, VoidFunctionPtr readAvailHandler, + VoidFunctionPtr writeDoneHandler, void *callArg); + // initialize the hardware console device, + // registers the readAvailHandler and writeDoneHandler + // callbacks + + ~Console(); // clean up console emulation + +// external interface -- Nachos kernel code can call these + void TX(int ch); // Write "ch" to the console display, + // and return immediately. "writeDone" + // is called when the I/O completes. + + int RX(); // Poll the console input. If a char is + // available, return it. Otherwise, crash. + // EOF is returned if the end of the input + // file was reached. + // "readDone" is called whenever there is + // a char to be gotten + +// internal emulation routines -- DO NOT call these. + void WriteDone(); // internal routines to signal I/O completion + void CheckCharAvail(); + + private: + int readFileNo; // UNIX file emulating the keyboard + int writeFileNo; // UNIX file emulating the display + VoidFunctionPtr writeHandler; // Interrupt handler to call when + // the TX I/O completes + VoidFunctionPtr readHandler; // Interrupt handler to call when + // a character arrives from the keyboard + void *handlerArg; // argument to be passed to the + // interrupt handlers + bool putBusy; // Is a TX operation in progress? + // If so, you can't do another one! + int incoming; // Contains the character to be read, + // if there is one available. + // Otherwise contains EOF. + static int stdin_busy; // Whether stdin is already read from + // by a console. +}; + +#endif // CONSOLE_H diff --git a/code/machine/disk.cc b/code/machine/disk.cc new file mode 100644 index 0000000..4f10581 --- /dev/null +++ b/code/machine/disk.cc @@ -0,0 +1,272 @@ +// disk.cc +// Routines to simulate a physical disk device; reading and writing +// to the disk is simulated as reading and writing to a UNIX file. +// See disk.h for details about the behavior of disks (and +// therefore about the behavior of this simulation). +// +// Disk operations are asynchronous, so we have to invoke an interrupt +// handler when the simulated operation completes. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "disk.h" +#include "system.h" + +// We put this at the front of the UNIX file representing the +// disk, to make it less likely we will accidentally treat a useful file +// as a disk (which would probably trash the file's contents). +#define MagicNumber 0x456789ab +#define MagicSize sizeof(int) + +#define DiskSize (MagicSize + (NumSectors * SectorSize)) + +// dummy procedure because we can't take a pointer of a member function +static void DiskDone(void *arg) { ((Disk *)arg)->HandleInterrupt(); } + +//---------------------------------------------------------------------- +// Disk::Disk() +// Initialize a simulated disk. Open the UNIX file (creating it +// if it doesn't exist), and check the magic number to make sure it's +// ok to treat it as Nachos disk storage. +// +// "name" -- text name of the file simulating the Nachos disk +// "callWhenDone" -- interrupt handler to be called when disk read/write +// request completes +// "callArg" -- argument to pass the interrupt handler +//---------------------------------------------------------------------- + +Disk::Disk(const char* name, VoidFunctionPtr callWhenDone, void *callArg) +{ + int magicNum; + int tmp = 0; + + DEBUG('d', "Initializing the disk, 0x%x 0x%x\n", callWhenDone, callArg); + handler = callWhenDone; + handlerArg = callArg; + lastSector = 0; + bufferInit = 0; + + fileno = OpenForReadWrite(name, FALSE); + if (fileno >= 0) { // file exists, check magic number + Read(fileno, &magicNum, MagicSize); + ASSERT(magicNum == MagicNumber); + } else { // file doesn't exist, create it + fileno = OpenForWrite(name); + magicNum = MagicNumber; + WriteFile(fileno, &magicNum, MagicSize); // write magic number + + // need to write at end of file, so that reads will not return EOF + Lseek(fileno, DiskSize - sizeof(int), SEEK_SET); + WriteFile(fileno, &tmp, sizeof(int)); + } + active = FALSE; +} + +//---------------------------------------------------------------------- +// Disk::~Disk() +// Clean up disk simulation, by closing the UNIX file representing the +// disk. +//---------------------------------------------------------------------- + +Disk::~Disk() +{ + Close(fileno); + fileno = -1; +} + +//---------------------------------------------------------------------- +// Disk::PrintSector() +// Dump the data in a disk read/write request, for debugging. +//---------------------------------------------------------------------- + +static void +PrintSector (bool writing, int sector, const void *data) +{ + const int *p = (const int *) data; + + if (writing) + printf("Writing sector: %d\n", sector); + else + printf("Reading sector: %d\n", sector); + for (unsigned int i = 0; i < (SectorSize/sizeof(int)); i++) + printf("%x ", p[i]); + printf("\n"); +} + +//---------------------------------------------------------------------- +// Disk::ReadRequest/WriteRequest +// Simulate a request to read/write a single disk sector +// Do the read/write immediately to the UNIX file +// Set up an interrupt handler to be called later, +// that will notify the caller when the simulator says +// the operation has completed. +// +// Note that a disk only allows an entire sector to be read/written, +// not part of a sector. +// +// "sectorNumber" -- the disk sector to read/write +// "data" -- the bytes to be written, the buffer to hold the incoming bytes +//---------------------------------------------------------------------- + +void +Disk::ReadRequest(int sectorNumber, void* data) +{ + int ticks = ComputeLatency(sectorNumber, FALSE); + + ASSERT(!active); // only one request at a time + ASSERT((sectorNumber >= 0) && (sectorNumber < NumSectors)); + + DEBUG('d', "Reading from sector %d\n", sectorNumber); + Lseek(fileno, SectorSize * sectorNumber + MagicSize, SEEK_SET); + Read(fileno, data, SectorSize); + if (DebugIsEnabled('d')) + PrintSector(FALSE, sectorNumber, data); + + active = TRUE; + UpdateLast(sectorNumber); + stats->numDiskReads++; + interrupt->Schedule(DiskDone, this, ticks, DiskInt); +} + +void +Disk::WriteRequest(int sectorNumber, const void* data) +{ + int ticks = ComputeLatency(sectorNumber, TRUE); + + ASSERT(!active); + ASSERT((sectorNumber >= 0) && (sectorNumber < NumSectors)); + + DEBUG('d', "Writing to sector %d\n", sectorNumber); + Lseek(fileno, SectorSize * sectorNumber + MagicSize, SEEK_SET); + WriteFile(fileno, data, SectorSize); + if (DebugIsEnabled('d')) + PrintSector(TRUE, sectorNumber, data); + + active = TRUE; + UpdateLast(sectorNumber); + stats->numDiskWrites++; + interrupt->Schedule(DiskDone, this, ticks, DiskInt); +} + +//---------------------------------------------------------------------- +// Disk::HandleInterrupt() +// Called when it is time to invoke the disk interrupt handler, +// to tell the Nachos kernel that the disk request is done. +//---------------------------------------------------------------------- + +void +Disk::HandleInterrupt () +{ + active = FALSE; + (*handler)(handlerArg); +} + +//---------------------------------------------------------------------- +// Disk::TimeToSeek() +// Returns how long it will take to position the disk head over the correct +// track on the disk. Since when we finish seeking, we are likely +// to be in the middle of a sector that is rotating past the head, +// we also return how long until the head is at the next sector boundary. +// +// Disk seeks at one track per SeekTime ticks (cf. stats.h) +// and rotates at one sector per RotationTime ticks +//---------------------------------------------------------------------- + +int +Disk::TimeToSeek(int newSector, int *rotation) +{ + int newTrack = newSector / SectorsPerTrack; + int oldTrack = lastSector / SectorsPerTrack; + int seek = abs(newTrack - oldTrack) * SeekTime; + // how long will seek take? + int over = (stats->totalTicks + seek) % RotationTime; + // will we be in the middle of a sector when + // we finish the seek? + + *rotation = 0; + if (over > 0) // if so, need to round up to next full sector + *rotation = RotationTime - over; + return seek; +} + +//---------------------------------------------------------------------- +// Disk::ModuloDiff() +// Return number of sectors of rotational delay between target sector +// "to" and current sector position "from" +//---------------------------------------------------------------------- + +int +Disk::ModuloDiff(int to, int from) +{ + int toOffset = to % SectorsPerTrack; + int fromOffset = from % SectorsPerTrack; + + return ((toOffset - fromOffset) + SectorsPerTrack) % SectorsPerTrack; +} + +//---------------------------------------------------------------------- +// Disk::ComputeLatency() +// Return how long will it take to read/write a disk sector, from +// the current position of the disk head. +// +// Latency = seek time + rotational latency + transfer time +// Disk seeks at one track per SeekTime ticks (cf. stats.h) +// and rotates at one sector per RotationTime ticks +// +// To find the rotational latency, we first must figure out where the +// disk head will be after the seek (if any). We then figure out +// how long it will take to rotate completely past newSector after +// that point. +// +// The disk also has a "track buffer"; the disk continuously reads +// the contents of the current disk track into the buffer. This allows +// read requests to the current track to be satisfied more quickly. +// The contents of the track buffer are discarded after every seek to +// a new track. +//---------------------------------------------------------------------- + +int +Disk::ComputeLatency(int newSector, bool writing) +{ + int rotation; + int seek = TimeToSeek(newSector, &rotation); + int timeAfter = stats->totalTicks + seek + rotation; + +#ifndef NOTRACKBUF // turn this on if you don't want the track buffer stuff + // check if track buffer applies + if ((writing == FALSE) && (seek == 0) + && (((timeAfter - bufferInit) / RotationTime) + > ModuloDiff(newSector, bufferInit / RotationTime))) { + DEBUG('d', "Request latency = %d\n", RotationTime); + return RotationTime; // time to transfer sector from the track buffer + } +#endif + + rotation += ModuloDiff(newSector, timeAfter / RotationTime) * RotationTime; + + DEBUG('d', "Request latency = %d\n", seek + rotation + RotationTime); + return(seek + rotation + RotationTime); +} + +//---------------------------------------------------------------------- +// Disk::UpdateLast +// Keep track of the most recently requested sector. So we can know +// what is in the track buffer. +//---------------------------------------------------------------------- + +void +Disk::UpdateLast(int newSector) +{ + int rotate; + int seek = TimeToSeek(newSector, &rotate); + + if (seek != 0) + bufferInit = stats->totalTicks + seek + rotate; + lastSector = newSector; + DEBUG('d', "Updating last sector = %d, %d\n", lastSector, bufferInit); +} diff --git a/code/machine/disk.h b/code/machine/disk.h new file mode 100644 index 0000000..af2ef86 --- /dev/null +++ b/code/machine/disk.h @@ -0,0 +1,93 @@ +// disk.h +// Data structures to emulate a physical disk. A physical disk +// can accept (one at a time) requests to read/write a disk sector; +// when the request is satisfied, the CPU gets an interrupt, and +// the next request can be sent to the disk. +// +// Disk contents are preserved across machine crashes, but if +// a file system operation (eg, create a file) is in progress when the +// system shuts down, the file system may be corrupted. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef DISK_H +#define DISK_H + +#include "copyright.h" +#include "utility.h" + +// The following class defines a physical disk I/O device. The disk +// has a single surface, split up into "tracks", and each track split +// up into "sectors" (the same number of sectors on each track, and each +// sector has the same number of bytes of storage). +// +// Addressing is by sector number -- each sector on the disk is given +// a unique number: track * SectorsPerTrack + offset within a track. +// +// As with other I/O devices, the raw physical disk is an asynchronous device -- +// requests to read or write portions of the disk return immediately, +// and an interrupt is invoked later to signal that the operation completed. +// +// The physical disk is in fact simulated via operations on a UNIX file. +// +// To make life a little more realistic, the simulated time for +// each operation reflects a "track buffer" -- RAM to store the contents +// of the current track as the disk head passes by. The idea is that the +// disk always transfers to the track buffer, in case that data is requested +// later on. This has the benefit of eliminating the need for +// "skip-sector" scheduling -- a read request which comes in shortly after +// the head has passed the beginning of the sector can be satisfied more +// quickly, because its contents are in the track buffer. Most +// disks these days now come with a track buffer. +// +// The track buffer simulation can be disabled by compiling with -DNOTRACKBUF + +#define SectorSize 128 // number of bytes per disk sector +#define SectorsPerTrack 32 // number of sectors per disk track +#define NumTracks 32 // number of tracks per disk +#define NumSectors (SectorsPerTrack * NumTracks) + // total # of sectors per disk + +class Disk:public dontcopythis { + public: + Disk(const char* name, VoidFunctionPtr callWhenDone, void *callArg); + // Create a simulated disk. + // Invoke (*callWhenDone)(callArg) + // every time a request completes. + ~Disk(); // Deallocate the disk. + + void ReadRequest(int sectorNumber, void* data); + // Read/write an single disk sector. + // These routines send a request to + // the disk and return immediately. + // Only one request allowed at a time! + void WriteRequest(int sectorNumber, const void* data); + + void HandleInterrupt(); // Interrupt handler, invoked when + // disk request finishes. + + int ComputeLatency(int newSector, bool writing); + // Return how long a request to + // newSector will take: + // (seek + rotational delay + transfer) + + private: + int fileno; // UNIX file number for simulated disk + VoidFunctionPtr handler; // Interrupt handler, to be invoked + // when any disk request finishes + void *handlerArg; // Argument to interrupt handler + bool active; // Is a disk operation in progress? + int lastSector; // The previous disk request + int bufferInit; // When the track buffer started + // being loaded + + int TimeToSeek(int newSector, int *rotate); // time to get to the new track + int ModuloDiff(int to, int from); // # sectors between to and from + void UpdateLast(int newSector); +}; + +#endif // DISK_H diff --git a/code/machine/interrupt.cc b/code/machine/interrupt.cc new file mode 100644 index 0000000..6fc4caa --- /dev/null +++ b/code/machine/interrupt.cc @@ -0,0 +1,385 @@ +// interrupt.cc +// Routines to simulate hardware interrupts. +// +// The hardware provides a routine (SetLevel) to enable or disable +// interrupts. +// +// In order to emulate the hardware, we need to keep track of all +// interrupts the hardware devices would cause, and when they +// are supposed to occur. +// +// This module also keeps track of simulated time. Time advances +// only when the following occur: +// interrupts are re-enabled +// a user instruction is executed +// there is nothing in the ready queue +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "interrupt.h" +#include "system.h" +#include "sysdep.h" + +// String definitions for debugging messages + +static const char *intLevelNames[] = { "off", "on"}; +static const char *intTypeNames[] = { "timer", "disk", "console write", + "console read", "network send", "network recv"}; + +//---------------------------------------------------------------------- +// PendingInterrupt::PendingInterrupt +// Initialize a hardware device interrupt that is to be scheduled +// to occur in the near future. +// +// "func" is the procedure to call when the interrupt occurs +// "param" is the argument to pass to the procedure +// "time" is when (in simulated time) the interrupt is to occur +// "kind" is the hardware device that generated the interrupt +//---------------------------------------------------------------------- + +PendingInterrupt::PendingInterrupt(VoidFunctionPtr func, void *param, long long time, + IntType kind) +{ + handler = func; + arg = param; + when = time; + type = kind; +} + +//---------------------------------------------------------------------- +// Interrupt::Interrupt +// Initialize the simulation of hardware device interrupts. +// +// Interrupts start disabled, with no interrupts pending, etc. +//---------------------------------------------------------------------- + +Interrupt::Interrupt() +{ + level = IntOff; + pending = new List(); + inHandler = FALSE; + yieldOnReturn = FALSE; + status = SystemMode; +} + +//---------------------------------------------------------------------- +// Interrupt::~Interrupt +// De-allocate the data structures needed by the interrupt simulation. +//---------------------------------------------------------------------- + +Interrupt::~Interrupt() +{ + while (!pending->IsEmpty()) + delete (PendingInterrupt *)(pending->Remove()); + delete pending; + pending = NULL; +} + +//---------------------------------------------------------------------- +// Interrupt::ChangeLevel +// Change interrupts to be enabled or disabled, without advancing +// the simulated time (normally, enabling interrupts advances the time). + +//---------------------------------------------------------------------- +// Interrupt::ChangeLevel +// Change interrupts to be enabled or disabled, without advancing +// the simulated time (normally, enabling interrupts advances the time). +// +// Used internally. +// +// "old" -- the old interrupt status +// "now" -- the new interrupt status +//---------------------------------------------------------------------- +void +Interrupt::ChangeLevel(IntStatus old, IntStatus now) +{ + if (now == IntOff) + BlockUserAbort(); + level = now; + DEBUG('i',"\tinterrupts: %s -> %s\n",intLevelNames[old],intLevelNames[now]); + if (now == IntOn) + UnBlockUserAbort(); +} + +//---------------------------------------------------------------------- +// Interrupt::SetLevel +// Change interrupts to be enabled or disabled, and if interrupts +// are being enabled, advance simulated time by calling OneTick(). +// +// Returns: +// The old interrupt status. +// Parameters: +// "now" -- the new interrupt status +//---------------------------------------------------------------------- + +IntStatus +Interrupt::SetLevel(IntStatus now) +{ + IntStatus old = level; + + ASSERT((now == IntOff) || (inHandler == FALSE));// interrupt handlers are + // prohibited from enabling + // interrupts + + ChangeLevel(old, now); // change to new state + if ((now == IntOn) && (old == IntOff)) + OneTick(); // advance simulated time + return old; +} + +//---------------------------------------------------------------------- +// Interrupt::Enable +// Turn interrupts on. Who cares what they used to be? +// Used in ThreadRoot, to turn interrupts on when first starting up +// a thread. +//---------------------------------------------------------------------- +void +Interrupt::Enable() +{ + (void) SetLevel(IntOn); +} + +//---------------------------------------------------------------------- +// Interrupt::OneTick +// Advance simulated time and check if there are any pending +// interrupts to be called. +// +// Two things can cause OneTick to be called: +// interrupts are re-enabled +// a user instruction is executed +//---------------------------------------------------------------------- +void +Interrupt::OneTick() +{ + MachineStatus old = status; + +// advance simulated time + if (status == SystemMode) { + stats->totalTicks += SystemTick; + stats->systemTicks += SystemTick; + } else { // USER_PROGRAM + stats->totalTicks += UserTick; + stats->userTicks += UserTick; + } + DEBUG('i', "\n== Tick %lld ==\n", stats->totalTicks); + +// check any pending interrupts are now ready to fire + ChangeLevel(IntOn, IntOff); // first, turn off interrupts + // (interrupt handlers run with + // interrupts disabled) + while (CheckIfDue(FALSE)) // check for pending interrupts + ; + ChangeLevel(IntOff, IntOn); // re-enable interrupts + if (yieldOnReturn) { // if the timer device handler asked + // for a context switch, ok to do it now + yieldOnReturn = FALSE; + status = SystemMode; // yield is a kernel routine + currentThread->Yield(); + status = old; + } +} + +//---------------------------------------------------------------------- +// Interrupt::YieldOnReturn +// Called from within an interrupt handler, to cause a context switch +// (for example, on a time slice) in the interrupted thread, +// when the handler returns. +// +// We can't do the context switch here, because that would switch +// out the interrupt handler, and we want to switch out the +// interrupted thread. +//---------------------------------------------------------------------- + +void +Interrupt::YieldOnReturn() +{ + ASSERT(inHandler == TRUE); + yieldOnReturn = TRUE; +} + +//---------------------------------------------------------------------- +// Interrupt::Idle +// Routine called when there is nothing in the ready queue. +// +// Since something has to be running in order to put a thread +// on the ready queue, the only thing to do is to advance +// simulated time until the next scheduled hardware interrupt. +// +// If there are no pending interrupts, stop. There's nothing +// more for us to do. +//---------------------------------------------------------------------- +void +Interrupt::Idle() +{ + DEBUG('i', "Machine idling; checking for interrupts.\n"); + status = IdleMode; + if (CheckIfDue(TRUE)) { // check for any pending interrupts + while (CheckIfDue(FALSE)) // check for any other pending + ; // interrupts + yieldOnReturn = FALSE; // since there's nothing in the + // ready queue, the yield is automatic + status = SystemMode; + return; // return in case there's now + // a runnable thread + } + + // if there are no pending interrupts, and nothing is on the ready + // queue, it is time to stop. If the console or the network is + // operating, there are *always* pending interrupts, so this code + // is not reached. Instead, the halt must be invoked by the user program. + + DEBUG('i', "Machine idle. No interrupts to do.\n"); + printf("No threads ready or runnable, and no pending interrupts.\n"); + printf("Assuming the program completed.\n"); + Powerdown(); +} + +//---------------------------------------------------------------------- +// Interrupt::Powerdown +// Shut down Nachos cleanly, printing out performance statistics. +//---------------------------------------------------------------------- +void +Interrupt::Powerdown() +{ + printf("Machine going down!\n\n"); + stats->Print(); + Cleanup(); // Never returns. +} + +//---------------------------------------------------------------------- +// Interrupt::Schedule +// Arrange for the CPU to be interrupted when simulated time +// reaches "now + when". +// +// Implementation: just put it on a sorted list. +// +// NOTE: the Nachos kernel should not call this routine directly. +// Instead, it is only called by the hardware device simulators. +// +// "handler" is the procedure to call when the interrupt occurs +// "arg" is the argument to pass to the procedure +// "fromNow" is how far in the future (in simulated time) the +// interrupt is to occur +// "type" is the hardware device that generated the interrupt +//---------------------------------------------------------------------- +void +Interrupt::Schedule(VoidFunctionPtr handler, void *arg, long long fromNow, IntType type) +{ + long long when = stats->totalTicks + fromNow; + PendingInterrupt *toOccur = new PendingInterrupt(handler, arg, when, type); + + DEBUG('i', "Scheduling interrupt handler the %s at time = %lld\n", + intTypeNames[type], when); + ASSERT(fromNow > 0); + + pending->SortedInsert(toOccur, when); +} + +//---------------------------------------------------------------------- +// Interrupt::CheckIfDue +// Check if an interrupt is scheduled to occur, and if so, fire it off. +// +// Returns: +// TRUE, if we fired off any interrupt handlers +// Params: +// "advanceClock" -- if TRUE, there is nothing in the ready queue, +// so we should simply advance the clock to when the next +// pending interrupt would occur (if any). If the pending +// interrupt is just the time-slice daemon, however, then +// we're done! +//---------------------------------------------------------------------- +bool +Interrupt::CheckIfDue(bool advanceClock) +{ + MachineStatus old = status; + long long when; + + ASSERT(level == IntOff); // interrupts need to be disabled, + // to invoke an interrupt handler + + UnBlockUserAbort(); // Here it is safe to let the User abort + BlockUserAbort(); + + if (DebugIsEnabled('i')) + DumpState(); + PendingInterrupt *toOccur = + (PendingInterrupt *)pending->SortedRemove(&when); + + if (toOccur == NULL) // no pending interrupts + return FALSE; + + if (advanceClock && when > stats->totalTicks) { // advance the clock + stats->idleTicks += (when - stats->totalTicks); + stats->totalTicks = when; + } else if (when > stats->totalTicks) { // not time yet, put it back + pending->SortedInsert(toOccur, when); + return FALSE; + } + +// Check if there is nothing more to do, and if so, quit + if ((status == IdleMode) && (toOccur->type == TimerInt) + && pending->IsEmpty()) { + pending->SortedInsert(toOccur, when); + return FALSE; + } + + DEBUG('i', "Invoking interrupt handler for the %s at time %lld\n", + intTypeNames[toOccur->type], toOccur->when); +#ifdef USER_PROGRAM + if (machine != NULL && status == UserMode) + machine->DelayedLoad(0, 0); +#endif + inHandler = TRUE; + status = SystemMode; // whatever we were doing, + // we are now going to be + // running in the kernel + (*(toOccur->handler))(toOccur->arg); // call the interrupt handler + status = old; // restore the machine status + inHandler = FALSE; + delete toOccur; + return TRUE; +} + +//---------------------------------------------------------------------- +// PrintPending +// Print information about an interrupt that is scheduled to occur. +// When, where, why, etc. +//---------------------------------------------------------------------- + +static void +PrintPending(void *arg) +{ + PendingInterrupt *pend = (PendingInterrupt *)arg; + + printf("Interrupt handler %s, scheduled at %lld\n", + intTypeNames[pend->type], pend->when); +} + +//---------------------------------------------------------------------- +// DumpState +// Print the complete interrupt state - the status, and all interrupts +// that are scheduled to occur in the future. +//---------------------------------------------------------------------- + +void +Interrupt::DumpState() +{ + // LB: Print format adapted after the promotion of tick type + // from int to long long + // printf("Time: %d, interrupts %s\n", stats->totalTicks, + // intLevelNames[level]); + printf("Time: %lld, interrupts %s\n", stats->totalTicks, + intLevelNames[level]); + // End of correction + + printf("Pending interrupts:\n"); + fflush(stdout); + pending->Mapcar(PrintPending); + printf("End of pending interrupts\n"); + fflush(stdout); +} diff --git a/code/machine/interrupt.h b/code/machine/interrupt.h new file mode 100644 index 0000000..b07f6c6 --- /dev/null +++ b/code/machine/interrupt.h @@ -0,0 +1,134 @@ +// interrupt.h +// Data structures to emulate low-level interrupt hardware. +// +// The hardware provides a routine (SetLevel) to enable or disable +// interrupts. +// +// In order to emulate the hardware, we need to keep track of all +// interrupts the hardware devices would cause, and when they +// are supposed to occur. +// +// This module also keeps track of simulated time. Time advances +// only when the following occur: +// interrupts are re-enabled +// a user instruction is executed +// there is nothing in the ready queue +// +// As a result, unlike real hardware, interrupts (and thus time-slice +// context switches) cannot occur anywhere in the code where interrupts +// are enabled, but rather only at those places in the code where +// simulated time advances (so that it becomes time to invoke an +// interrupt in the hardware simulation). +// +// NOTE: this means that incorrectly synchronized code may work +// fine on this hardware simulation (even with randomized time slices), +// but it wouldn't work on real hardware. (Just because we can't +// always detect when your program would fail in real life, does not +// mean it's ok to write incorrectly synchronized code!) +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef INTERRUPT_H +#define INTERRUPT_H + +#include "copyright.h" +#include "list.h" + +// Interrupts can be disabled (IntOff) or enabled (IntOn) +enum IntStatus { IntOff, IntOn }; + +// Nachos can be running kernel code (SystemMode), user code (UserMode), +// or there can be no runnable thread, because the ready list +// is empty (IdleMode). +enum MachineStatus {IdleMode, SystemMode, UserMode}; + +// IntType records which hardware device generated an interrupt. +// In Nachos, we support a hardware timer device, a disk, a console +// display and keyboard, and a network. +enum IntType { TimerInt, DiskInt, ConsoleWriteInt, ConsoleReadInt, + NetworkSendInt, NetworkRecvInt}; + +// The following class defines an interrupt that is scheduled +// to occur in the future. The internal data structures are +// left public to make it simpler to manipulate. + +class PendingInterrupt { + public: + PendingInterrupt(VoidFunctionPtr func, void *param, + long long time, IntType kind); + // initialize an interrupt that will + // occur in the future + + VoidFunctionPtr handler; // The function (in the hardware device + // emulator) to call when the interrupt occurs + void *arg; // The argument to the function. + long long when; // When the interrupt is supposed to fire + IntType type; // for debugging +}; + +// The following class defines the data structures for the simulation +// of hardware interrupts. We record whether interrupts are enabled +// or disabled, and any hardware interrupts that are scheduled to occur +// in the future. + +class Interrupt:public dontcopythis { + public: + Interrupt(); // initialize the interrupt simulation + ~Interrupt(); // de-allocate data structures + + IntStatus SetLevel(IntStatus level);// Disable or enable interrupts + // and return previous setting. + + void Enable(); // Enable interrupts. + IntStatus getLevel() {return level;}// Return whether interrupts + // are enabled or disabled + + void Idle(); // The ready queue is empty, roll + // simulated time forward until the + // next interrupt + + void Powerdown(); // quit and print out stats + + void YieldOnReturn(); // cause a context switch on return + // from an interrupt handler + + MachineStatus getStatus() { return status; } // idle, kernel, user + void setStatus(MachineStatus st) { status = st; } + + void DumpState(); // Print interrupt state + + + // NOTE: the following are internal to the hardware simulation code. + // DO NOT call these directly. I should make them "private", + // but they need to be public since they are called by the + // hardware device simulators. + + void Schedule(VoidFunctionPtr handler,// Schedule an interrupt to occur + void *arg, long long when, IntType type);// at time ``when''. This is called + // by the hardware device simulators. + + void OneTick(); // Advance simulated time + + private: + IntStatus level; // are interrupts enabled or disabled? + List *pending; // the list of interrupts scheduled + // to occur in the future + bool inHandler; // TRUE if we are running an interrupt handler + bool yieldOnReturn; // TRUE if we are to context switch + // on return from the interrupt handler + MachineStatus status; // idle, kernel mode, user mode + + // these functions are internal to the interrupt simulation code + + bool CheckIfDue(bool advanceClock); // Check if an interrupt is supposed + // to occur now + + void ChangeLevel(IntStatus old, // SetLevel, without advancing the + IntStatus now); // simulated time +}; + +#endif // INTERRRUPT_H diff --git a/code/machine/machine.cc b/code/machine/machine.cc new file mode 100644 index 0000000..af290ec --- /dev/null +++ b/code/machine/machine.cc @@ -0,0 +1,437 @@ +// machine.cc +// Routines for simulating the execution of user programs. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "machine.h" +#include "system.h" + +// Textual names of the exceptions that can be generated by user program +// execution, for debugging. +static const char* exceptionNames[] = { "no exception", "syscall", + "page fault/no TLB entry", "page read only", + "bus error", "address error", "overflow", + "illegal instruction" }; + +//---------------------------------------------------------------------- +// CheckEndian +// Check to be sure that the host really uses the format it says it +// does, for storing the bytes of an integer. Stop on error. +//---------------------------------------------------------------------- + +static +void CheckEndian() +{ + union checkit { + char charword[4]; + unsigned int intword; + } check; + + check.charword[0] = 1; + check.charword[1] = 2; + check.charword[2] = 3; + check.charword[3] = 4; + +#ifdef HOST_IS_BIG_ENDIAN + ASSERT (check.intword == 0x01020304); +#else + ASSERT (check.intword == 0x04030201); +#endif +} + +//---------------------------------------------------------------------- +// Machine::Machine +// Initialize the simulation of user program execution. +// +// "debug" -- if TRUE, drop into the debugger after each user instruction +// is executed. +//---------------------------------------------------------------------- + +Machine::Machine(bool debug) +{ + int i; + + for (i = 0; i < NumTotalRegs; i++) + registers[i] = 0; + mainMemory = new char[MemorySize]; + for (i = 0; i < MemorySize; i++) + mainMemory[i] = 0; +#ifdef USE_TLB + tlb = new TranslationEntry[TLBSize]; + for (i = 0; i < TLBSize; i++) + tlb[i].valid = FALSE; +#else // use linear page table + tlb = NULL; +#endif + currentPageTable = NULL; + currentPageTableSize = 0; + + singleStep = debug; + runUntilTime = 0; + CheckEndian(); +} + +//---------------------------------------------------------------------- +// Machine::~Machine +// De-allocate the data structures used to simulate user program execution. +//---------------------------------------------------------------------- + +Machine::~Machine() +{ + delete [] mainMemory; + mainMemory = NULL; + if (tlb != NULL) + { + delete [] tlb; + tlb = NULL; + } +} + +//---------------------------------------------------------------------- +// Machine::RaiseException +// Transfer control to the Nachos kernel from user mode, because +// the user program either invoked a system call, or some exception +// occured (such as the address translation failed). +// +// "which" -- the cause of the kernel trap +// "badVaddr" -- the virtual address causing the trap, if appropriate +//---------------------------------------------------------------------- + +void +Machine::RaiseException(ExceptionType which, int badVAddr) +{ + enum MachineStatus oldStatus = interrupt->getStatus(); + DEBUG('m', "Exception: %s\n", exceptionNames[which]); + + registers[BadVAddrReg] = badVAddr; + DelayedLoad(0, 0); // finish anything in progress + interrupt->setStatus(SystemMode); + ExceptionHandler(which); // interrupts are enabled at this point + interrupt->setStatus(oldStatus); +} + +//---------------------------------------------------------------------- +// Machine::Debugger +// Primitive debugger for user programs. Note that we can't use +// gdb to debug user programs, since gdb doesn't run on top of Nachos. +// It could, but you'd have to implement *a lot* more system calls +// to get it to work! +// +// So just allow single-stepping, and printing the contents of memory. +//---------------------------------------------------------------------- + +void Machine::Debugger() +{ + char *buf = new char[80]; + int num; + + interrupt->DumpState(); + DumpState(); + + // LB: Update the print format after the promotion of tick types + // from int to long long + // printf("%d> ", stats->totalTicks); + printf("%lld> ", stats->totalTicks); + // End of correction + + fflush(stdout); + fgets(buf, 80, stdin); + if (sscanf(buf, "%d", &num) == 1) + runUntilTime = num; + else { + runUntilTime = 0; + switch (*buf) { + case '\n': + break; + + case 'c': + singleStep = FALSE; + break; + + case '?': + printf("Machine commands:\n"); + printf(" execute one instruction\n"); + printf(" run until the given timer tick\n"); + printf(" c run until completion\n"); + printf(" ? print help message\n"); + break; + } + } + delete [] buf; +} + +//---------------------------------------------------------------------- +// Machine::DumpState +// Print the user program's CPU state. We might print the contents +// of memory, but that seemed like overkill. +//---------------------------------------------------------------------- + +void +Machine::DumpState() +{ + int i; + + printf("Machine registers:\n"); + for (i = 0; i < NumGPRegs; i++) + switch (i) { + case StackReg: + printf("\tSP(%d):\t0x%x%s", i, registers[i], + ((i % 4) == 3) ? "\n" : ""); + break; + + case RetAddrReg: + printf("\tRA(%d):\t0x%x%s", i, registers[i], + ((i % 4) == 3) ? "\n" : ""); + break; + + default: + printf("\t%d:\t0x%x%s", i, registers[i], + ((i % 4) == 3) ? "\n" : ""); + break; + } + + printf("\tHi:\t0x%x", registers[HiReg]); + printf("\tLo:\t0x%x\n", registers[LoReg]); + printf("\tPC:\t0x%x", registers[PCReg]); + printf("\tNextPC:\t0x%x", registers[NextPCReg]); + printf("\tPrevPC:\t0x%x\n", registers[PrevPCReg]); + printf("\tLoad:\t0x%x", registers[LoadReg]); + printf("\tLoadV:\t0x%x\n", registers[LoadValueReg]); + printf("\n"); +} + +//---------------------------------------------------------------------- +// DumpReg +// Draw a pointer register in the virtual address space +//---------------------------------------------------------------------- + +void +Machine::DumpReg(FILE *output, int val, const char *name, const char *color, + int ptr_x, int ptr_y, unsigned virtual_x, + unsigned y, unsigned blocksize) +{ + unsigned page = val / PageSize; + unsigned offset = val % PageSize; + + fprintf(output, "%s\n", + ptr_x, y - page * blocksize, color, color, blocksize, name); + fprintf(output, "\n", + ptr_x + 3*blocksize/2, + ptr_y - page * blocksize - blocksize/2, + virtual_x + offset * blocksize + blocksize/2, + ptr_y - page * blocksize - blocksize/2); +} + +//---------------------------------------------------------------------- +// DumpRegs +// Draw machine pointer registers in the virtual address space +//---------------------------------------------------------------------- + +void +Machine::DumpRegs(FILE *output, int ptr_x, int ptr_y, unsigned virtual_x, + unsigned y, unsigned blocksize) +{ + DumpReg(output, registers[PCReg], "PC", "#FF0000", ptr_x, ptr_y, virtual_x, y, blocksize); + DumpReg(output, registers[StackReg], "SP", "#FF0000", ptr_x, ptr_y, virtual_x, y, blocksize); +} + +//---------------------------------------------------------------------- +// PageTableRoom +// Return how much room would be needed for showing this page table +//---------------------------------------------------------------------- + +unsigned +Machine::PageTableRoom(unsigned numPages, unsigned blocksize) +{ + return (numPages+1) * blocksize; +} + +//---------------------------------------------------------------------- +// get_RGB +// Turn a byte into a representative color of the byte +//---------------------------------------------------------------------- + +static void +get_RGB(unsigned char value, unsigned char *r, unsigned char *g, unsigned char *b) +{ + *r = (value & 0x7) << 5; + *g = (value & 0x38) << 2; + *b = (value & 0xc0) << 0; // Humans don't see blue that well +} + +//---------------------------------------------------------------------- +// DumpPageTable +// Draw a page table and its mapping to physical address space +//---------------------------------------------------------------------- + +unsigned +Machine::DumpPageTable(FILE *output, + TranslationEntry *_pageTable, unsigned _pageTableSize, + unsigned addr_x, unsigned virtual_x, unsigned virtual_width, + unsigned physical_x, unsigned virtual_y, unsigned y, + unsigned blocksize) +{ + unsigned page, offset; + + for (page = 0; page < _pageTableSize; page++) { + TranslationEntry *e = &_pageTable[page]; + + fprintf(output, "0x%04x\n", + addr_x, virtual_y - page * blocksize, blocksize, page * PageSize); + + if (e->valid) { + for (offset = 0; offset < PageSize; offset++) { + int value; + unsigned char r, g, b; + int virt = page * PageSize + offset; + int phys; + + TranslationEntry *save_pageTable = currentPageTable; + unsigned save_pageTableSize = currentPageTableSize; + + currentPageTable = _pageTable; + currentPageTableSize = _pageTableSize; + + ExceptionType res = Translate(virt, &phys, 1, FALSE, FALSE); + if (res == NoException) + ReadMem(virt, 1, &value, FALSE); + else + value = -1; + + currentPageTable = save_pageTable; + currentPageTableSize = save_pageTableSize; + + get_RGB(value, &r, &g, &b); + + fprintf(output, "\n", + virtual_x + offset * blocksize, + virtual_y - page * blocksize - blocksize, + blocksize, blocksize, + r, g, b); + } + + fprintf(output, "\n", + virtual_x + virtual_width, + virtual_y - page * blocksize - blocksize/2, + physical_x, + y - e->physicalPage * blocksize - blocksize/2); + } + } + + if (_pageTable == currentPageTable) { + fprintf(output, "\n", + virtual_x, + virtual_y - _pageTableSize * blocksize, + virtual_width, _pageTableSize * blocksize); + } + + return PageTableRoom(_pageTableSize, blocksize); +} + +//---------------------------------------------------------------------- +// Machine::DumpMem +// Draw the user program's memory layout. +//---------------------------------------------------------------------- + +void +Machine::DumpMem(const char *name) +{ + FILE *output = fopen(name, "w+"); + + const unsigned blocksize = 32; + + const unsigned addr_x = 0; + const unsigned addr_width = 4*blocksize; + + const unsigned ptr_x = addr_x + addr_width; + const unsigned ptr_width = 6*blocksize; + + const unsigned virtual_x = ptr_x + ptr_width; + const unsigned virtual_width = PageSize * blocksize; + + const unsigned physical_x = virtual_x + virtual_width + 30 * blocksize; + const unsigned physical_width = PageSize * blocksize; + + const unsigned width = physical_x + physical_width; + unsigned height; + unsigned page, offset; + + unsigned virtual_height = AddrSpacesRoom(blocksize); + unsigned physical_height = NumPhysPages * blocksize; + + height = virtual_height > physical_height ? virtual_height : physical_height; + + fprintf(output, "\n"); + fprintf(output, "\n", width, height); + + fprintf(output, "\n", + virtual_x, + height - currentPageTableSize * blocksize, + virtual_width, + currentPageTableSize * blocksize); + + DumpAddrSpaces(output, addr_x, ptr_x, virtual_x, virtual_width, physical_x, height, blocksize); + + for (page = 0; page < NumPhysPages; page++) { + for (offset = 0; offset < PageSize; offset++) { + int value; + unsigned char r, g, b; + + value = machine->mainMemory[page * PageSize + offset]; + get_RGB(value, &r, &g, &b); + + fprintf(output, "\n", + physical_x + offset * blocksize, + height - page * blocksize - blocksize, + blocksize, blocksize, + r, g, b); + } + } + + fprintf(output, "\n"); + fclose(output); +} + +//---------------------------------------------------------------------- +// Machine::ReadRegister/WriteRegister +// Fetch or write the contents of a user program register. +//---------------------------------------------------------------------- + +int Machine::ReadRegister(int num) + { + ASSERT((num >= 0) && (num < NumTotalRegs)); + return registers[num]; + } + +void Machine::WriteRegister(int num, int value) + { + ASSERT((num >= 0) && (num < NumTotalRegs)); + // DEBUG('m', "WriteRegister %d, value %d\n", num, value); + registers[num] = value; + } + diff --git a/code/machine/machine.h b/code/machine/machine.h new file mode 100644 index 0000000..79556db --- /dev/null +++ b/code/machine/machine.h @@ -0,0 +1,239 @@ +// machine.h +// Data structures for simulating the execution of user programs +// running on top of Nachos. +// +// User programs are loaded into "mainMemory"; to Nachos, +// this looks just like an array of bytes. Of course, the Nachos +// kernel is in memory too -- but as in most machines these days, +// the kernel is loaded into a separate memory region from user +// programs, and accesses to kernel memory are not translated or paged. +// +// In Nachos, user programs are executed one instruction at a time, +// by the simulator. Each memory reference is translated, checked +// for errors, etc. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef MACHINE_H +#define MACHINE_H + +#include "copyright.h" +#include "utility.h" +#include "translate.h" +#include "disk.h" + +// Definitions related to the size, and format of user memory + +#define PageSize SectorSize // set the page size equal to + // the disk sector size, for + // simplicity + +#define NumPhysPages 64 // Increase this as necessary! +#define MemorySize (NumPhysPages * PageSize) +#define TLBSize 4 // if there is a TLB, make it small + +enum ExceptionType { NoException, // Everything ok! + SyscallException, // A program executed a system call. + PageFaultException, // No valid translation found + ReadOnlyException, // Write attempted to page marked + // "read-only" + BusErrorException, // Translation resulted in an + // invalid physical address + AddressErrorException, // Unaligned reference or one that + // was beyond the end of the + // address space + OverflowException, // Integer overflow in add or sub. + IllegalInstrException, // Unimplemented or reserved instr. + + NumExceptionTypes +}; + +// User program CPU state. The full set of MIPS registers, plus a few +// more because we need to be able to start/stop a user program between +// any two instructions (thus we need to keep track of things like load +// delay slots, etc.) + +#define StackReg 29 // User's stack pointer +#define RetAddrReg 31 // Holds return address for procedure calls +#define NumGPRegs 32 // 32 general purpose registers on MIPS +#define HiReg 32 // Double register to hold multiply result +#define LoReg 33 +#define PCReg 34 // Current program counter +#define NextPCReg 35 // Next program counter (for branch delay) +#define PrevPCReg 36 // Previous program counter (for debugging) +#define LoadReg 37 // The register target of a delayed load. +#define LoadValueReg 38 // The value to be loaded by a delayed load. +#define BadVAddrReg 39 // The failing virtual address on an exception + +#define NumTotalRegs 40 + +// The following class defines an instruction, represented in both +// undecoded binary form +// decoded to identify +// operation to do +// registers to act on +// any immediate operand value + +class Instruction { + public: + void Decode(); // decode the binary representation of the instruction + + unsigned int value; // binary representation of the instruction + + // Type of instruction. This is NOT the same as the + // opcode field from the instruction: see defs in mips.h + unsigned char opCode; + // Three registers from instruction. + unsigned char rs, rt, rd; + // Immediate or target or shamt field or offset. + // Immediates are sign-extended. + unsigned int extra; +}; + +// The following class defines the simulated host workstation hardware, as +// seen by user programs -- the CPU registers, main memory, etc. +// User programs shouldn't be able to tell that they are running on our +// simulator or on the real hardware, except +// we don't support floating point instructions +// the system call interface to Nachos is not the same as UNIX +// (10 system calls in Nachos vs. 200 in UNIX!) +// If we were to implement more of the UNIX system calls, we ought to be +// able to run Nachos on top of Nachos! +// +// The procedures in this class are defined in machine.cc, mipssim.cc, and +// translate.cc. + +class Machine:public dontcopythis { + public: + Machine(bool debug); // Initialize the simulation of the hardware + // for running user programs + ~Machine(); // De-allocate the data structures + +// Routines callable by the Nachos kernel + void Run(); // Run a user program + + int ReadRegister(int num); // read the contents of a CPU register + + void WriteRegister(int num, int value); + // store a value into a CPU register + + +// Routines internal to the machine simulation -- DO NOT call these + + void OneInstruction(Instruction *instr); + // Run one instruction of a user program. + void DelayedLoad(int nextReg, int nextVal); + // Do a pending delayed load (modifying a reg) + + bool ReadMem(int addr, int size, int* value); + bool ReadMem(int addr, int size, int* value, bool debug); + bool WriteMem(int addr, int size, int value); + // Read or write 1, 2, or 4 bytes of virtual + // memory (at addr). Return FALSE if a + // correct translation couldn't be found. + + ExceptionType Translate(int virtAddr, int* physAddr, int size, bool writing, bool debug); + // Translate an address, and check for + // alignment. Set the use and dirty bits in + // the translation entry appropriately, + // and return an exception code if the + // translation couldn't be completed. + + void RaiseException(ExceptionType which, int badVAddr); + // Trap to the Nachos kernel, because of a + // system call or other exception. + + void Debugger(); // invoke the user program debugger + void DumpState(); // print the user CPU and memory state + void DumpMem(const char *name); // Draw the memory state + void DumpReg(FILE *output, int val, const char *name, const char *color, + int ptr_x, int ptr_y, unsigned virtual_x, + unsigned y, unsigned blocksize); + // Dump a register + void DumpRegs(FILE *output, int ptr_x, int ptr_y, unsigned virtual_x, + unsigned y, unsigned blocksize); + // Dump the machine registers + unsigned PageTableRoom(unsigned numPages, unsigned blocksize); + // Return how much room is needed for a page table + unsigned DumpPageTable(FILE *output, + TranslationEntry *pageTable, unsigned pageTableSize, + unsigned addr_x, unsigned virtual_x, unsigned virtual_width, + unsigned physical_x, unsigned virtual_y, unsigned y, + unsigned blocksize); + // Dump a pagetable + + +// Data structures -- all of these are accessible to Nachos kernel code. +// "public" for convenience. +// +// Note that *all* communication between the user program and the kernel +// are in terms of these data structures. + + char *mainMemory; // physical memory to store user program, + // code and data, while executing + int registers[NumTotalRegs]; // CPU registers, for executing user programs + + +// NOTE: the hardware translation of virtual addresses in the user program +// to physical addresses (relative to the beginning of "mainMemory") +// can be controlled by one of: +// a traditional linear page table +// a software-loaded translation lookaside buffer (tlb) -- a cache of +// mappings of virtual page #'s to physical page #'s +// +// If "tlb" is NULL, the linear page table is used +// If "tlb" is non-NULL, the Nachos kernel is responsible for managing +// the contents of the TLB. But the kernel can use any data structure +// it wants (eg, segmented paging) for handling TLB cache misses. +// +// For simplicity, both the page table pointer and the TLB pointer are +// public. However, while there can be multiple page tables (one per address +// space, stored in memory), there is only one TLB (implemented in hardware). +// Thus the TLB pointer should be considered as *read-only*, although +// the contents of the TLB are free to be modified by the kernel software. + + TranslationEntry *tlb; // this pointer should be considered + // "read-only" to Nachos kernel code + + TranslationEntry *currentPageTable; + unsigned int currentPageTableSize; + + private: + bool singleStep; // drop back into the debugger after each + // simulated instruction + int runUntilTime; // drop back into the debugger when simulated + // time reaches this value +}; + +extern void ExceptionHandler(ExceptionType which); + // Entry point into Nachos for handling + // user system calls and exceptions + // Defined in exception.cc + + +// Routines for converting Words and Short Words to and from the +// simulated machine's format of little endian. If the host machine +// is little endian (DEC and Intel), these end up being NOPs. +// +// What is stored in each format: +// host byte ordering: +// kernel data structures +// user registers +// simulated machine byte ordering: +// contents of main memory + +unsigned int WordToHost(unsigned int word); +unsigned short ShortToHost(unsigned short shortword); +unsigned int WordToMachine(unsigned int word); +unsigned short ShortToMachine(unsigned short shortword); + +extern unsigned AddrSpacesRoom(unsigned blocksize); +extern void DumpAddrSpaces(FILE *output, + unsigned addr_x, unsigned sections_x, unsigned virtual_x, unsigned virtual_width, + unsigned physical_x, unsigned y, unsigned blocksize); + +#endif // MACHINE_H diff --git a/code/machine/mipssim.cc b/code/machine/mipssim.cc new file mode 100644 index 0000000..3f6b475 --- /dev/null +++ b/code/machine/mipssim.cc @@ -0,0 +1,703 @@ +// mipssim.cc -- simulate a MIPS R2/3000 processor +// +// This code has been adapted from Ousterhout's MIPSSIM package. +// Byte ordering is little-endian, so we can be compatible with +// DEC RISC systems. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#include "machine.h" +#include "mipssim.h" +#include "system.h" + +static void Mult(int a, int b, bool signedArith, int* hiPtr, int* loPtr); + +//---------------------------------------------------------------------- +// Machine::Run +// Simulate the execution of a user-level program on Nachos. +// Called by the kernel when the program starts up; never returns. +// +// This routine is re-entrant, in that it can be called multiple +// times concurrently -- one for each thread executing user code. +//---------------------------------------------------------------------- + +void +Machine::Run() +{ + // LB: Using a dynamic instr is right here as one never exits this + // function. + // Instruction *instr = new Instruction; // storage for decoded instruction + Instruction the_instr; + Instruction *instr = &the_instr; + // End of Modification + + if(DebugIsEnabled('m')) + + // LB: Update the print format after the promotion of tick types + // from int to long long + // printf("Starting thread \"%s\" at time %d\n", + // currentThread->getName(), stats->totalTicks); + printf("Starting thread \"%s\" at %d and %d at time %lld\n", + currentThread->getName(), + machine->ReadRegister(PCReg), + machine->ReadRegister(NextPCReg), + stats->totalTicks); + // End of correction + + interrupt->setStatus(UserMode); + for (;;) { + OneInstruction(instr); + interrupt->OneTick(); + if (singleStep && (runUntilTime <= stats->totalTicks)) + Debugger(); + } +} + + +//---------------------------------------------------------------------- +// TypeToReg +// Retrieve the register # referred to in an instruction. +//---------------------------------------------------------------------- + +static int +TypeToReg(RegType reg, Instruction *instr) +{ + switch (reg) { + case RS: + return instr->rs; + case RT: + return instr->rt; + case RD: + return instr->rd; + case EXTRA: + return instr->extra; + default: + return -1; + } +} + +//---------------------------------------------------------------------- +// Machine::OneInstruction +// Execute one instruction from a user-level program +// +// If there is any kind of exception or interrupt, we invoke the +// exception handler, and when it returns, we return to Run(), which +// will re-invoke us in a loop. This allows us to +// re-start the instruction execution from the beginning, in +// case any of our state has changed. On a syscall, +// the OS software must increment the PC so execution begins +// at the instruction immediately after the syscall. +// +// This routine is re-entrant, in that it can be called multiple +// times concurrently -- one for each thread executing user code. +// We get re-entrancy by never caching any data -- we always re-start the +// simulation from scratch each time we are called (or after trapping +// back to the Nachos kernel on an exception or interrupt), and we always +// store all data back to the machine registers and memory before +// leaving. This allows the Nachos kernel to control our behavior +// by controlling the contents of memory, the translation table, +// and the register set. +//---------------------------------------------------------------------- + +void +Machine::OneInstruction(Instruction *instr) +{ + int raw; + int nextLoadReg = 0; + int nextLoadValue = 0; // record delayed load operation, to apply + // in the future + + // Fetch instruction + if (!machine->ReadMem(registers[PCReg], 4, &raw)) + return; // exception occurred + instr->value = raw; + instr->Decode(); + + if (DebugIsEnabled('m')) { + struct OpString *str = &opStrings[instr->opCode]; + + ASSERT(instr->opCode <= MaxOpcode); + printf("At PC = 0x%x: ", registers[PCReg]); + printf(str->string, TypeToReg(str->args[0], instr), + TypeToReg(str->args[1], instr), TypeToReg(str->args[2], instr)); + printf("\n"); + } + + // Compute next pc, but don't install in case there's an error or branch. + int pcAfter = registers[NextPCReg] + 4; + int sum, diff, tmp, value; + unsigned int rs, rt, imm; + + unsigned tmp_unsigned; + + // Execute the instruction (cf. Kane's book) + switch (instr->opCode) { + + case OP_ADD: + sum = registers[instr->rs] + registers[instr->rt]; + if (!((registers[instr->rs] ^ registers[instr->rt]) & SIGN_BIT) && + ((registers[instr->rs] ^ sum) & SIGN_BIT)) { + RaiseException(OverflowException, 0); + return; + } + registers[instr->rd] = sum; + break; + + case OP_ADDI: + sum = registers[instr->rs] + instr->extra; + if (!((registers[instr->rs] ^ instr->extra) & SIGN_BIT) && + ((instr->extra ^ sum) & SIGN_BIT)) { + RaiseException(OverflowException, 0); + return; + } + registers[instr->rt] = sum; + break; + + case OP_ADDIU: + registers[instr->rt] = registers[instr->rs] + instr->extra; + break; + + case OP_ADDU: + registers[instr->rd] = registers[instr->rs] + registers[instr->rt]; + break; + + case OP_AND: + registers[instr->rd] = registers[instr->rs] & registers[instr->rt]; + break; + + case OP_ANDI: + registers[instr->rt] = registers[instr->rs] & (instr->extra & 0xffff); + break; + + case OP_BEQ: + if (registers[instr->rs] == registers[instr->rt]) + pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra); + break; + + case OP_BGEZAL: + registers[R31] = registers[NextPCReg] + 4; + /* FALLTHRU */ + case OP_BGEZ: + if (!(registers[instr->rs] & SIGN_BIT)) + pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra); + break; + + case OP_BGTZ: + if (registers[instr->rs] > 0) + pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra); + break; + + case OP_BLEZ: + if (registers[instr->rs] <= 0) + pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra); + break; + + case OP_BLTZAL: + registers[R31] = registers[NextPCReg] + 4; + /* FALLTHRU */ + case OP_BLTZ: + if (registers[instr->rs] & SIGN_BIT) + pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra); + break; + + case OP_BNE: + if (registers[instr->rs] != registers[instr->rt]) + pcAfter = registers[NextPCReg] + IndexToAddr(instr->extra); + break; + + case OP_DIV: + if (registers[instr->rt] == 0) { + registers[LoReg] = 0; + registers[HiReg] = 0; + } else { + registers[LoReg] = registers[instr->rs] / registers[instr->rt]; + registers[HiReg] = registers[instr->rs] % registers[instr->rt]; + } + break; + + case OP_DIVU: + rs = (unsigned int) registers[instr->rs]; + rt = (unsigned int) registers[instr->rt]; + if (rt == 0) { + registers[LoReg] = 0; + registers[HiReg] = 0; + } else { + tmp = rs / rt; + registers[LoReg] = (int) tmp; + tmp = rs % rt; + registers[HiReg] = (int) tmp; + } + break; + + case OP_JAL: + registers[R31] = registers[NextPCReg] + 4; + /* FALLTHRU */ + case OP_J: + pcAfter = (pcAfter & 0xf0000000) | IndexToAddr(instr->extra); + break; + + case OP_JALR: + registers[instr->rd] = registers[NextPCReg] + 4; + /* FALLTHRU */ + case OP_JR: + pcAfter = registers[instr->rs]; + break; + + case OP_LB: + case OP_LBU: + tmp = registers[instr->rs] + instr->extra; + if (!machine->ReadMem(tmp, 1, &value)) + return; + + if ((value & 0x80) && (instr->opCode == OP_LB)) + value |= 0xffffff00; + else + value &= 0xff; + nextLoadReg = instr->rt; + nextLoadValue = value; + break; + + case OP_LH: + case OP_LHU: + tmp = registers[instr->rs] + instr->extra; + if (tmp & 0x1) { + RaiseException(AddressErrorException, tmp); + return; + } + if (!machine->ReadMem(tmp, 2, &value)) + return; + + if ((value & 0x8000) && (instr->opCode == OP_LH)) + value |= 0xffff0000; + else + value &= 0xffff; + nextLoadReg = instr->rt; + nextLoadValue = value; + break; + + case OP_LUI: + DEBUG('m', "Executing: LUI r%d,%d\n", instr->rt, instr->extra); + registers[instr->rt] = instr->extra << 16; + break; + + case OP_LW: + tmp = registers[instr->rs] + instr->extra; + if (tmp & 0x3) { + RaiseException(AddressErrorException, tmp); + return; + } + if (!machine->ReadMem(tmp, 4, &value)) + return; + nextLoadReg = instr->rt; + nextLoadValue = value; + break; + + case OP_LWR: + tmp = registers[instr->rs] + instr->extra; + + // ReadMem assumes all 4 byte requests are aligned on an even + // word boundary. + + if (!machine->ReadMem(tmp & ~0x3, 4, &value)) + return; + if (registers[LoadReg] == instr->rt) + nextLoadValue = registers[LoadValueReg]; + else + nextLoadValue = registers[instr->rt]; + switch (tmp & 0x3) { + case 0: + nextLoadValue = value; + break; + case 1: + nextLoadValue = (nextLoadValue & 0xff000000) + | ((value >> 8) & 0xffffff); + break; + case 2: + nextLoadValue = (nextLoadValue & 0xffff0000) + | ((value >> 16) & 0xffff); + break; + case 3: + nextLoadValue = (nextLoadValue & 0xffffff00) + | ((value >> 24) & 0xff); + break; + } + nextLoadReg = instr->rt; + break; + + case OP_LWL: + tmp = registers[instr->rs] + instr->extra; + + // ReadMem assumes all 4 byte requests are aligned on an even + // word boundary. + if (!machine->ReadMem(tmp & ~0x3, 4, &value)) + return; + if (registers[LoadReg] == instr->rt) + nextLoadValue = registers[LoadValueReg]; + else + nextLoadValue = registers[instr->rt]; + switch (tmp & 0x3) { + case 0: + nextLoadValue = (nextLoadValue & 0xff) | (value << 8); + break; + case 1: + nextLoadValue = (nextLoadValue & 0xffff) | (value << 16); + break; + case 2: + nextLoadValue = (nextLoadValue & 0xffffff) | (value << 24); + break; + case 3: + nextLoadValue = value; + break; + } + nextLoadReg = instr->rt; + break; + + case OP_MFHI: + registers[instr->rd] = registers[HiReg]; + break; + + case OP_MFLO: + registers[instr->rd] = registers[LoReg]; + break; + + case OP_MTHI: + registers[HiReg] = registers[instr->rs]; + break; + + case OP_MTLO: + registers[LoReg] = registers[instr->rs]; + break; + + case OP_MULT: + Mult(registers[instr->rs], registers[instr->rt], TRUE, + ®isters[HiReg], ®isters[LoReg]); + break; + + case OP_MULTU: + Mult(registers[instr->rs], registers[instr->rt], FALSE, + ®isters[HiReg], ®isters[LoReg]); + break; + + case OP_NOR: + registers[instr->rd] = ~(registers[instr->rs] | registers[instr->rt]); + break; + + case OP_OR: + registers[instr->rd] = registers[instr->rs] | registers[instr->rt]; + break; + + case OP_ORI: + registers[instr->rt] = registers[instr->rs] | (instr->extra & 0xffff); + break; + + case OP_SB: + if (!machine->WriteMem((unsigned) + (registers[instr->rs] + instr->extra), 1, registers[instr->rt])) + return; + break; + + case OP_SH: + if (!machine->WriteMem((unsigned) + (registers[instr->rs] + instr->extra), 2, registers[instr->rt])) + return; + break; + + case OP_SLL: + registers[instr->rd] = (int) (((unsigned) registers[instr->rt]) << instr->extra); + break; + + case OP_SLLV: + registers[instr->rd] = (int) (((unsigned) registers[instr->rt]) << + (registers[instr->rs] & 0x1f)); + break; + + case OP_SLT: + if (registers[instr->rs] < registers[instr->rt]) + registers[instr->rd] = 1; + else + registers[instr->rd] = 0; + break; + + case OP_SLTI: + if (registers[instr->rs] < (int) instr->extra) + registers[instr->rt] = 1; + else + registers[instr->rt] = 0; + break; + + case OP_SLTIU: + rs = registers[instr->rs]; + imm = instr->extra; + if (rs < imm) + registers[instr->rt] = 1; + else + registers[instr->rt] = 0; + break; + + case OP_SLTU: + rs = registers[instr->rs]; + rt = registers[instr->rt]; + if (rs < rt) + registers[instr->rd] = 1; + else + registers[instr->rd] = 0; + break; + + case OP_SRA: + registers[instr->rd] = registers[instr->rt] >> instr->extra; + break; + + case OP_SRAV: + registers[instr->rd] = registers[instr->rt] >> + (registers[instr->rs] & 0x1f); + break; + + case OP_SRL: + tmp_unsigned = registers[instr->rt]; + tmp_unsigned >>= instr->extra; + registers[instr->rd] = tmp_unsigned; + break; + + case OP_SRLV: + tmp_unsigned = registers[instr->rt]; + tmp_unsigned >>= (registers[instr->rs] & 0x1f); + registers[instr->rd] = tmp_unsigned; + + // End of correction + //------------------------------------------------------------ + break; + + case OP_SUB: + diff = registers[instr->rs] - registers[instr->rt]; + if (((registers[instr->rs] ^ registers[instr->rt]) & SIGN_BIT) && + ((registers[instr->rs] ^ diff) & SIGN_BIT)) { + RaiseException(OverflowException, 0); + return; + } + registers[instr->rd] = diff; + break; + + case OP_SUBU: + registers[instr->rd] = registers[instr->rs] - registers[instr->rt]; + break; + + case OP_SW: + if (!machine->WriteMem((unsigned) + (registers[instr->rs] + instr->extra), 4, registers[instr->rt])) + return; + break; + + case OP_SWR: + tmp = registers[instr->rs] + instr->extra; + + if (!machine->ReadMem((tmp & ~0x3), 4, &value)) + return; + switch (tmp & 0x3) { + case 0: + value = registers[instr->rt]; + break; + case 1: + value = (value & 0xff) | (registers[instr->rt] << 8); + break; + case 2: + value = (value & 0xffff) | (registers[instr->rt] << 16); + break; + case 3: + value = (value & 0xffffff) | (registers[instr->rt] << 24); + break; + } + if (!machine->WriteMem((tmp & ~0x3), 4, value)) + return; + break; + + case OP_SWL: + tmp = registers[instr->rs] + instr->extra; + + if (!machine->ReadMem((tmp & ~0x3), 4, &value)) + return; + switch (tmp & 0x3) { + case 0: + value = (value & 0xffffff00) | ((registers[instr->rt] >> 24) & + 0xff); + break; + case 1: + value = (value & 0xffff0000) | ((registers[instr->rt] >> 16) & + 0xffff); + break; + case 2: + value = (value & 0xff000000) | ((registers[instr->rt] >> 8) & + 0xffffff); + break; + case 3: + value = registers[instr->rt]; + break; + } + if (!machine->WriteMem((tmp & ~0x3), 4, value)) + return; + break; + + case OP_SYSCALL: + RaiseException(SyscallException, 0); + return; + + case OP_XOR: + registers[instr->rd] = registers[instr->rs] ^ registers[instr->rt]; + break; + + case OP_XORI: + registers[instr->rt] = registers[instr->rs] ^ (instr->extra & 0xffff); + break; + + case OP_RES: + case OP_UNIMP: + RaiseException(IllegalInstrException, 0); + return; + + default: + ASSERT(FALSE); + } + + // Now we have successfully executed the instruction. + + // Do any delayed load operation + DelayedLoad(nextLoadReg, nextLoadValue); + + // Advance program counters. + registers[PrevPCReg] = registers[PCReg]; // for debugging, in case we + // are jumping into lala-land + registers[PCReg] = registers[NextPCReg]; + registers[NextPCReg] = pcAfter; +} + +//---------------------------------------------------------------------- +// Machine::DelayedLoad +// Simulate effects of a delayed load. +// +// NOTE -- RaiseException/CheckInterrupts must also call DelayedLoad, +// since any delayed load must get applied before we trap to the kernel. +//---------------------------------------------------------------------- + +void +Machine::DelayedLoad(int nextReg, int nextValue) +{ + registers[registers[LoadReg]] = registers[LoadValueReg]; + registers[LoadReg] = nextReg; + registers[LoadValueReg] = nextValue; + registers[0] = 0; // and always make sure R0 stays zero. +} + +//---------------------------------------------------------------------- +// Instruction::Decode +// Decode a MIPS instruction +//---------------------------------------------------------------------- + +void +Instruction::Decode() +{ + OpInfo *opPtr; + + rs = (value >> 21) & 0x1f; + rt = (value >> 16) & 0x1f; + rd = (value >> 11) & 0x1f; + opPtr = &opTable[(value >> 26) & 0x3f]; + opCode = opPtr->opCode; + if (opPtr->format == IFMT) { + extra = value & 0xffff; + if (extra & 0x8000) { + extra |= 0xffff0000; + } + } else if (opPtr->format == RFMT) { + extra = (value >> 6) & 0x1f; + } else { + extra = value & 0x3ffffff; + } + if (opCode == SPECIAL) { + opCode = specialTable[value & 0x3f]; + } else if (opCode == BCOND) { + int i = value & 0x1f0000; + + if (i == 0) { + opCode = OP_BLTZ; + } else if (i == 0x10000) { + opCode = OP_BGEZ; + } else if (i == 0x100000) { + opCode = OP_BLTZAL; + } else if (i == 0x110000) { + opCode = OP_BGEZAL; + } else { + opCode = OP_UNIMP; + } + } +} + +//---------------------------------------------------------------------- +// Mult +// Simulate R2000 multiplication. +// The words at *hiPtr and *loPtr are overwritten with the +// double-length result of the multiplication. +//---------------------------------------------------------------------- + +static void +Mult(int a, int b, bool signedArith, int* hiPtr, int* loPtr) +{ + if ((a == 0) || (b == 0)) { + *hiPtr = *loPtr = 0; + return; + } + + // Compute the sign of the result, then make everything positive + // so unsigned computation can be done in the main loop. + bool negative = FALSE; + if (signedArith) { + if (a < 0) { + negative = !negative; + a = -a; + } + if (b < 0) { + negative = !negative; + b = -b; + } + } + + // Compute the result in unsigned arithmetic (check a's bits one at + // a time, and add in a shifted value of b). + unsigned int bLo = b; + unsigned int bHi = 0; + unsigned int lo = 0; + unsigned int hi = 0; + for (int i = 0; i < 32; i++) { + if (a & 1) { + lo += bLo; + if (lo < bLo) // Carry out of the low bits? + hi += 1; + hi += bHi; + if ((a & 0xfffffffe) == 0) + break; + } + bHi <<= 1; + if (bLo & 0x80000000) + bHi |= 1; + + bLo <<= 1; + a >>= 1; + } + + // If the result is supposed to be negative, compute the two's + // complement of the double-word result. + if (negative) { + hi = ~hi; + lo = ~lo; + lo++; + if (lo == 0) + hi++; + } + + *hiPtr = (int) hi; + *loPtr = (int) lo; +} diff --git a/code/machine/mipssim.h b/code/machine/mipssim.h new file mode 100644 index 0000000..569419d --- /dev/null +++ b/code/machine/mipssim.h @@ -0,0 +1,229 @@ +// mipssim.h +// Internal data structures for simulating the MIPS instruction set. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef MIPSSIM_H +#define MIPSSIM_H + +#include "copyright.h" + +/* + * OpCode values. The names are straight from the MIPS + * manual except for the following special ones: + * + * OP_UNIMP - means that this instruction is legal, but hasn't + * been implemented in the simulator yet. + * OP_RES - means that this is a reserved opcode (it isn't + * supported by the architecture). + */ + +#define OP_ADD 1 +#define OP_ADDI 2 +#define OP_ADDIU 3 +#define OP_ADDU 4 +#define OP_AND 5 +#define OP_ANDI 6 +#define OP_BEQ 7 +#define OP_BGEZ 8 +#define OP_BGEZAL 9 +#define OP_BGTZ 10 +#define OP_BLEZ 11 +#define OP_BLTZ 12 +#define OP_BLTZAL 13 +#define OP_BNE 14 + +#define OP_DIV 16 +#define OP_DIVU 17 +#define OP_J 18 +#define OP_JAL 19 +#define OP_JALR 20 +#define OP_JR 21 +#define OP_LB 22 +#define OP_LBU 23 +#define OP_LH 24 +#define OP_LHU 25 +#define OP_LUI 26 +#define OP_LW 27 +#define OP_LWL 28 +#define OP_LWR 29 + +#define OP_MFHI 31 +#define OP_MFLO 32 + +#define OP_MTHI 34 +#define OP_MTLO 35 +#define OP_MULT 36 +#define OP_MULTU 37 +#define OP_NOR 38 +#define OP_OR 39 +#define OP_ORI 40 +#define OP_RFE 41 +#define OP_SB 42 +#define OP_SH 43 +#define OP_SLL 44 +#define OP_SLLV 45 +#define OP_SLT 46 +#define OP_SLTI 47 +#define OP_SLTIU 48 +#define OP_SLTU 49 +#define OP_SRA 50 +#define OP_SRAV 51 +#define OP_SRL 52 +#define OP_SRLV 53 +#define OP_SUB 54 +#define OP_SUBU 55 +#define OP_SW 56 +#define OP_SWL 57 +#define OP_SWR 58 +#define OP_XOR 59 +#define OP_XORI 60 +#define OP_SYSCALL 61 +#define OP_UNIMP 62 +#define OP_RES 63 +#define MaxOpcode 63 + +/* + * Miscellaneous definitions: + */ + +#define IndexToAddr(x) ((x) << 2) + +#define SIGN_BIT 0x80000000 +#define R31 31 + +/* + * The table below is used to translate bits 31:26 of the instruction + * into a value suitable for the "opCode" field of a MemWord structure, + * or into a special value for further decoding. + */ + +#define SPECIAL 100 +#define BCOND 101 + +#define IFMT 1 +#define JFMT 2 +#define RFMT 3 + +struct OpInfo { + int opCode; /* Translated op code. */ + int format; /* Format type (IFMT or JFMT or RFMT) */ +}; + +static OpInfo opTable[] = { + {SPECIAL, RFMT}, {BCOND, IFMT}, {OP_J, JFMT}, {OP_JAL, JFMT}, + {OP_BEQ, IFMT}, {OP_BNE, IFMT}, {OP_BLEZ, IFMT}, {OP_BGTZ, IFMT}, + {OP_ADDI, IFMT}, {OP_ADDIU, IFMT}, {OP_SLTI, IFMT}, {OP_SLTIU, IFMT}, + {OP_ANDI, IFMT}, {OP_ORI, IFMT}, {OP_XORI, IFMT}, {OP_LUI, IFMT}, + {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, + {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, + {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, + {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, + {OP_LB, IFMT}, {OP_LH, IFMT}, {OP_LWL, IFMT}, {OP_LW, IFMT}, + {OP_LBU, IFMT}, {OP_LHU, IFMT}, {OP_LWR, IFMT}, {OP_RES, IFMT}, + {OP_SB, IFMT}, {OP_SH, IFMT}, {OP_SWL, IFMT}, {OP_SW, IFMT}, + {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_SWR, IFMT}, {OP_RES, IFMT}, + {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, + {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, + {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, {OP_UNIMP, IFMT}, + {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT}, {OP_RES, IFMT} +}; + +/* + * The table below is used to convert the "funct" field of SPECIAL + * instructions into the "opCode" field of a MemWord. + */ + +static int specialTable[] = { + OP_SLL, OP_RES, OP_SRL, OP_SRA, OP_SLLV, OP_RES, OP_SRLV, OP_SRAV, + OP_JR, OP_JALR, OP_RES, OP_RES, OP_SYSCALL, OP_UNIMP, OP_RES, OP_RES, + OP_MFHI, OP_MTHI, OP_MFLO, OP_MTLO, OP_RES, OP_RES, OP_RES, OP_RES, + OP_MULT, OP_MULTU, OP_DIV, OP_DIVU, OP_RES, OP_RES, OP_RES, OP_RES, + OP_ADD, OP_ADDU, OP_SUB, OP_SUBU, OP_AND, OP_OR, OP_XOR, OP_NOR, + OP_RES, OP_RES, OP_SLT, OP_SLTU, OP_RES, OP_RES, OP_RES, OP_RES, + OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, + OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES, OP_RES +}; + + +// Stuff to help print out each instruction, for debugging + +enum RegType { NONE, RS, RT, RD, EXTRA }; + +struct OpString { + const char *string; // Printed version of instruction + RegType args[3]; +}; + +static struct OpString opStrings[] = { + {"Shouldn't happen", {NONE, NONE, NONE}}, + {"ADD r%d,r%d,r%d", {RD, RS, RT}}, + {"ADDI r%d,r%d,%d", {RT, RS, EXTRA}}, + {"ADDIU r%d,r%d,%d", {RT, RS, EXTRA}}, + {"ADDU r%d,r%d,r%d", {RD, RS, RT}}, + {"AND r%d,r%d,r%d", {RD, RS, RT}}, + {"ANDI r%d,r%d,%d", {RT, RS, EXTRA}}, + {"BEQ r%d,r%d,%d", {RS, RT, EXTRA}}, + {"BGEZ r%d,%d", {RS, EXTRA, NONE}}, + {"BGEZAL r%d,%d", {RS, EXTRA, NONE}}, + {"BGTZ r%d,%d", {RS, EXTRA, NONE}}, + {"BLEZ r%d,%d", {RS, EXTRA, NONE}}, + {"BLTZ r%d,%d", {RS, EXTRA, NONE}}, + {"BLTZAL r%d,%d", {RS, EXTRA, NONE}}, + {"BNE r%d,r%d,%d", {RS, RT, EXTRA}}, + {"Shouldn't happen", {NONE, NONE, NONE}}, + {"DIV r%d,r%d", {RS, RT, NONE}}, + {"DIVU r%d,r%d", {RS, RT, NONE}}, + {"J 4*%d", {EXTRA, NONE, NONE}}, + {"JAL 4*%d", {EXTRA, NONE, NONE}}, + {"JALR r%d,r%d", {RD, RS, NONE}}, + {"JR r%d,r%d", {RD, RS, NONE}}, + {"LB r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"LBU r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"LH r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"LHU r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"LUI r%d,%d", {RT, EXTRA, NONE}}, + {"LW r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"LWL r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"LWR r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"Shouldn't happen", {NONE, NONE, NONE}}, + {"MFHI r%d", {RD, NONE, NONE}}, + {"MFLO r%d", {RD, NONE, NONE}}, + {"Shouldn't happen", {NONE, NONE, NONE}}, + {"MTHI r%d", {RS, NONE, NONE}}, + {"MTLO r%d", {RS, NONE, NONE}}, + {"MULT r%d,r%d", {RS, RT, NONE}}, + {"MULTU r%d,r%d", {RS, RT, NONE}}, + {"NOR r%d,r%d,r%d", {RD, RS, RT}}, + {"OR r%d,r%d,r%d", {RD, RS, RT}}, + {"ORI r%d,r%d,%d", {RT, RS, EXTRA}}, + {"RFE", {NONE, NONE, NONE}}, + {"SB r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"SH r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"SLL r%d,r%d,%d", {RD, RT, EXTRA}}, + {"SLLV r%d,r%d,r%d", {RD, RT, RS}}, + {"SLT r%d,r%d,r%d", {RD, RS, RT}}, + {"SLTI r%d,r%d,%d", {RT, RS, EXTRA}}, + {"SLTIU r%d,r%d,%d", {RT, RS, EXTRA}}, + {"SLTU r%d,r%d,r%d", {RD, RS, RT}}, + {"SRA r%d,r%d,%d", {RD, RT, EXTRA}}, + {"SRAV r%d,r%d,r%d", {RD, RT, RS}}, + {"SRL r%d,r%d,%d", {RD, RT, EXTRA}}, + {"SRLV r%d,r%d,r%d", {RD, RT, RS}}, + {"SUB r%d,r%d,r%d", {RD, RS, RT}}, + {"SUBU r%d,r%d,r%d", {RD, RS, RT}}, + {"SW r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"SWL r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"SWR r%d,%d(r%d)", {RT, EXTRA, RS}}, + {"XOR r%d,r%d,r%d", {RD, RS, RT}}, + {"XORI r%d,r%d,%d", {RT, RS, EXTRA}}, + {"SYSCALL", {NONE, NONE, NONE}}, + {"Unimplemented", {NONE, NONE, NONE}}, + {"Reserved", {NONE, NONE, NONE}} + }; + +#endif // MIPSSIM_H diff --git a/code/machine/network.cc b/code/machine/network.cc new file mode 100644 index 0000000..83a09f6 --- /dev/null +++ b/code/machine/network.cc @@ -0,0 +1,139 @@ +// network.cc +// Routines to simulate a network interface, using UNIX sockets +// to deliver packets between multiple invocations of nachos. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "system.h" + +#include /* for bzero */ + +// Dummy functions because C++ can't call member functions indirectly +static void NetworkReadPoll(void *arg) +{ Network *net = (Network *)arg; net->CheckPktAvail(); } +static void NetworkSendDone(void *arg) +{ Network *net = (Network *)arg; net->SendDone(); } + +// Initialize the network emulation +// addr is used to generate the socket name +// reliability says whether we drop packets to emulate unreliable links +// readAvailHandler, writeDoneHandler, callArg -- analogous to console +Network::Network(NetworkAddress addr, double reliability, + VoidFunctionPtr readAvailHandler, VoidFunctionPtr writeDoneHandler, void *callArg) +{ + ident = addr; + if (reliability < 0) chanceToWork = 0; + else if (reliability > 1) chanceToWork = 1; + else chanceToWork = reliability; + + // set up the stuff to emulate asynchronous interrupts + writeHandler = writeDoneHandler; + readHandler = readAvailHandler; + handlerArg = callArg; + sendBusy = FALSE; + inHdr.length = 0; + + sock = OpenSocket(); + sprintf(sockName, "SOCKET_%d", (int)addr); + AssignNameToSocket(sockName, sock); // Bind socket to a filename + // in the current directory. + + // start polling for incoming packets + interrupt->Schedule(NetworkReadPoll, this, NetworkTime, NetworkRecvInt); +} + +Network::~Network() +{ + CloseSocket(sock); + sock = -1; + DeAssignNameToSocket(sockName); +} + +// if a packet is already buffered, we simply delay reading +// the incoming packet. In real life, the incoming +// packet might be dropped if we can't read it in time. +void +Network::CheckPktAvail() +{ + // schedule the next time to poll for a packet + interrupt->Schedule(NetworkReadPoll, this, NetworkTime, NetworkRecvInt); + + if (inHdr.length != 0) // do nothing if packet is already buffered + return; + if (!PollSocket(sock)) // do nothing if no packet to be read + return; + + // otherwise, read packet in + char *buffer = new char[MaxWireSize]; + ReadFromSocket(sock, buffer, MaxWireSize); + + // divide packet into header and data + inHdr = *(PacketHeader *)buffer; + ASSERT((inHdr.to == ident) && (inHdr.length <= MaxPacketSize)); + bcopy(buffer + sizeof(PacketHeader), inbox, inHdr.length); + delete []buffer ; + + DEBUG('n', "Network received packet from %d, length %d...\n", + (int) inHdr.from, inHdr.length); + stats->numPacketsRecvd++; + + // tell post office that the packet has arrived + (*readHandler)(handlerArg); +} + +// notify user that another packet can be sent +void +Network::SendDone() +{ + sendBusy = FALSE; + stats->numPacketsSent++; + (*writeHandler)(handlerArg); +} + +// send a packet by concatenating hdr and data, and schedule +// an interrupt to tell the user when the next packet can be sent +// +// Note we always pad out a packet to MaxWireSize before putting it into +// the socket, because it's simpler at the receive end. +void +Network::Send(PacketHeader hdr, const void* data) +{ + char toName[32]; + + sprintf(toName, "SOCKET_%d", (int)hdr.to); + + ASSERT((sendBusy == FALSE) && (hdr.length > 0) + && (hdr.length <= MaxPacketSize) && (hdr.from == ident)); + DEBUG('n', "Sending to addr %d, %d bytes... ", hdr.to, hdr.length); + + interrupt->Schedule(NetworkSendDone, this, NetworkTime, NetworkSendInt); + + if (Random() % 100 >= chanceToWork * 100) { // emulate a lost packet + DEBUG('n', "oops, lost it!\n"); + return; + } + + // concatenate hdr and data into a single buffer, and send it out + char *buffer = new char[MaxWireSize]; + *(PacketHeader *)buffer = hdr; + bcopy(data, buffer + sizeof(PacketHeader), hdr.length); + SendToSocket(sock, buffer, MaxWireSize, toName); + delete []buffer; +} + +// read a packet, if one is buffered +PacketHeader +Network::Receive(void* data) +{ + PacketHeader hdr = inHdr; + + inHdr.length = 0; + if (hdr.length != 0) + bcopy(inbox, data, hdr.length); + return hdr; +} diff --git a/code/machine/network.h b/code/machine/network.h new file mode 100644 index 0000000..7a3c9b7 --- /dev/null +++ b/code/machine/network.h @@ -0,0 +1,101 @@ +// network.h +// Data structures to emulate a physical network connection. +// The network provides the abstraction of ordered, unreliable, +// fixed-size packet delivery to other machines on the network. +// +// You may note that the interface to the network is similar to +// the console device -- both are full duplex channels. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef NETWORK_H +#define NETWORK_H + +#include "copyright.h" +#include "utility.h" + +// Network address -- uniquely identifies a machine. This machine's ID +// is given on the command line. +typedef int NetworkAddress; + +// The following class defines the network packet header. +// The packet header is prepended to the data payload by the Network driver, +// before the packet is sent over the wire. The format on the wire is: +// packet header (PacketHeader) +// data (containing MailHeader from the PostOffice!) + +class PacketHeader { + public: + NetworkAddress to; // Destination machine ID + NetworkAddress from; // source machine ID + unsigned length; // bytes of packet data, excluding the + // packet header (but including the + // MailHeader prepended by the post office) +}; + +#define MaxWireSize 64 // largest packet that can go out on the wire +#define MaxPacketSize (MaxWireSize - sizeof(struct PacketHeader)) + // data "payload" of the largest packet + + +// The following class defines a physical network device. The network +// is capable of delivering fixed sized packets, in order but unreliably, +// to other machines connected to the network. +// +// The "reliability" of the network can be specified to the constructor. +// This number, between 0 and 1, is the chance that the network will lose +// a packet. Note that you can change the seed for the random number +// generator, by changing the arguments to RandomInit() in Initialize(). +// The random number generator is used to choose which packets to drop. + +class Network { + public: + Network(NetworkAddress addr, double reliability, + VoidFunctionPtr readAvailHandler, VoidFunctionPtr writeDoneHandler, void *callArg); + // Allocate and initialize network driver + ~Network(); // De-allocate the network driver data + + void Send(PacketHeader hdr, const void* data); + // Send the packet data to a remote machine, + // specified by "hdr". Returns immediately. + // "writeHandler" is invoked once the next + // packet can be sent. Note that writeHandler + // is called whether or not the packet is + // dropped, and note that the "from" field of + // the PacketHeader is filled in automatically + // by Send(). + + PacketHeader Receive(void* data); + // Poll the network for incoming messages. + // If there is a packet waiting, copy the + // packet into "data" and return the header. + // If no packet is waiting, return a header + // with length 0. + + void SendDone(); // Interrupt handler, called when message is + // sent + void CheckPktAvail(); // Check if there is an incoming packet + + private: + NetworkAddress ident; // This machine's network address + double chanceToWork; // Likelihood packet will be dropped + int sock; // UNIX socket number for incoming packets + char sockName[32]; // File name corresponding to UNIX socket + VoidFunctionPtr writeHandler; // Interrupt handler, signalling next packet + // can be sent. + VoidFunctionPtr readHandler; // Interrupt handler, signalling packet has + // arrived. + void *handlerArg; // Argument to be passed to interrupt handler + // (pointer to post office) + bool sendBusy; // Packet is being sent. + bool packetAvail; // Packet has arrived, can be pulled off of + // network + PacketHeader inHdr; // Information about arrived packet + char inbox[MaxPacketSize]; // Data for arrived packet +}; + +#endif // NETWORK_H diff --git a/code/machine/stats.cc b/code/machine/stats.cc new file mode 100644 index 0000000..9a86e7c --- /dev/null +++ b/code/machine/stats.cc @@ -0,0 +1,49 @@ +// stats.h +// Routines for managing statistics about Nachos performance. +// +// DO NOT CHANGE -- these stats are maintained by the machine emulation. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "utility.h" +#include "stats.h" + +//---------------------------------------------------------------------- +// Statistics::Statistics +// Initialize performance metrics to zero, at system startup. +//---------------------------------------------------------------------- + +Statistics::Statistics() +{ + totalTicks = idleTicks = systemTicks = userTicks = 0; + numDiskReads = numDiskWrites = 0; + numConsoleCharsRead = numConsoleCharsWritten = 0; + numPageFaults = numPacketsSent = numPacketsRecvd = 0; +} + +//---------------------------------------------------------------------- +// Statistics::Print +// Print performance metrics, when we've finished everything +// at system shutdown. +//---------------------------------------------------------------------- + +void +Statistics::Print() +{ + // LB: format adapted to long long tick type + // printf("Ticks: total %d, idle %d, system %d, user %d\n", totalTicks, + // idleTicks, systemTicks, userTicks); + printf("Ticks: total %lld, idle %lld, system %lld, user %lld\n", + totalTicks, idleTicks, systemTicks, userTicks); + // End of correction + + printf("Disk I/O: reads %d, writes %d\n", numDiskReads, numDiskWrites); + printf("Console I/O: reads %d, writes %d\n", numConsoleCharsRead, + numConsoleCharsWritten); + printf("Paging: faults %d\n", numPageFaults); + printf("Network I/O: packets received %d, sent %d\n", numPacketsRecvd, + numPacketsSent); +} diff --git a/code/machine/stats.h b/code/machine/stats.h new file mode 100644 index 0000000..3b11634 --- /dev/null +++ b/code/machine/stats.h @@ -0,0 +1,70 @@ +// stats.h +// Data structures for gathering statistics about Nachos performance. +// +// DO NOT CHANGE -- these stats are maintained by the machine emulation +// +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef STATS_H +#define STATS_H + +#include "copyright.h" + +// The following class defines the statistics that are to be kept +// about Nachos behavior -- how much time (ticks) elapsed, how +// many user instructions executed, etc. +// +// The fields in this class are public to make it easier to update. + +class Statistics { + public: + // LB: type of ticks promoted from 32 bit int to 64 bit long long + // to cope with long runs + // int totalTicks; // Total time running Nachos + // int idleTicks; // Time spent idle (no threads to run) + // int systemTicks; // Time spent executing system code + // int userTicks; // Time spent executing user code + // (this is also equal to # of + // user instructions executed) + long long totalTicks; // Total time running Nachos + long long idleTicks; // Time spent idle (no threads to run) + long long systemTicks; // Time spent executing system code + long long userTicks; // Time spent executing user code + // (this is also equal to # of + // user instructions executed) + // End of correction + + + int numDiskReads; // number of disk read requests + int numDiskWrites; // number of disk write requests + int numConsoleCharsRead; // number of characters read from the keyboard + int numConsoleCharsWritten; // number of characters written to the display + int numPageFaults; // number of virtual memory page faults + int numPacketsSent; // number of packets sent over the network + int numPacketsRecvd; // number of packets received over the network + + Statistics(); // initialize everything to zero + + void Print(); // print collected statistics +}; + +// Constants used to reflect the relative time an operation would +// take in a real system. A "tick" is a just a unit of time -- if you +// like, a microsecond. +// +// Since Nachos kernel code is directly executed, and the time spent +// in the kernel measured by the number of calls to enable interrupts, +// these time constants are none too exact. + +#define UserTick 1 // advance for each user-level instruction +#define SystemTick 10 // advance each time interrupts are enabled +#define RotationTime 500 // time disk takes to rotate one sector +#define SeekTime 500 // time disk takes to seek past one track +#define ConsoleTime 100 // time to read or write one character +#define NetworkTime 100 // time to send or receive one packet +#define TimerTicks 100 // (average) time between timer interrupts + +#endif // STATS_H diff --git a/code/machine/sysdep.cc b/code/machine/sysdep.cc new file mode 100644 index 0000000..07680ba --- /dev/null +++ b/code/machine/sysdep.cc @@ -0,0 +1,537 @@ +// sysdep.cc +// Implementation of system-dependent interface. Nachos uses the +// routines defined here, rather than directly calling the UNIX library, +// to simplify porting between versions of UNIX, and even to +// other systems, such as MSDOS. +// +// On UNIX, almost all of these routines are simple wrappers +// for the underlying UNIX system calls. +// +// NOTE: all of these routines refer to operations on the underlying +// host machine (e.g., the DECstation, SPARC, etc.), supporting the +// Nachos simulation code. Nachos implements similar operations, +// (such as opening a file), but those are implemented in terms +// of hardware devices, which are simulated by calls to the underlying +// routines in the host workstation OS. +// +// This file includes lots of calls to C routines. C++ requires +// us to wrap all C definitions with a "extern "C" block". +// This prevents the internal forms of the names from being +// changed by the C++ compiler. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// UNIX routines called by procedures in this file + +#ifdef HOST_SNAKE +// int creat(char *name, unsigned short mode); +// int open(const char *name, int flags, ...); +#else +#if !defined(SOLARIS) && !defined(LINUX) && !defined(MAC_OS) +int creat(const char *name, unsigned short mode); +int open(const char *name, int flags, ...); +// void signal(int sig, VoidFunctionPtr func); -- this may work now! +#ifdef HOST_i386 +int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, + struct timeval *timeout); +#else +int select(int numBits, void *readFds, void *writeFds, void *exceptFds, + struct timeval *timeout); +#endif +#endif +#endif + +#if !defined(SOLARIS) && !defined(LINUX) && !defined(MAC_OS) +int unlink(char *name); +int read(int filedes, char *buf, int numBytes); +int write(int filedes, char *buf, int numBytes); +int lseek(int filedes, int offset, int whence); +int tell(int filedes); +int close(int filedes); +int unlink(char *name); + +// definition varies slightly from platform to platform, so don't +// define unless gcc complains +// extern int recvfrom(int s, void *buf, int len, int flags, void *from, int *fromlen); +// extern int sendto(int s, void *msg, int len, int flags, void *to, int tolen); + + +void srand(unsigned seed); +int rand(void); +unsigned sleep(unsigned); +void abort(); +void exit(); +int mprotect(char *addr, int len, int prot); + +int socket(int, int, int); +int bind (int, const void*, int); +int recvfrom (int, void*, int, int, void*, int *); +int sendto (int, const void*, int, int, void*, int); +#endif +} + +#include "interrupt.h" +#include "system.h" + +//---------------------------------------------------------------------- +// PollFile +// Check open file or open socket to see if there are any +// characters that can be read immediately. If so, read them +// in, and return TRUE. +// +// In the network case, if there are no threads for us to run, +// and no characters to be read, +// we need to give the other side a chance to get our host's CPU +// (otherwise, we'll go really slowly, since UNIX time-slices +// infrequently, and this would be like busy-waiting). So we +// delay for a short fixed time, before allowing ourselves to be +// re-scheduled (sort of like a Yield, but cast in terms of UNIX). +// +// "fd" -- the file descriptor of the file to be polled +//---------------------------------------------------------------------- + +bool +PollFile(int fd) +{ +#if defined(SOLARIS) || defined(LINUX) || defined(MAC_OS) + fd_set rfd; + int retVal; +#else + int rfd = (1 << fd), wfd = 0, xfd = 0, retVal; +#endif + struct timeval pollTime; + +// decide how long to wait if there are no characters on the file + pollTime.tv_sec = 0; + if (interrupt->getStatus() == IdleMode) + pollTime.tv_usec = 20000; // delay to let other nachos run + else + pollTime.tv_usec = 0; // no delay + +// poll file or socket +#if defined(SOLARIS) || defined(LINUX) || defined(MAC_OS) + FD_ZERO(&rfd); + FD_SET(fd, &rfd); + retVal = select(fd + 1, &rfd, NULL, NULL, &pollTime); +#else + retVal = select(32, &rfd, &wfd, &xfd, &pollTime); +#endif + + ASSERT((retVal == 0) || (retVal == 1)); + if (retVal == 0) + return FALSE; // no char waiting to be read + return TRUE; +} + +//---------------------------------------------------------------------- +// OpenForWrite +// Open a file for writing. Create it if it doesn't exist; truncate it +// if it does already exist. Return the file descriptor. +// +// "name" -- file name +//---------------------------------------------------------------------- + +int +OpenForWrite(const char *name) +{ + int fd = open(name, O_RDWR|O_CREAT|O_TRUNC, 0666); + + ASSERT(fd >= 0); + return fd; +} + +//---------------------------------------------------------------------- +// OpenForReadWrite +// Open a file for reading or writing. +// Return the file descriptor, or error if it doesn't exist. +// +// "name" -- file name +//---------------------------------------------------------------------- + +int +OpenForReadWrite(const char *name, bool crashOnError) +{ + int fd = open(name, O_RDWR, 0); + + ASSERT(!crashOnError || fd >= 0); + return fd; +} + +//---------------------------------------------------------------------- +// Read +// Read characters from an open file. Abort if read fails. +//---------------------------------------------------------------------- + +void +Read(int fd, void *buffer, int nBytes) +{ + int retVal = read(fd, buffer, nBytes); + ASSERT(retVal == nBytes); +} + +//---------------------------------------------------------------------- +// ReadPartial +// Read characters from an open file, returning as many as are +// available. +//---------------------------------------------------------------------- + +int +ReadPartial(int fd, void *buffer, int nBytes) +{ + return read(fd, buffer, nBytes); +} + + +//---------------------------------------------------------------------- +// WriteFile +// Write characters to an open file. Abort if write fails. +//---------------------------------------------------------------------- + +void +WriteFile(int fd, const void *buffer, int nBytes) +{ + int retVal = write(fd, buffer, nBytes); + ASSERT(retVal == nBytes); +} + +//---------------------------------------------------------------------- +// Lseek +// Change the location within an open file. Abort on error. +//---------------------------------------------------------------------- + +void +Lseek(int fd, int offset, int whence) +{ + int retVal = lseek(fd, offset, whence); + ASSERT(retVal >= 0); +} + +//---------------------------------------------------------------------- +// Tell +// Report the current location within an open file. +//---------------------------------------------------------------------- + +int +Tell(int fd) +{ +#if defined(SOLARIS) || defined(LINUX) || defined(MAC_OS) + return lseek(fd,0,SEEK_CUR); // 386BSD doesn't have the tell() system call +#else + return tell(fd); +#endif +} + + +//---------------------------------------------------------------------- +// Close +// Close a file. Abort on error. +//---------------------------------------------------------------------- + +void +Close(int fd) +{ + int retVal = close(fd); + ASSERT(retVal >= 0); +} + +//---------------------------------------------------------------------- +// Unlink +// Delete a file. +//---------------------------------------------------------------------- + +bool +Unlink(const char *name) +{ + return unlink(name); +} + +//---------------------------------------------------------------------- +// OpenSocket +// Open an interprocess communication (IPC) connection. For now, +// just open a datagram port where other Nachos (simulating +// workstations on a network) can send messages to this Nachos. +//---------------------------------------------------------------------- + +int +OpenSocket() +{ + int sockID; + + sockID = socket(AF_UNIX, SOCK_DGRAM, 0); + ASSERT(sockID >= 0); + + return sockID; +} + +//---------------------------------------------------------------------- +// CloseSocket +// Close the IPC connection. +//---------------------------------------------------------------------- + +void +CloseSocket(int sockID) +{ + (void) close(sockID); +} + +//---------------------------------------------------------------------- +// InitSocketName +// Initialize a UNIX socket address -- magical! +//---------------------------------------------------------------------- + +static void +InitSocketName(struct sockaddr_un *uname, const char *name) +{ + uname->sun_family = AF_UNIX; + strcpy(uname->sun_path, name); +} + +//---------------------------------------------------------------------- +// AssignNameToSocket +// Give a UNIX file name to the IPC port, so other instances of Nachos +// can locate the port. +//---------------------------------------------------------------------- + +void +AssignNameToSocket(const char *socketName, int sockID) +{ + struct sockaddr_un uName; + int retVal; + + (void) unlink(socketName); // in case it's still around from last time + + InitSocketName(&uName, socketName); + retVal = bind(sockID, (struct sockaddr *) &uName, sizeof(uName)); + ASSERT(retVal >= 0); + DEBUG('n', "Created socket %s\n", socketName); +} + +//---------------------------------------------------------------------- +// DeAssignNameToSocket +// Delete the UNIX file name we assigned to our IPC port, on cleanup. +//---------------------------------------------------------------------- +void +DeAssignNameToSocket(const char *socketName) +{ + (void) unlink(socketName); +} + +//---------------------------------------------------------------------- +// PollSocket +// Return TRUE if there are any messages waiting to arrive on the +// IPC port. +//---------------------------------------------------------------------- +bool +PollSocket(int sockID) +{ + return PollFile(sockID); // on UNIX, socket ID's are just file ID's +} + +//---------------------------------------------------------------------- +// ReadFromSocket +// Read a fixed size packet off the IPC port. Abort on error. +//---------------------------------------------------------------------- +void +ReadFromSocket(int sockID, void *buffer, int packetSize) +{ + int retVal; + struct sockaddr_un uName; + + // LB: Signedness problem on Solaris 5.6/SPARC, as the last + // parameter of recvfrom is specified as a int *. In the later + // versions, it is specified as a void *. Casting size to int instead + // of unsigned seems to fix the problem, but it is admittingly + // rather ad-hoc... +#ifndef SOLARIS + unsigned int size = sizeof(uName); +#else + int size = (int) sizeof(uName); +#endif + // End of correction. + + retVal = recvfrom(sockID, buffer, packetSize, 0, + (struct sockaddr *) &uName, &size); + + if (retVal != packetSize) { + perror("in recvfrom"); + } + ASSERT(retVal == packetSize); +} + +//---------------------------------------------------------------------- +// SendToSocket +// Transmit a fixed size packet to another Nachos' IPC port. +// Abort on error. +//---------------------------------------------------------------------- +void +SendToSocket(int sockID, const void *buffer, int packetSize, const char *toName) +{ + struct sockaddr_un uName; + int retVal; + + InitSocketName(&uName, toName); + retVal = sendto(sockID, buffer, packetSize, 0, + (sockaddr *) &uName, sizeof(uName)); + ASSERT(retVal == packetSize); +} + + +//---------------------------------------------------------------------- +// CallOnUserAbort +// Arrange that "func" will be called when the user aborts (e.g., by +// hitting ctl-C. +//---------------------------------------------------------------------- + +void +CallOnUserAbort(VoidNoArgFunctionPtr func) +{ + (void)signal(SIGINT, (void (*)(int)) func); +} + +//---------------------------------------------------------------------- +// BlockUserAbort +// Prevent from abortion (e.g. ctl-C) +//---------------------------------------------------------------------- + +void +BlockUserAbort(void) +{ + sighold(SIGINT); +} + + +//---------------------------------------------------------------------- +// UnBlockUserAbort +// Re-allow abortion (e.g. ctl-C) +//---------------------------------------------------------------------- + +void +UnBlockUserAbort(void) +{ + sigrelse(SIGINT); +} + + +//---------------------------------------------------------------------- +// Sleep +// Put the UNIX process running Nachos to sleep for x seconds, +// to give the user time to start up another invocation of Nachos +// in a different UNIX shell. +//---------------------------------------------------------------------- + +void +Delay(int seconds) +{ + (void) sleep((unsigned) seconds); +} + +//---------------------------------------------------------------------- +// Abort +// Quit and drop core. +//---------------------------------------------------------------------- + +void +Abort() +{ +#ifdef USER_PROGRAM + if (machine) + machine->DumpMem("abort.svg"); +#endif + abort(); +} + +//---------------------------------------------------------------------- +// Exit +// Quit without dropping core. +//---------------------------------------------------------------------- + +void +Exit(int exitCode) +{ + exit(exitCode); +} + +//---------------------------------------------------------------------- +// RandomInit +// Initialize the pseudo-random number generator. We use the +// now obsolete "srand" and "rand" because they are more portable! +//---------------------------------------------------------------------- + +void +RandomInit(unsigned seed) +{ + srand(seed); +} + +//---------------------------------------------------------------------- +// Random +// Return a pseudo-random number. +//---------------------------------------------------------------------- + +int +Random() +{ + return rand(); +} + +//---------------------------------------------------------------------- +// AllocBoundedArray +// Return an array, with the two pages just before +// and after the array unmapped, to catch illegal references off +// the end of the array. Particularly useful for catching overflow +// beyond fixed-size thread execution stacks. +// +// Note: Just return the useful part! +// +// "size" -- amount of useful space needed (in bytes) +//---------------------------------------------------------------------- + +char * +AllocBoundedArray(int size) +{ + int pgSize = getpagesize(); + char *ptr = new char[pgSize * 2 + size]; + + mprotect(ptr, pgSize, 0); + mprotect(ptr + pgSize + size, pgSize, 0); + return ptr + pgSize; +} + +//---------------------------------------------------------------------- +// DeallocBoundedArray +// Deallocate an array of integers, unprotecting its two boundary pages. +// +// "ptr" -- the array to be deallocated +// "size" -- amount of useful space in the array (in bytes) +//---------------------------------------------------------------------- + +void +DeallocBoundedArray(char *ptr, int size) +{ + int pgSize = getpagesize(); + + mprotect(ptr - pgSize, pgSize, PROT_READ | PROT_WRITE | PROT_EXEC); + mprotect(ptr + size, pgSize, PROT_READ | PROT_WRITE | PROT_EXEC); + delete [] (ptr - pgSize); +} diff --git a/code/machine/sysdep.h b/code/machine/sysdep.h new file mode 100644 index 0000000..8e7ad17 --- /dev/null +++ b/code/machine/sysdep.h @@ -0,0 +1,68 @@ +// sysdep.h +// System-dependent interface. Nachos uses the routines defined +// here, rather than directly calling the UNIX library functions, to +// simplify porting between versions of UNIX, and even to +// other systems, such as MSDOS and the Macintosh. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef SYSDEP_H +#define SYSDEP_H + +#include "copyright.h" + +// Check file to see if there are any characters to be read. +// If no characters in the file, return without waiting. +extern bool PollFile(int fd); + +// File operations: open/read/write/lseek/close, and check for error +// For simulating the disk and the console devices. +extern int OpenForWrite(const char *name); +extern int OpenForReadWrite(const char *name, bool crashOnError); +extern void Read(int fd, void *buffer, int nBytes); +extern int ReadPartial(int fd, void *buffer, int nBytes); +extern void WriteFile(int fd, const void *buffer, int nBytes); +extern void Lseek(int fd, int offset, int whence); +extern int Tell(int fd); +extern void Close(int fd); +extern bool Unlink(const char *name); + +// Interprocess communication operations, for simulating the network +extern int OpenSocket(); +extern void CloseSocket(int sockID); +extern void AssignNameToSocket(const char *socketName, int sockID); +extern void DeAssignNameToSocket(const char *socketName); +extern bool PollSocket(int sockID); +extern void ReadFromSocket(int sockID, void *buffer, int packetSize); +extern void SendToSocket(int sockID, const void *buffer, int packetSize,const char *toName); + +// Process control: abort, exit, and sleep +extern void Abort(); +extern void Exit(int exitCode); +extern void Delay(int seconds); + +// Initialize system so that cleanUp routine is called when user hits ctl-C +extern void CallOnUserAbort(VoidNoArgFunctionPtr cleanUp); +extern void BlockUserAbort(void); +extern void UnBlockUserAbort(void); + +// Initialize the pseudo random number generator +extern void RandomInit(unsigned seed); +extern int Random(); + +// Allocate, de-allocate an array, such that de-referencing +// just beyond either end of the array will cause an error +extern char *AllocBoundedArray(int size); +extern void DeallocBoundedArray(char *p, int size); + +// Other C library routines that are used by Nachos. +// These are assumed to be portable, so we don't include a wrapper. +extern "C" { +#include // for atoi, atof, abs +#include // for printf, fprintf +#include // for DEBUG, etc. +} + +#endif // SYSDEP_H diff --git a/code/machine/timer.cc b/code/machine/timer.cc new file mode 100644 index 0000000..b5cf6e7 --- /dev/null +++ b/code/machine/timer.cc @@ -0,0 +1,85 @@ +// timer.cc +// Routines to emulate a hardware timer device. +// +// A hardware timer generates a CPU interrupt every X milliseconds. +// This means it can be used for implementing time-slicing. +// +// We emulate a hardware timer by scheduling an interrupt to occur +// every time stats->totalTicks has increased by TimerTicks. +// +// In order to introduce some randomness into time-slicing, if "doRandom" +// is set, then the interrupt is comes after a random number of ticks. +// +// Remember -- nothing in here is part of Nachos. It is just +// an emulation for the hardware that Nachos is running on top of. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "timer.h" +#include "system.h" + +// dummy function because C++ does not allow pointers to member functions +static void TimerHandler(void *arg) +{ Timer *p = (Timer *)arg; p->TimerExpired(); } + +//---------------------------------------------------------------------- +// Timer::Timer +// Initialize a hardware timer device. Save the place to call +// on each interrupt, and then arrange for the timer to start +// generating interrupts. +// +// "timerHandler" is the interrupt handler for the timer device. +// It is called with interrupts disabled every time the +// the timer expires. +// "callArg" is the parameter to be passed to the interrupt handler. +// "doRandom" -- if true, arrange for the interrupts to occur +// at random, instead of fixed, intervals. +//---------------------------------------------------------------------- + +Timer::Timer(VoidFunctionPtr timerHandler, void *callArg, bool doRandom) +{ + randomize = doRandom; + handler = timerHandler; + arg = callArg; + + // schedule the first interrupt from the timer device + interrupt->Schedule(TimerHandler, this, TimeOfNextInterrupt(), + TimerInt); +} + +//---------------------------------------------------------------------- +// Timer::TimerExpired +// Routine to simulate the interrupt generated by the hardware +// timer device. Schedule the next interrupt, and invoke the +// interrupt handler. +//---------------------------------------------------------------------- +void +Timer::TimerExpired() +{ + // schedule the next timer device interrupt + interrupt->Schedule(TimerHandler, this, TimeOfNextInterrupt(), + TimerInt); + + // invoke the Nachos interrupt handler for this device + (*handler)(arg); +} + +//---------------------------------------------------------------------- +// Timer::TimeOfNextInterrupt +// Return when the hardware timer device will next cause an interrupt. +// If randomize is turned on, make it a (pseudo-)random delay. +//---------------------------------------------------------------------- + +int +Timer::TimeOfNextInterrupt() +{ + if (randomize) + return 1 + (Random() % (TimerTicks * 2)); + else + return TimerTicks; +} diff --git a/code/machine/timer.h b/code/machine/timer.h new file mode 100644 index 0000000..4e82718 --- /dev/null +++ b/code/machine/timer.h @@ -0,0 +1,49 @@ +// timer.h +// Data structures to emulate a hardware timer. +// +// A hardware timer generates a CPU interrupt every X milliseconds. +// This means it can be used for implementing time-slicing, or for +// having a thread go to sleep for a specific period of time. +// +// We emulate a hardware timer by scheduling an interrupt to occur +// every time stats->totalTicks has increased by TimerTicks. +// +// In order to introduce some randomness into time-slicing, if "doRandom" +// is set, then the interrupt comes after a random number of ticks. +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef TIMER_H +#define TIMER_H + +#include "copyright.h" +#include "utility.h" + +// The following class defines a hardware timer. +class Timer { + public: + Timer(VoidFunctionPtr timerHandler, void *callArg, bool doRandom); + // Initialize the timer, to call the interrupt + // handler "timerHandler" every time slice. + ~Timer() {} + +// Internal routines to the timer emulation -- DO NOT call these + + void TimerExpired(); // called internally when the hardware + // timer generates an interrupt + + int TimeOfNextInterrupt(); // figure out when the timer will generate + // its next interrupt + + private: + bool randomize; // set if we need to use a random timeout delay + VoidFunctionPtr handler; // timer interrupt handler + void *arg; // argument to pass to interrupt handler + +}; + +#endif // TIMER_H diff --git a/code/machine/translate.cc b/code/machine/translate.cc new file mode 100644 index 0000000..f8e5c72 --- /dev/null +++ b/code/machine/translate.cc @@ -0,0 +1,267 @@ +// translate.cc +// Routines to translate virtual addresses to physical addresses. +// Software sets up a table of legal translations. We look up +// in the table on every memory reference to find the true physical +// memory location. +// +// Two types of translation are supported here. +// +// Linear page table -- the virtual page # is used as an index +// into the table, to find the physical page #. +// +// Translation lookaside buffer -- associative lookup in the table +// to find an entry with the same virtual page #. If found, +// this entry is used for the translation. +// If not, it traps to software with an exception. +// +// In practice, the TLB is much smaller than the amount of physical +// memory (16 entries is common on a machine that has 1000's of +// pages). Thus, there must also be a backup translation scheme +// (such as page tables), but the hardware doesn't need to know +// anything at all about that. +// +// Note that the contents of the TLB are specific to an address space. +// If the address space changes, so does the contents of the TLB! +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "machine.h" +#include "addrspace.h" +#include "system.h" + +// Routines for converting Words and Short Words to and from the +// simulated machine's format of little endian. These end up +// being NOPs when the host machine is also little endian (DEC and Intel). + +unsigned int +WordToHost(unsigned int word) { +#ifdef HOST_IS_BIG_ENDIAN + register unsigned long result; + result = (word >> 24) & 0x000000ff; + result |= (word >> 8) & 0x0000ff00; + result |= (word << 8) & 0x00ff0000; + result |= (word << 24) & 0xff000000; + return result; +#else + return word; +#endif /* HOST_IS_BIG_ENDIAN */ +} + +unsigned short +ShortToHost(unsigned short shortword) { +#ifdef HOST_IS_BIG_ENDIAN + register unsigned short result; + result = (shortword << 8) & 0xff00; + result |= (shortword >> 8) & 0x00ff; + return result; +#else + return shortword; +#endif /* HOST_IS_BIG_ENDIAN */ +} + +unsigned int +WordToMachine(unsigned int word) { return WordToHost(word); } + +unsigned short +ShortToMachine(unsigned short shortword) { return ShortToHost(shortword); } + + +//---------------------------------------------------------------------- +// Machine::ReadMem +// Read "size" (1, 2, or 4) bytes of virtual memory at "addr" into +// the location pointed to by "value". +// +// Returns FALSE if the translation step from virtual to physical memory +// failed. +// +// "addr" -- the virtual address to read from +// "size" -- the number of bytes to read (1, 2, or 4) +// "value" -- the place to write the result +//---------------------------------------------------------------------- + +bool +Machine::ReadMem(int addr, int size, int *value, bool debug) +{ + int data; + ExceptionType exception; + int physicalAddress; + + if (debug) + DEBUG('a', "Reading VA 0x%x, size %d\n", addr, size); + + exception = Translate(addr, &physicalAddress, size, FALSE, debug); + if (exception != NoException) { + machine->RaiseException(exception, addr); + return FALSE; + } + switch (size) { + case 1: + data = machine->mainMemory[physicalAddress]; + *value = data; + break; + + case 2: + data = *(unsigned short *) &machine->mainMemory[physicalAddress]; + *value = ShortToHost(data); + break; + + case 4: + data = *(unsigned int *) &machine->mainMemory[physicalAddress]; + *value = WordToHost(data); + break; + + default: ASSERT(FALSE); + } + + if (debug) + DEBUG('a', "\tvalue read = %8.8x\n", *value); + return (TRUE); +} + +bool +Machine::ReadMem(int addr, int size, int *value) +{ + return ReadMem(addr, size, value, TRUE); +} + +//---------------------------------------------------------------------- +// Machine::WriteMem +// Write "size" (1, 2, or 4) bytes of the contents of "value" into +// virtual memory at location "addr". +// +// Returns FALSE if the translation step from virtual to physical memory +// failed. +// +// "addr" -- the virtual address to write to +// "size" -- the number of bytes to be written (1, 2, or 4) +// "value" -- the data to be written +//---------------------------------------------------------------------- + +bool +Machine::WriteMem(int addr, int size, int value) +{ + ExceptionType exception; + int physicalAddress; + + DEBUG('a', "Writing VA 0x%x, size %d, value 0x%x\n", addr, size, value); + + exception = Translate(addr, &physicalAddress, size, TRUE, TRUE); + if (exception != NoException) { + machine->RaiseException(exception, addr); + return FALSE; + } + switch (size) { + case 1: + machine->mainMemory[physicalAddress] = (unsigned char) (value & 0xff); + break; + + case 2: + *(unsigned short *) &machine->mainMemory[physicalAddress] + = ShortToMachine((unsigned short) (value & 0xffff)); + break; + + case 4: + *(unsigned int *) &machine->mainMemory[physicalAddress] + = WordToMachine((unsigned int) value); + break; + + default: ASSERT(FALSE); + } + + return TRUE; +} + +//---------------------------------------------------------------------- +// Machine::Translate +// Translate a virtual address into a physical address, using +// either a page table or a TLB. Check for alignment and all sorts +// of other errors, and if everything is ok, set the use/dirty bits in +// the translation table entry, and store the translated physical +// address in "physAddr". If there was an error, returns the type +// of the exception. +// +// "virtAddr" -- the virtual address to translate +// "physAddr" -- the place to store the physical address +// "size" -- the amount of memory being read or written +// "writing" -- if TRUE, check the "read-only" bit in the TLB +//---------------------------------------------------------------------- + +ExceptionType +Machine::Translate(int virtAddr, int* physAddr, int size, bool writing, bool debug) +{ + int i; + unsigned int vpn, offset; + TranslationEntry *entry; + unsigned int pageFrame; + + if (debug) DEBUG('a', "\tTranslate 0x%x, %s: ", virtAddr, writing ? "write" : "read"); + +// check for alignment errors + if (((size == 4) && (virtAddr & 0x3)) || ((size == 2) && (virtAddr & 0x1))){ + if (debug) DEBUG('a', "alignment problem at %d, size %d!\n", virtAddr, size); + return AddressErrorException; + } + + // we must have either a TLB or a page table, but not both! + ASSERT(tlb == NULL || currentPageTable == NULL); + ASSERT(tlb != NULL || currentPageTable != NULL); + +// calculate the virtual page number, and offset within the page, +// from the virtual address + vpn = (unsigned) virtAddr / PageSize; + offset = (unsigned) virtAddr % PageSize; + + if (tlb == NULL) { // => page table => vpn is index into table + if (vpn >= currentPageTableSize) { + if (debug) DEBUG('a', "virtual page # %d too large for page table size %d!\n", + virtAddr, currentPageTableSize); + return AddressErrorException; + } else if (!currentPageTable[vpn].valid) { + if (debug) DEBUG('a', "virtual page # %d : page %d is invalid !\n", + virtAddr, vpn); + return PageFaultException; + } + entry = ¤tPageTable[vpn]; + } else { + for (entry = NULL, i = 0; i < TLBSize; i++) + if (tlb[i].valid && (tlb[i].virtualPage == vpn)) { + entry = &tlb[i]; // FOUND! + break; + } + if (entry == NULL) { // not found + if (debug) DEBUG('a', "*** no valid TLB entry found for this virtual page!\n"); + return PageFaultException; // really, this is a TLB fault, + // the page may be in memory, + // but not in the TLB + } + } + + if (entry->readOnly && writing) { // trying to write to a read-only page + if (tlb == NULL) { + if (debug) DEBUG('a', "%d mapped read-only in page table!\n", virtAddr); + } else { + if (debug) DEBUG('a', "%d mapped read-only at %d in TLB!\n", virtAddr, i); + } + return ReadOnlyException; + } + pageFrame = entry->physicalPage; + + // if the pageFrame is too big, there is something really wrong! + // An invalid translation was loaded into the page table or TLB. + if (pageFrame >= NumPhysPages) { + if (debug) DEBUG('a', "*** frame %d > %d!\n", pageFrame, NumPhysPages); + return BusErrorException; + } + entry->use = TRUE; // set the use, dirty bits + if (writing) + entry->dirty = TRUE; + *physAddr = pageFrame * PageSize + offset; + ASSERT((*physAddr >= 0) && ((*physAddr + size) <= MemorySize)); + if (debug) DEBUG('a', "phys addr = 0x%x\n", *physAddr); + return NoException; +} diff --git a/code/machine/translate.h b/code/machine/translate.h new file mode 100644 index 0000000..6d6751b --- /dev/null +++ b/code/machine/translate.h @@ -0,0 +1,46 @@ +// translate.h +// Data structures for managing the translation from +// virtual page # -> physical page #, used for managing +// physical memory on behalf of user programs. +// +// The data structures in this file are "dual-use" - they +// serve both as a page table entry, and as an entry in +// a software-managed translation lookaside buffer (TLB). +// Either way, each entry is of the form: +// . +// +// DO NOT CHANGE -- part of the machine emulation +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef TLB_H +#define TLB_H + +#include "copyright.h" +#include "utility.h" + +// The following class defines an entry in a translation table -- either +// in a page table or a TLB. Each entry defines a mapping from one +// virtual page to one physical page. +// In addition, there are some extra bits for access control (valid and +// read-only) and some bits for usage information (use and dirty). + +class TranslationEntry { + public: + unsigned int virtualPage; // The page number in virtual memory, only when + // using a TLB + unsigned int physicalPage; // The page number in real memory (relative to the + // start of "mainMemory" + bool valid; // If this bit is cleared, the translation is ignored. + // (In other words, the entry hasn't been initialized.) + bool readOnly; // If this bit is set, the user program is not allowed + // to modify the contents of the page. + bool use; // This bit is set by the hardware every time the + // page is referenced or modified. + bool dirty; // This bit is set by the hardware every time the + // page is modified. +}; + +#endif diff --git a/code/machine/valgrind.h b/code/machine/valgrind.h new file mode 100644 index 0000000..cbfc12e --- /dev/null +++ b/code/machine/valgrind.h @@ -0,0 +1,23 @@ +// valgrind.h +// Valgrind hooks to announce stack allocation/deallocation +// +// Copyright (c) 2009 Samuel Thibault +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef VALGRIND_H +#define VALGRIND_H + +#ifdef HAVE_VALGRIND +#include +#endif + +#ifndef VALGRIND_STACK_REGISTER +#define VALGRIND_STACK_REGISTER(start, end) 0 +#endif + +#ifndef VALGRIND_STACK_DEREGISTER +#define VALGRIND_STACK_DEREGISTER(id) ((void)0) +#endif + +#endif // VALGRIND_H diff --git a/code/network/Makefile b/code/network/Makefile new file mode 100644 index 0000000..7b793d4 --- /dev/null +++ b/code/network/Makefile @@ -0,0 +1,23 @@ +# NOTE: this is a GNU Makefile. You must use "gmake" rather than "make". +# +# Makefile for the network assignment +# Defines set up assuming this assignment is done last +# If not, use the "bare bones" defines below. +# +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +DEFINES = -DUSER_PROGRAM -DVM -DFILESYS_NEEDED -DFILESYS -DNETWORK +INCPATH = -I../network -I../bin -I../filesys -I../vm -I../userprog -I../threads -I../machine +C_OFILES = $(THREAD_O) $(USERPROG_O) $(VM_O) $(FILESYS_O) $(NETWORK_O) + +# bare bones version +# DEFINES =-DTHREADS -DNETWORK +# INCPATH = -I../network -I../threads -I../machine +# C_OFILES = $(THREAD_O) $(NETWORK_O) + +include ../Makefile.common +include ../Makefile.dep + + diff --git a/code/network/README b/code/network/README new file mode 100644 index 0000000..980a1c6 --- /dev/null +++ b/code/network/README @@ -0,0 +1,59 @@ +From: cs162@po.EECS.Berkeley.EDU (Class Master) +Subject: Answer to problems running assignment 5 + +Some people have been having problems running assignment 5. +Unfortunately, the way the network gets initialized, if things +don't get done in exactly the right order, you can get an error +in sending the first packet to the socket. + +I have modified synch.h and synch.cc in code/, and re-compiled +the contents of code/network. I was then able to run the +test case successfully, at least one out of two times. See below. +So, be careful out there! + +tom +----------- +po.EECS.Berkeley.EDU:network>!19 +./nachos -m 0 -o 1 > & log & ; ./nachos -m 1 -o 0 > & log2 & +[1] 10882 +[2] 10883 +po.EECS.Berkeley.EDU:network> +[2] Illegal instruction ./nachos -m 1 -o 0 >& log2 (core dumped) + +[1] Illegal instruction ./nachos -m 0 -o 1 >& log (core dumped) + +po.EECS.Berkeley.EDU:network>rm -f log* SOCKET* +po.EECS.Berkeley.EDU:network>!19 +./nachos -m 0 -o 1 > & log & ; ./nachos -m 1 -o 0 > & log2 & +[1] 10895 +[2] 10896 +po.EECS.Berkeley.EDU:network> +[1] Done ./nachos -m 0 -o 1 >& log + +[2] Done ./nachos -m 1 -o 0 >& log2 +more log +Got "Hello there!" from 1, box 1 +Got "Got it!" from 1, box 1 +Machine halting! + +Ticks: total 57080, idle 56810, system 270, user 0 +Disk I/O: reads 2, writes 0 +Console I/O: reads 0, writes 0 +Paging: faults 0 +Network I/O: packets received 2, sent 2 + + + +Cleaning up... +po.EECS.Berkeley.EDU:network>more log2 +Got "Hello there!" from 0, box 1 +Got "Got it!" from 0, box 1 +Machine halting! + +Ticks: total 58530, idle 58260, system 270, user 0 +Disk I/O: reads 2, writes 0 +Console I/O: reads 0, writes 0 +Paging: faults 0 +Network I/O: packets received 2, sent 2 + +Cleaning up... diff --git a/code/network/nettest.cc b/code/network/nettest.cc new file mode 100644 index 0000000..a2b3181 --- /dev/null +++ b/code/network/nettest.cc @@ -0,0 +1,72 @@ +// nettest.cc +// Test out message delivery between two "Nachos" machines, +// using the Post Office to coordinate delivery. +// +// Two caveats: +// 1. Two copies of Nachos must be running, with machine ID's 0 and 1: +// ./nachos -m 0 -o 1 & +// ./nachos -m 1 -o 0 & +// +// 2. You need an implementation of condition variables, +// which is *not* provided as part of the baseline threads +// implementation. The Post Office won't work without +// a correct implementation of condition variables. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#include "system.h" +#include "network.h" +#include "post.h" +#include "interrupt.h" + +// Test out message delivery, by doing the following: +// 1. send a message to the machine with ID "farAddr", at mail box #0 +// 2. wait for the other machine's message to arrive (in our mailbox #0) +// 3. send an acknowledgment for the other machine's message +// 4. wait for an acknowledgement from the other machine to our +// original message + +void +MailTest(int farAddr) +{ + PacketHeader outPktHdr, inPktHdr; + MailHeader outMailHdr, inMailHdr; + const char *data = "Hello there!"; + const char *ack = "Got it!"; + char buffer[MaxMailSize]; + + // construct packet, mail header for original message + // To: destination machine, mailbox 0 + // From: our machine, reply to: mailbox 1 + outPktHdr.to = farAddr; + outMailHdr.to = 0; + outMailHdr.from = 1; + outMailHdr.length = strlen(data) + 1; + + // Send the first message + postOffice->Send(outPktHdr, outMailHdr, data); + + // Wait for the first message from the other machine + postOffice->Receive(0, &inPktHdr, &inMailHdr, buffer); + printf("Got \"%s\" from %d, box %d\n",buffer,inPktHdr.from,inMailHdr.from); + fflush(stdout); + + // Send acknowledgement to the other machine (using "reply to" mailbox + // in the message that just arrived + outPktHdr.to = inPktHdr.from; + outMailHdr.to = inMailHdr.from; + outMailHdr.length = strlen(ack) + 1; + postOffice->Send(outPktHdr, outMailHdr, ack); + + // Wait for the ack from the other machine to the first message we sent. + postOffice->Receive(1, &inPktHdr, &inMailHdr, buffer); + printf("Got \"%s\" from %d, box %d\n",buffer,inPktHdr.from,inMailHdr.from); + fflush(stdout); + + // Then we're done! + interrupt->Powerdown(); +} diff --git a/code/network/post.cc b/code/network/post.cc new file mode 100644 index 0000000..0733559 --- /dev/null +++ b/code/network/post.cc @@ -0,0 +1,347 @@ +// post.cc +// Routines to deliver incoming network messages to the correct +// "address" -- a mailbox, or a holding area for incoming messages. +// This module operates just like the US postal service (in other +// words, it works, but it's slow, and you can't really be sure if +// your mail really got through!). +// +// Note that once we prepend the MailHdr to the outgoing message data, +// the combination (MailHdr plus data) looks like "data" to the Network +// device. +// +// The implementation synchronizes incoming messages with threads +// waiting for those messages. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "post.h" + +#include /* for bzero */ + +//---------------------------------------------------------------------- +// Mail::Mail +// Initialize a single mail message, by concatenating the headers to +// the data. +// +// "pktH" -- source, destination machine ID's +// "mailH" -- source, destination mailbox ID's +// "data" -- payload data +//---------------------------------------------------------------------- + +Mail::Mail(PacketHeader pktH, MailHeader mailH, const void *msgData) +{ + ASSERT(mailH.length <= MaxMailSize); + + pktHdr = pktH; + mailHdr = mailH; + bcopy(msgData, data, mailHdr.length); +} + +//---------------------------------------------------------------------- +// MailBox::MailBox +// Initialize a single mail box within the post office, so that it +// can receive incoming messages. +// +// Just initialize a list of messages, representing the mailbox. +//---------------------------------------------------------------------- + + +MailBox::MailBox() +{ + messages = new SynchList(); +} + +//---------------------------------------------------------------------- +// MailBox::~MailBox +// De-allocate a single mail box within the post office. +// +// Just delete the mailbox, and throw away all the queued messages +// in the mailbox. +//---------------------------------------------------------------------- + +MailBox::~MailBox() +{ + delete messages; + messages = NULL; +} + +//---------------------------------------------------------------------- +// PrintHeader +// Print the message header -- the destination machine ID and mailbox +// #, source machine ID and mailbox #, and message length. +// +// "pktHdr" -- source, destination machine ID's +// "mailHdr" -- source, destination mailbox ID's +//---------------------------------------------------------------------- + +static void +PrintHeader(PacketHeader pktHdr, MailHeader mailHdr) +{ + printf("From (%d, %d) to (%d, %d) bytes %d\n", + pktHdr.from, mailHdr.from, pktHdr.to, mailHdr.to, mailHdr.length); +} + +//---------------------------------------------------------------------- +// MailBox::Put +// Add a message to the mailbox. If anyone is waiting for message +// arrival, wake them up! +// +// We need to reconstruct the Mail message (by concatenating the headers +// to the data), to simplify queueing the message on the SynchList. +// +// "pktHdr" -- source, destination machine ID's +// "mailHdr" -- source, destination mailbox ID's +// "data" -- payload message data +//---------------------------------------------------------------------- + +void +MailBox::Put(PacketHeader pktHdr, MailHeader mailHdr, const void *data) +{ + Mail *mail = new Mail(pktHdr, mailHdr, data); + + messages->Append((void *)mail); // put on the end of the list of + // arrived messages, and wake up + // any waiters +} + +//---------------------------------------------------------------------- +// MailBox::Get +// Get a message from a mailbox, parsing it into the packet header, +// mailbox header, and data. +// +// The calling thread waits if there are no messages in the mailbox. +// +// "pktHdr" -- address to put: source, destination machine ID's +// "mailHdr" -- address to put: source, destination mailbox ID's +// "data" -- address to put: payload message data +//---------------------------------------------------------------------- + +void +MailBox::Get(PacketHeader *pktHdr, MailHeader *mailHdr, void *data) +{ + DEBUG('n', "Waiting for mail in mailbox\n"); + Mail *mail = (Mail *) messages->Remove(); // remove message from list; + // will wait if list is empty + + *pktHdr = mail->pktHdr; + *mailHdr = mail->mailHdr; + if (DebugIsEnabled('n')) { + printf("Got mail from mailbox: "); + PrintHeader(*pktHdr, *mailHdr); + } + bcopy(mail->data, data, mail->mailHdr.length); + // copy the message data into + // the caller's buffer + delete mail; // we've copied out the stuff we + // need, we can now discard the message +} + +//---------------------------------------------------------------------- +// PostalHelper, ReadAvailHandler, WriteDoneHandler +// Dummy functions because C++ can't indirectly invoke member functions +// The first is forked as part of the "postal worker thread; the +// later two are called by the network interrupt handler. +// +// "arg" -- pointer to the Post Office managing the Network +//---------------------------------------------------------------------- + +static void PostalHelper(void *arg) +{ PostOffice* po = (PostOffice *) arg; po->PostalDelivery(); } +static void ReadAvailHandler(void *arg) +{ PostOffice* po = (PostOffice *) arg; po->IncomingPacket(); } +static void WriteDoneHandler(void *arg) +{ PostOffice* po = (PostOffice *) arg; po->PacketSent(); } + +//---------------------------------------------------------------------- +// PostOffice::PostOffice +// Initialize a post office as a collection of mailboxes. +// Also initialize the network device, to allow post offices +// on different machines to deliver messages to one another. +// +// We use a separate thread "the postal worker" to wait for messages +// to arrive, and deliver them to the correct mailbox. Note that +// delivering messages to the mailboxes can't be done directly +// by the interrupt handlers, because it requires a Lock. +// +// "addr" is this machine's network ID +// "reliability" is the probability that a network packet will +// be delivered (e.g., reliability = 1 means the network never +// drops any packets; reliability = 0 means the network never +// delivers any packets) +// "nBoxes" is the number of mail boxes in this Post Office +//---------------------------------------------------------------------- + +PostOffice::PostOffice(NetworkAddress addr, double reliability, int nBoxes) +{ +// First, initialize the synchronization with the interrupt handlers + messageAvailable = new Semaphore("message available", 0); + messageSent = new Semaphore("message sent", 0); + sendLock = new Lock("message send lock"); + +// Second, initialize the mailboxes + netAddr = addr; + numBoxes = nBoxes; + boxes = new MailBox[nBoxes]; + +// Third, initialize the network; tell it which interrupt handlers to call + network = new Network(addr, reliability, ReadAvailHandler, WriteDoneHandler, this); + + +// Finally, create a thread whose sole job is to wait for incoming messages, +// and put them in the right mailbox. + Thread *t = new Thread("postal worker"); + + t->Start(PostalHelper, this); +} + +//---------------------------------------------------------------------- +// PostOffice::~PostOffice +// De-allocate the post office data structures. +//---------------------------------------------------------------------- + +PostOffice::~PostOffice() +{ + delete network; + delete [] boxes; + delete messageAvailable; + delete messageSent; + delete sendLock; +} + +//---------------------------------------------------------------------- +// PostOffice::PostalDelivery +// Wait for incoming messages, and put them in the right mailbox. +// +// Incoming messages have had the PacketHeader stripped off, +// but the MailHeader is still tacked on the front of the data. +//---------------------------------------------------------------------- + +void +PostOffice::PostalDelivery() +{ + PacketHeader pktHdr; + MailHeader mailHdr; + char *buffer = new char[MaxPacketSize]; + + for (;;) { + // first, wait for a message + messageAvailable->P(); + pktHdr = network->Receive(buffer); + + mailHdr = *(MailHeader *)buffer; + if (DebugIsEnabled('n')) { + printf("Putting mail into mailbox: "); + PrintHeader(pktHdr, mailHdr); + } + + // check that arriving message is legal! + ASSERT(0 <= mailHdr.to && mailHdr.to < numBoxes); + ASSERT(mailHdr.length <= MaxMailSize); + + // put into mailbox + boxes[mailHdr.to].Put(pktHdr, mailHdr, buffer + sizeof(MailHeader)); + } +} + +//---------------------------------------------------------------------- +// PostOffice::Send +// Concatenate the MailHeader to the front of the data, and pass +// the result to the Network for delivery to the destination machine. +// +// Note that the MailHeader + data looks just like normal payload +// data to the Network. +// +// "pktHdr" -- source, destination machine ID's +// "mailHdr" -- source, destination mailbox ID's +// "data" -- payload message data +//---------------------------------------------------------------------- + +void +PostOffice::Send(PacketHeader pktHdr, MailHeader mailHdr, const void* data) +{ + char* buffer = new char[MaxPacketSize]; // space to hold concatenated + // mailHdr + data + + if (DebugIsEnabled('n')) { + printf("Post send: "); + PrintHeader(pktHdr, mailHdr); + } + ASSERT(mailHdr.length <= MaxMailSize); + ASSERT(0 <= mailHdr.to && mailHdr.to < numBoxes); + + // fill in pktHdr, for the Network layer + pktHdr.from = netAddr; + pktHdr.length = mailHdr.length + sizeof(MailHeader); + + // concatenate MailHeader and data + bcopy(&mailHdr, buffer, sizeof(MailHeader)); + bcopy(data, buffer + sizeof(MailHeader), mailHdr.length); + + sendLock->Acquire(); // only one message can be sent + // to the network at any one time + network->Send(pktHdr, buffer); + messageSent->P(); // wait for interrupt to tell us + // ok to send the next message + sendLock->Release(); + + delete [] buffer; // we've sent the message, so + // we can delete our buffer +} + +//---------------------------------------------------------------------- +// PostOffice::Send +// Retrieve a message from a specific box if one is available, +// otherwise wait for a message to arrive in the box. +// +// Note that the MailHeader + data looks just like normal payload +// data to the Network. +// +// +// "box" -- mailbox ID in which to look for message +// "pktHdr" -- address to put: source, destination machine ID's +// "mailHdr" -- address to put: source, destination mailbox ID's +// "data" -- address to put: payload message data +//---------------------------------------------------------------------- + +void +PostOffice::Receive(int box, PacketHeader *pktHdr, + MailHeader *mailHdr, void* data) +{ + ASSERT((box >= 0) && (box < numBoxes)); + + boxes[box].Get(pktHdr, mailHdr, data); + ASSERT(mailHdr->length <= MaxMailSize); +} + +//---------------------------------------------------------------------- +// PostOffice::IncomingPacket +// Interrupt handler, called when a packet arrives from the network. +// +// Signal the PostalDelivery routine that it is time to get to work! +//---------------------------------------------------------------------- + +void +PostOffice::IncomingPacket() +{ + messageAvailable->V(); +} + +//---------------------------------------------------------------------- +// PostOffice::PacketSent +// Interrupt handler, called when the next packet can be put onto the +// network. +// +// The name of this routine is a misnomer; if "reliability < 1", +// the packet could have been dropped by the network, so it won't get +// through. +//---------------------------------------------------------------------- + +void +PostOffice::PacketSent() +{ + messageSent->V(); +} + diff --git a/code/network/post.h b/code/network/post.h new file mode 100644 index 0000000..1e4cfdb --- /dev/null +++ b/code/network/post.h @@ -0,0 +1,141 @@ +// post.h +// Data structures for providing the abstraction of unreliable, +// ordered, fixed-size message delivery to mailboxes on other +// (directly connected) machines. Messages can be dropped by +// the network, but they are never corrupted. +// +// The US Post Office delivers mail to the addressed mailbox. +// By analogy, our post office delivers packets to a specific buffer +// (MailBox), based on the mailbox number stored in the packet header. +// Mail waits in the box until a thread asks for it; if the mailbox +// is empty, threads can wait for mail to arrive in it. +// +// Thus, the service our post office provides is to de-multiplex +// incoming packets, delivering them to the appropriate thread. +// +// With each message, you get a return address, which consists of a "from +// address", which is the id of the machine that sent the message, and +// a "from box", which is the number of a mailbox on the sending machine +// to which you can send an acknowledgement, if your protocol requires +// this. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" + +#ifndef POST_H +#define POST_H + +#include "network.h" +#include "synchlist.h" + +// Mailbox address -- uniquely identifies a mailbox on a given machine. +// A mailbox is just a place for temporary storage for messages. +typedef int MailBoxAddress; + +// The following class defines part of the message header. +// This is prepended to the message by the PostOffice, before the message +// is sent to the Network. + +class MailHeader { + public: + MailBoxAddress to; // Destination mail box + MailBoxAddress from; // Mail box to reply to + unsigned length; // Bytes of message data (excluding the + // mail header) +}; + +// Maximum "payload" -- real data -- that can included in a single message +// Excluding the MailHeader and the PacketHeader + +#define MaxMailSize (MaxPacketSize - sizeof(MailHeader)) + + +// The following class defines the format of an incoming/outgoing +// "Mail" message. The message format is layered: +// network header (PacketHeader) +// post office header (MailHeader) +// data + +class Mail { + public: + Mail(PacketHeader pktH, MailHeader mailH, const void *msgData); + // Initialize a mail message by + // concatenating the headers to the data + + PacketHeader pktHdr; // Header appended by Network + MailHeader mailHdr; // Header appended by PostOffice + char data[MaxMailSize]; // Payload -- message data +}; + +// The following class defines a single mailbox, or temporary storage +// for messages. Incoming messages are put by the PostOffice into the +// appropriate mailbox, and these messages can then be retrieved by +// threads on this machine. + +class MailBox:public dontcopythis { + public: + MailBox(); // Allocate and initialize mail box + ~MailBox(); // De-allocate mail box + + void Put(PacketHeader pktHdr, MailHeader mailHdr, const void *data); + // Atomically put a message into the mailbox + void Get(PacketHeader *pktHdr, MailHeader *mailHdr, void *data); + // Atomically get a message out of the + // mailbox (and wait if there is no message + // to get!) + private: + SynchList *messages; // A mailbox is just a list of arrived messages +}; + +// The following class defines a "Post Office", or a collection of +// mailboxes. The Post Office is a synchronization object that provides +// two main operations: Send -- send a message to a mailbox on a remote +// machine, and Receive -- wait until a message is in the mailbox, +// then remove and return it. +// +// Incoming messages are put by the PostOffice into the +// appropriate mailbox, waking up any threads waiting on Receive. + +class PostOffice:public dontcopythis { + public: + PostOffice(NetworkAddress addr, double reliability, int nBoxes); + // Allocate and initialize Post Office + // "reliability" is how many packets + // get dropped by the underlying network + ~PostOffice(); // De-allocate Post Office data + + void Send(PacketHeader pktHdr, MailHeader mailHdr, const void *data); + // Send a message to a mailbox on a remote + // machine. The fromBox in the MailHeader is + // the return box for ack's. + + void Receive(int box, PacketHeader *pktHdr, + MailHeader *mailHdr, void *data); + // Retrieve a message from "box". Wait if + // there is no message in the box. + + void PostalDelivery(); // Wait for incoming messages, + // and then put them in the correct mailbox + + void PacketSent(); // Interrupt handler, called when outgoing + // packet has been put on network; next + // packet can now be sent + void IncomingPacket(); // Interrupt handler, called when incoming + // packet has arrived and can be pulled + // off of network (i.e., time to call + // PostalDelivery) + + private: + Network *network; // Physical network connection + NetworkAddress netAddr; // Network address of this machine + MailBox *boxes; // Table of mail boxes to hold incoming mail + int numBoxes; // Number of mail boxes + Semaphore *messageAvailable;// V'ed when message has arrived from network + Semaphore *messageSent; // V'ed when next message can be sent to network + Lock *sendLock; // Only one outgoing message at a time +}; + +#endif diff --git a/code/test/Makefile b/code/test/Makefile new file mode 100644 index 0000000..de1f7fc --- /dev/null +++ b/code/test/Makefile @@ -0,0 +1,97 @@ +# Use regular make for this Makefile +# +# Makefile for building user programs to run on top of Nachos +# Several things to be aware of: Nachos assumes that the location of +# the program startup routine (the location where the kernel jumps to +# when the program initially starts up) is location 0. This means: +# start.o must be the first .o passed to ld, so that routine "Start" +# gets loaded at location 0 +# +# if you are cross-compiling, you need to point to the right executables +# and change the flags to "ld" and the build procedure for "as". + +NACHOS_ROOT = ../../ +NACHOS_SYS := $(shell $(NACHOS_ROOT)/bin/nachos_sys) +NACHOS_ARCH := $(shell $(NACHOS_ROOT)/bin/nachos_arch) + +ifneq ($(wildcard /net/cremi/aguermou/xgcc/decstation-ultrix/bin),) +# Cremi 32bit +GCCDIR = /net/cremi/aguermou/xgcc/decstation-ultrix/bin +GCC := gcc +endif + +ifneq ($(wildcard /net/ens/nachos/mipsel/bin),) +# Cremi 64bit +GCCDIR = /net/ens/nachos/mipsel/bin +GCC := gcc +endif + +ifneq ($(wildcard /opt/xgcc/decstation-ultrix/bin),) +# Maison 32bit +GCCDIR = /opt/xgcc/decstation-ultrix/bin +GCC := xgcc +endif + +ifneq ($(wildcard /opt/xgcc/mipsel/bin),) +# Maison 64bit +GCCDIR = /opt/xgcc/mipsel/bin +GCC := gcc +endif + +# We don't support native builds +ifeq ($(GCCDIR),) +$(error When not running at CREMI, I need the cross compiler installed in /opt/xgcc) +endif + +LDFLAGS = -T script -N +ASFLAGS = +CPPFLAGS = $(INCDIR) -DCHANGED + + +# If you aren't cross-compiling: +# GCCDIR = +# LDFLAGS = -N -T 0 +# ASFLAGS = +# CPPFLAGS = -P $(INCDIR) + + +CC := $(GCCDIR)/$(GCC) +AS := $(GCCDIR)/as +LD := $(GCCDIR)/ld +STRIP := $(GCCDIR)/strip +OBJDUMP := $(GCCDIR)/objdump + +CPP := $(GCCDIR)/$(GCC) -E -P +INCDIR := -I../userprog -I../threads +CFLAGS := -DIN_USER_MODE $(INCDIR) -Wall -O2 -DCHANGED + +SOURCES := $(wildcard *.c) +PROGS := $(patsubst %.c,%,$(SOURCES)) + +.PHONY: all + +all: $(PROGS) + +start.o: start.S ../userprog/syscall.h + $(CPP) $(CPPFLAGS) start.S > strt.s + $(AS) $(ASFLAGS) -o start.o strt.s + rm strt.s + +%.o: %.c ../userprog/syscall.h + $(CC) $(CFLAGS) -c $< + +# LB: Caution! start.o should appear *before* $< for the load! + +%.coff: %.o start.o + $(LD) $(LDFLAGS) start.o $< -o $@ + +%.s: %.coff + $(OBJDUMP) -d $< | sed -e 's/\/r0/g;s/\/r1/g;s/\/r2/g;s/\/r3/g;s/\/r4/g;s/\/r5/g;s/\/r6/g;s/\/r7/g;s/\/r8/g;s/\/r28/g;s/\/r29/g;s/\/r30/g;s/\/r31/g;' > $@ + +$(PROGS): %: %.coff + ../bin/coff2noff $< $@ + +# Cleaning rule +.PHONY: clean +clean: + rm -f core *.coff *.o *.s $(PROGS) diff --git a/code/test/halt.c b/code/test/halt.c new file mode 100644 index 0000000..a83b5bf --- /dev/null +++ b/code/test/halt.c @@ -0,0 +1,22 @@ +/* halt.c + * Simple program to test whether running a user program works. + * + * Just do a "syscall" that shuts down the OS. + * + * NOTE: for some reason, user programs with global data structures + * sometimes haven't worked in the Nachos environment. So be careful + * out there! One option is to allocate data structures as + * automatics within a procedure, but if you do this, you have to + * be careful to allocate a big enough stack to hold the automatics! + */ + +#include "syscall.h" + +int +main () +{ + Halt (); + + /* not reached */ + return 0; +} diff --git a/code/test/matmult.c b/code/test/matmult.c new file mode 100644 index 0000000..7396343 --- /dev/null +++ b/code/test/matmult.c @@ -0,0 +1,39 @@ +/* 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 "syscall.h" + +#define Dim 20 /* sum total of the arrays doesn't fit in + * physical memory + */ + +int A[Dim][Dim]; +int B[Dim][Dim]; +int C[Dim][Dim]; + +int +main () +{ + int i, j, k; + + for (i = 0; i < Dim; i++) /* first initialize the matrices */ + for (j = 0; j < Dim; j++) + { + A[i][j] = i; + B[i][j] = j; + C[i][j] = 0; + } + + for (i = 0; i < Dim; i++) /* then multiply them together */ + for (j = 0; j < Dim; j++) + for (k = 0; k < Dim; k++) + C[i][j] += A[i][k] * B[k][j]; + + Exit (C[Dim - 1][Dim - 1]); /* and then we're done */ +} diff --git a/code/test/script b/code/test/script new file mode 100644 index 0000000..1ece1a6 --- /dev/null +++ b/code/test/script @@ -0,0 +1,47 @@ +OUTPUT_FORMAT("ecoff-littlemips") +ENTRY(__start) +SECTIONS +{ + .text 0 : { + _ftext = . ; + *(.init) + eprol = .; + *(.text*) + *(.fini) + . = ALIGN(128); + } + etext = .; + _etext = .; + _fdata = .; + .data . : { + *(.rdata) + *(.rodata) + *(.sdata) + *(.data) + CONSTRUCTORS + . = ALIGN(128); + } + edata = .; + _edata = .; + _fbss = .; + .sbss . : { + *(.sbss) + *(.scommon) + . = ALIGN(128); + } + .bss . : { + *(.bss) + *(COMMON) + . = ALIGN(128); + } + .drop . : { + *(.reginfo) + *(.pdr) + *(.MIPS.ab*) + *(.comment*) + *(.gnu.att*) + } + end = .; + _end = .; +} + diff --git a/code/test/shell.c b/code/test/shell.c new file mode 100644 index 0000000..e41eaf2 --- /dev/null +++ b/code/test/shell.c @@ -0,0 +1,37 @@ +#include "syscall.h" + +int +main () +{ + SpaceId newProc; + OpenFileId input = ConsoleInput; + OpenFileId output = ConsoleOutput; + char prompt[2], buffer[60]; + int i; + + prompt[0] = '$'; + prompt[1] = ' '; + + while (1) + { + Write (prompt, 2, output); + + i = 0; + + do + { + + Read (&buffer[i], 1, input); + + } + while (buffer[i++] != '\n'); + + buffer[--i] = '\0'; + + if (i > 0) + { + newProc = Exec (buffer); + Join (newProc); + } + } +} diff --git a/code/test/sort.c b/code/test/sort.c new file mode 100644 index 0000000..ae11eac --- /dev/null +++ b/code/test/sort.c @@ -0,0 +1,34 @@ +/* sort.c + * Test program to sort a large number of integers through BubbleSort. + * + * 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 "syscall.h" + +#define N 1024 +int A[N]; /* size of physical memory; with code, we'll run out of space! */ + +int +main () +{ + int i, j, tmp; + + /* first initialize the array, in reverse sorted order */ + for (i = 0; i < N; i++) + A[i] = N - i; + + /* then sort! */ + for (i = 1; i < N; i++) + for (j = 0; j < N - i; j++) + if (A[j] > A[j + 1]) + { /* out of order -> need to swap ! */ + tmp = A[j]; + A[j] = A[j + 1]; + A[j + 1] = tmp; + } + Exit (A[0]); /* and then we're done -- should be 0! */ +} diff --git a/code/test/start.S b/code/test/start.S new file mode 100644 index 0000000..cd9817f --- /dev/null +++ b/code/test/start.S @@ -0,0 +1,155 @@ +/* Start.s + * Assembly language assist for user programs running on top of Nachos. + * + * Since we don't want to pull in the entire C library, we define + * what we need for a user program here, namely Start and the system + * calls. + */ + +#include "syscall.h" + +/* ------------------------------------------------------------- + * 0-jump catcher + * Catch jumps to 0 by emiting an illegal instruction at 0 + * ------------------------------------------------------------- + */ + .text + .org 0 + .long 0xffffffff + .long 0xffffffff + +/* ------------------------------------------------------------- + * __start + * Initialize running a C program, by calling "main". + * + * NOTE: This has to be first, so that it gets loaded at location 0. + * The Nachos kernel always starts a program by jumping to location 0. + * ------------------------------------------------------------- + */ + + .org USER_START_ADDRESS + + .globl __start + .ent __start +__start: + jal main + move $4,$0 + jal Exit /* if we return from main, exit(0) */ + .end __start + +/* ------------------------------------------------------------- + * Exit-returning catcher + * Catch Exit system call returning + * ------------------------------------------------------------- + */ + .long 0xffffffff + .long 0xffffffff + +/* ------------------------------------------------------------- + * System call stubs: + * Assembly language assist to make system calls to the Nachos kernel. + * There is one stub per system call, that places the code for the + * system call into register r2, and leaves the arguments to the + * system call alone (in other words, arg1 is in r4, arg2 is + * in r5, arg3 is in r6, arg4 is in r7) + * + * The return value is in r2. This follows the standard C calling + * convention on the MIPS. + * ------------------------------------------------------------- + */ + + .globl Halt + .ent Halt +Halt: + addiu $2,$0,SC_Halt + syscall + j $31 + .end Halt + + .globl Exit + .ent Exit +Exit: + addiu $2,$0,SC_Exit + syscall + j $31 + .end Exit + + .globl Exec + .ent Exec +Exec: + addiu $2,$0,SC_Exec + syscall + j $31 + .end Exec + + .globl Join + .ent Join +Join: + addiu $2,$0,SC_Join + syscall + j $31 + .end Join + + .globl Create + .ent Create +Create: + addiu $2,$0,SC_Create + syscall + j $31 + .end Create + + .globl Open + .ent Open +Open: + addiu $2,$0,SC_Open + syscall + j $31 + .end Open + + .globl Read + .ent Read +Read: + addiu $2,$0,SC_Read + syscall + j $31 + .end Read + + .globl Write + .ent Write +Write: + addiu $2,$0,SC_Write + syscall + j $31 + .end Write + + .globl Close + .ent Close +Close: + addiu $2,$0,SC_Close + syscall + j $31 + .end Close + + .globl Fork + .ent Fork +Fork: + addiu $2,$0,SC_Fork + syscall + j $31 + .end Fork + + .globl Yield + .ent Yield +Yield: + addiu $2,$0,SC_Yield + syscall + j $31 + .end Yield + +/* dummy function to keep gcc happy */ + .globl __main + .ent __main +__main: + j $31 + .end __main + diff --git a/code/threads/Makefile b/code/threads/Makefile new file mode 100644 index 0000000..8f2b8a6 --- /dev/null +++ b/code/threads/Makefile @@ -0,0 +1,17 @@ +# NOTE: this is a GNU Makefile. You must use "gmake" rather than "make". +# +# Makefile for the threads assignment. The threads assignment must +# be done first! +# +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +DEFINES = -DTHREADS +INCPATH = -I../threads -I../machine +C_OFILES = $(THREAD_O) + +include ../Makefile.common +include ../Makefile.dep + + diff --git a/code/threads/bool.h b/code/threads/bool.h new file mode 100644 index 0000000..ce4b550 --- /dev/null +++ b/code/threads/bool.h @@ -0,0 +1,14 @@ +// Defining TRUE and FALSE is usually a Bad Idea, +// because you will probably be inconsistent with anyone +// else who had the same clever idea. +// Therefore: DON'T USE THIS FILE. + +#ifndef _bool_h +#define _bool_h 1 + +#include + +#define TRUE true +#define FALSE false + +#endif diff --git a/code/threads/copyright.h b/code/threads/copyright.h new file mode 100644 index 0000000..1d6b910 --- /dev/null +++ b/code/threads/copyright.h @@ -0,0 +1,25 @@ +/* +Copyright (c) 1992-1993 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. +*/ + +#ifdef MAIN /* include the copyright message in every executable */ +static const char *copyright = + "Copyright (c) 1992-1993 The Regents of the University of California. All rights reserved."; +#endif // MAIN diff --git a/code/threads/list.cc b/code/threads/list.cc new file mode 100644 index 0000000..c3bc0f3 --- /dev/null +++ b/code/threads/list.cc @@ -0,0 +1,332 @@ +// list.cc +// +// Routines to manage a singly-linked list of "things". +// +// A "ListElement" is allocated for each item to be put on the +// list; it is de-allocated when the item is removed. This means +// we don't need to keep a "next" pointer in every object we +// want to put on a list. +// +// NOTE: Mutual exclusion must be provided by the caller. +// If you want a synchronized list, you must use the routines +// in synchlist.cc. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "list.h" + +//---------------------------------------------------------------------- +// ListElement::ListElement +// Initialize a list element, so it can be added somewhere on a list. +// +// "itemPtr" is the item to be put on the list. It can be a pointer +// to anything. +// "sortKey" is the priority of the item, if any. +//---------------------------------------------------------------------- + +ListElement::ListElement (void *itemPtr, long long sortKey) +{ + item = itemPtr; + key = sortKey; + next = NULL; // assume we'll put it at the end of the list +} + +//---------------------------------------------------------------------- +// List::List +// Initialize a list, empty to start with. +// Elements can now be added to the list. +//---------------------------------------------------------------------- + +List::List () +{ + first = last = NULL; +} + +//---------------------------------------------------------------------- +// List::~List +// Prepare a list for deallocation. If the list still contains any +// ListElements, de-allocate them. However, note that we do *not* +// de-allocate the "items" on the list -- this module allocates +// and de-allocates the ListElements to keep track of each item, +// but a given item may be on multiple lists, so we can't +// de-allocate them here. +//---------------------------------------------------------------------- + +List::~List () +{ + while (Remove () != NULL) + ; // delete all the list elements +} + +//---------------------------------------------------------------------- +// List::Append +// Append an "item" to the end of the list. +// +// Allocate a ListElement to keep track of the item. +// If the list is empty, then this will be the only element. +// Otherwise, put it at the end. +// +// "item" is the thing to put on the list, it can be a pointer to +// anything. +//---------------------------------------------------------------------- + +void +List::Append (void *item) +{ + ListElement *element = new ListElement (item, 0); + + if (IsEmpty ()) + { // list is empty + first = element; + last = element; + } + else + { // else put it after last + last->next = element; + last = element; + } +} + +//---------------------------------------------------------------------- +// List::Prepend +// Put an "item" on the front of the list. +// +// Allocate a ListElement to keep track of the item. +// If the list is empty, then this will be the only element. +// Otherwise, put it at the beginning. +// +// "item" is the thing to put on the list, it can be a pointer to +// anything. +//---------------------------------------------------------------------- + +void +List::Prepend (void *item) +{ + ListElement *element = new ListElement (item, 0); + + if (IsEmpty ()) + { // list is empty + first = element; + last = element; + } + else + { // else put it before first + element->next = first; + first = element; + } +} + +//---------------------------------------------------------------------- +// List::FirstItem +// Read item off the front of the list +// +// Returns: +// Pointer to first item, NULL if nothing on the list. +//---------------------------------------------------------------------- + +ListElement * +List::FirstElement () +{ + return first; +} + +//---------------------------------------------------------------------- +// List::Remove +// Remove the first "item" from the front of the list. +// +// Returns: +// Pointer to removed item, NULL if nothing on the list. +//---------------------------------------------------------------------- + +void * +List::Remove () +{ + return SortedRemove (NULL); // Same as SortedRemove, but ignore the key +} + +//---------------------------------------------------------------------- +// List::Remove +// Remove "item" off the list. +//---------------------------------------------------------------------- + +void +List::Remove (void *item) +{ + ListElement **cur; + ListElement *prec = NULL, *next; + + for (cur = &first; *cur; prec=*cur, cur = &(*cur)->next) + { + if ((*cur)->item == item) + { + if(*cur==last) + last = prec; + next = (*cur)->next; + delete *cur; + *cur = next; + return; + } + } + + ASSERT(FALSE); +} + +//---------------------------------------------------------------------- +// List::Length +// Return the length of the list. +//---------------------------------------------------------------------- + +int +List::Length (void) +{ + ListElement *cur; + int n = 0; + + for (cur = first; cur; cur = cur->next) + n++; + + return n; +} + +//---------------------------------------------------------------------- +// List::Mapcar +// Apply a function to each item on the list, by walking through +// the list, one element at a time. +// +// Unlike LISP, this mapcar does not return anything! +// +// "func" is the procedure to apply to each element of the list. +//---------------------------------------------------------------------- + +void +List::Mapcar (VoidFunctionPtr func) +{ + ListElement *next; + for (ListElement * ptr = first; ptr != NULL; ptr = next) + { + next = ptr->next; + DEBUG ('l', "In mapcar, about to invoke %p(%p)\n", func, ptr->item); + (*func) (ptr->item); + } +} + +//---------------------------------------------------------------------- +// List::Mapcar +// Similar to the former, but also passes an addition argument to the +// function. +//---------------------------------------------------------------------- + +void +List::Mapcar (VoidFunctionPtr2 func, void *arg) +{ + ListElement *next; + for (ListElement * ptr = first; ptr != NULL; ptr = next) + { + next = ptr->next; + DEBUG ('l', "In mapcar, about to invoke %p(%p)\n", func, ptr->item); + (*func) (ptr->item, arg); + } +} + +//---------------------------------------------------------------------- +// List::IsEmpty +// Returns TRUE if the list is empty (has no items). +//---------------------------------------------------------------------- + +bool +List::IsEmpty () +{ + if (first == NULL) + return TRUE; + else + return FALSE; +} + +//---------------------------------------------------------------------- +// List::SortedInsert +// Insert an "item" into a list, so that the list elements are +// sorted in increasing order by "sortKey". +// +// Allocate a ListElement to keep track of the item. +// If the list is empty, then this will be the only element. +// Otherwise, walk through the list, one element at a time, +// to find where the new item should be placed. +// +// "item" is the thing to put on the list, it can be a pointer to +// anything. +// "sortKey" is the priority of the item. +//---------------------------------------------------------------------- + +void +List::SortedInsert (void *item, long long sortKey) +{ + ListElement *element = new ListElement (item, sortKey); + ListElement *ptr; // keep track + + if (IsEmpty ()) + { // if list is empty, put + first = element; + last = element; + } + else if (sortKey < first->key) + { + // item goes on front of list + element->next = first; + first = element; + } + else + { // look for first elt in list bigger than item + for (ptr = first; ptr->next != NULL; ptr = ptr->next) + { + if (sortKey < ptr->next->key) + { + element->next = ptr->next; + ptr->next = element; + return; + } + } + last->next = element; // item goes at end of list + last = element; + } +} + +//---------------------------------------------------------------------- +// List::SortedRemove +// Remove the first "item" from the front of a sorted list. +// +// Returns: +// Pointer to removed item, NULL if nothing on the list. +// Sets *keyPtr to the priority value of the removed item +// (this is needed by interrupt.cc, for instance). +// +// "keyPtr" is a pointer to the location in which to store the +// priority of the removed item. +//---------------------------------------------------------------------- + +void * +List::SortedRemove (long long *keyPtr) +{ + ListElement *element = first; + void *thing; + + if (IsEmpty ()) + return NULL; + + thing = first->item; + if (first == last) + { // list had one item, now has none + first = NULL; + last = NULL; + } + else + { + first = element->next; + } + if (keyPtr != NULL) + *keyPtr = element->key; + delete element; + return thing; +} diff --git a/code/threads/list.h b/code/threads/list.h new file mode 100644 index 0000000..82ac90b --- /dev/null +++ b/code/threads/list.h @@ -0,0 +1,74 @@ +// list.h +// Data structures to manage LISP-like lists. +// +// As in LISP, a list can contain any type of data structure +// as an item on the list: thread control blocks, +// pending interrupts, etc. That is why each item is a "void *", +// or in other words, a "pointers to anything". +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef LIST_H +#define LIST_H + +#include "copyright.h" +#include "utility.h" + +// The following class defines a "list element" -- which is +// used to keep track of one item on a list. It is equivalent to a +// LISP cell, with a "car" ("next") pointing to the next element on the list, +// and a "cdr" ("item") pointing to the item on the list. +// +// Internal data structures kept public so that List operations can +// access them directly. + +class ListElement:public dontcopythis +{ + public: + ListElement (void *itemPtr, long long sortKey); // initialize a list element + + ListElement *next; // next element on list, + // NULL if this is the last + long long key; // priority, for a sorted list + void *item; // pointer to item on the list +}; + +// The following class defines a "list" -- a singly linked list of +// list elements, each of which points to a single item on the list. +// +// By using the "Sorted" functions, the list can be kept in sorted +// in increasing order by "key" in ListElement. + +class List:public dontcopythis +{ + public: + List (); // initialize the list + ~List (); // de-allocate the list + + void Prepend (void *item); // Put item at the beginning of the list + void Append (void *item); // Put item at the end of the list + ListElement *FirstElement (); // Read item off the front of the list + void *Remove (); // Take item off the front of the list + void Remove (void *item); // Remove item off the list + int Length (); // Return length + + void Mapcar (VoidFunctionPtr func); // Apply "func" to every element + // on the list + void Mapcar (VoidFunctionPtr2 func, void *arg); // Apply "func" to every + // element on the list with the + // additional arg + bool IsEmpty (); // is the list empty? + + + // Routines to put/get items on/off list in order (sorted by key) + void SortedInsert (void *item, long long sortKey); // Put item into list + void *SortedRemove (long long *keyPtr); // Remove first item from list + + private: + ListElement *first; // Head of the list, NULL if list is empty + ListElement *last; // Last element of list +}; + +#endif // LIST_H diff --git a/code/threads/main.cc b/code/threads/main.cc new file mode 100644 index 0000000..cad9249 --- /dev/null +++ b/code/threads/main.cc @@ -0,0 +1,190 @@ +// main.cc +// Bootstrap code to initialize the operating system kernel. +// +// Allows direct calls into internal operating system functions, +// to simplify debugging and testing. In practice, the +// bootstrap code would just initialize data structures, +// and start a user program to print the login prompt. +// +// Most of this file is not needed until later assignments. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#define MAIN +#include "copyright.h" +#undef MAIN + +#include "utility.h" +#include "system.h" + +#ifdef USER_PROGRAM +#include "progtest.h" +#endif + +#include + + +// External functions used by this file + +extern void ThreadTest (void), Copy (char *unixFile, char *nachosFile); +extern void Print (char *file), PerformanceTest (void); +extern void MailTest (int networkID); + +//---------------------------------------------------------------------- +// main +// Bootstrap the operating system kernel. +// +// Check command line arguments +// Initialize data structures +// (optionally) Call test procedure +// +// "argc" is the number of command line arguments (including the name +// of the command) -- ex: "nachos -d +" -> argc = 3 +// "argv" is an array of strings, one for each command line argument +// ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} +//---------------------------------------------------------------------- + +int +main (int argc, char **argv) +{ + int argCount; // the number of arguments + // for a particular command + + if (argc > 1 && !strcmp (argv[1], "-h")) // print help + { +// NOTE -- flags are ignored until the relevant assignment. +// Some of the flags are interpreted here; some in system.cc. +// + printf ( +"Usage: nachos -d -rs -z -h\n" +#ifdef USER_PROGRAM +" -s -x -c \n" +#endif +#ifdef FILESYS +" -f -cp \n" +" -p -r -l -D -t\n" +#endif +#ifdef NETWORK +" -n -m \n" +" -o \n" +#endif +"\n" +"-d causes certain debugging messages to be printed (cf. utility.h)\n" +"-rs causes Yield to occur at random (but repeatable) spots\n" +"-z prints the copyright message\n" +"-h prints some help about options\n" +"\n" +#ifdef USER_PROGRAM +"USER_PROGRAM\n" +"-s causes user programs to be executed in single-step mode\n" +"-x runs a user program\n" +"-c tests the console\n" +#endif +#ifdef FILESYS +"FILESYS\n" +"-f causes the physical disk to be formatted\n" +"-cp copies a file from UNIX to Nachos\n" +"-p prints a Nachos file to stdout\n" +"-r removes a Nachos file from the file system\n" +"-l lists the contents of the Nachos directory\n" +"-D prints the contents of the entire file system\n" +"-t tests the performance of the Nachos file system\n" +#endif +#ifdef NETWORK +"NETWORK\n" +"-n sets the network reliability\n" +"-m sets this machine's host id (needed for the network)\n" +"-o runs a simple test of the Nachos network software" +#endif + ); + return (0); + } + + DEBUG ('t', "Entering main"); + (void) Initialize (argc, argv); + +#ifdef THREADS + ThreadTest (); +#endif + + for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) + { + argCount = 1; + if (!strcmp (*argv, "-z")) // print copyright + printf ("%s", copyright); +#ifdef USER_PROGRAM + if (!strcmp (*argv, "-x")) + { // run a user program + ASSERT (argc > 1); + StartProcess (*(argv + 1)); + argCount = 2; + } + else if (!strcmp (*argv, "-c")) + { // test the console + if (argc == 1) + ConsoleTest (NULL, NULL); + else + { + ASSERT (argc > 2); + ConsoleTest (*(argv + 1), *(argv + 2)); + argCount = 3; + } + } +#endif // USER_PROGRAM +#ifdef FILESYS + if (!strcmp (*argv, "-cp")) + { // copy from UNIX to Nachos + ASSERT (argc > 2); + Copy (*(argv + 1), *(argv + 2)); + argCount = 3; + } + else if (!strcmp (*argv, "-p")) + { // print a Nachos file + ASSERT (argc > 1); + Print (*(argv + 1)); + argCount = 2; + } + else if (!strcmp (*argv, "-r")) + { // remove Nachos file + ASSERT (argc > 1); + fileSystem->Remove (*(argv + 1)); + argCount = 2; + } + else if (!strcmp (*argv, "-l")) + { // list Nachos directory + fileSystem->List (); + } + else if (!strcmp (*argv, "-D")) + { // print entire filesystem + fileSystem->Print (); + } + else if (!strcmp (*argv, "-t")) + { // performance test + PerformanceTest (); + } +#endif // FILESYS +#ifdef NETWORK + if (!strcmp (*argv, "-o")) + { + ASSERT (argc > 1); + Delay (2); // delay for 2 seconds + // to give the user time to + // start up another nachos + MailTest (atoi (*(argv + 1))); + argCount = 2; + } +#endif // NETWORK + } + + currentThread->Finish (); // NOTE: if the procedure "main" + // returns, then the program "nachos" + // will exit (as any other normal program + // would). But there may be other + // threads on the ready list. We switch + // to those threads by saying that the + // "main" thread is finished, preventing + // it from returning. + return (0); // Not reached... +} diff --git a/code/threads/scheduler.cc b/code/threads/scheduler.cc new file mode 100644 index 0000000..cb96756 --- /dev/null +++ b/code/threads/scheduler.cc @@ -0,0 +1,182 @@ +// scheduler.cc +// Routines to choose the next thread to run, and to dispatch to +// that thread. +// +// These routines assume that interrupts are already disabled. +// If interrupts are disabled, we can assume mutual exclusion +// (since we are on a uniprocessor). +// +// NOTE: We can't use Locks to provide mutual exclusion here, since +// if we needed to wait for a lock, and the lock was busy, we would +// end up calling FindNextToRun(), and that would put us in an +// infinite loop. +// +// Very simple implementation -- no priorities, straight FIFO. +// Might need to be improved in later assignments. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "scheduler.h" +#include "system.h" +#ifdef __SANITIZE_ADDRESS__ +#include +#endif + +//---------------------------------------------------------------------- +// Scheduler::Scheduler +// Initialize the list of ready but not running threads to empty. +//---------------------------------------------------------------------- + +Scheduler::Scheduler () +{ + readyList = new List; + halted = FALSE; +} + +//---------------------------------------------------------------------- +// Scheduler::Stop +// Prevent further context switches, used when halting the system +//---------------------------------------------------------------------- + +void +Scheduler::Stop () +{ + halted = TRUE; +} + +//---------------------------------------------------------------------- +// Scheduler::~Scheduler +// De-allocate the list of ready threads. +//---------------------------------------------------------------------- + +Scheduler::~Scheduler () +{ + delete readyList; + readyList = NULL; +} + +//---------------------------------------------------------------------- +// Scheduler::ReadyToRun +// Mark a thread as ready, but not running. +// Put it on the ready list, for later scheduling onto the CPU. +// +// "thread" is the thread to be put on the ready list. +//---------------------------------------------------------------------- + +void +Scheduler::ReadyToRun (Thread * thread) +{ + DEBUG ('t', "Putting thread %p %s on ready list.\n", thread, thread->getName ()); + + thread->setStatus (READY); + readyList->Append ((void *) thread); +} + +//---------------------------------------------------------------------- +// Scheduler::FindNextToRun +// Return the next thread to be scheduled onto the CPU. +// If there are no ready threads, return NULL. +// Side effect: +// Thread is removed from the ready list. +//---------------------------------------------------------------------- + +Thread * +Scheduler::FindNextToRun () +{ + if (halted) + return NULL; + return (Thread *) readyList->Remove (); +} + +//---------------------------------------------------------------------- +// Scheduler::Run +// Dispatch the CPU to nextThread. Save the state of the old thread, +// and load the state of the new thread, by calling the machine +// dependent context switch routine, SWITCH. +// +// Note: we assume the state of the previously running thread has +// already been changed from running to blocked or ready (depending). +// Side effect: +// The global variable currentThread becomes nextThread. +// +// "nextThread" is the thread to be put into the CPU. +//---------------------------------------------------------------------- + +void +Scheduler::Run (Thread * nextThread) +{ + Thread *oldThread = currentThread; + + // LB: For safety... + ASSERT (interrupt->getLevel () == IntOff); + // End of addition + +#ifdef USER_PROGRAM // ignore until running user programs + if (currentThread->space != NULL) + { // if this thread is a user program, + currentThread->SaveUserState (); // save the user's CPU registers + currentThread->space->SaveState (); + } +#endif + + oldThread->CheckOverflow (); // check if the old thread + // had an undetected stack overflow + + currentThread = nextThread; // switch to the next thread + currentThread->setStatus (RUNNING); // nextThread is now running + + DEBUG ('t', "Switching from thread %p \"%s\" to thread %p \"%s\"\n", + oldThread, oldThread->getName (), nextThread, nextThread->getName ()); + + // This is a machine-dependent assembly language routine defined + // in switch.s. You may have to think + // a bit to figure out what happens after this, both from the point + // of view of the thread and from the perspective of the "outside world". + +#ifdef __SANITIZE_ADDRESS__ + if (threadToBeDestroyed == oldThread) + __sanitizer_start_switch_fiber (NULL, nextThread->stack, nextThread->stack_size); + else + __sanitizer_start_switch_fiber (&oldThread->fake_stack, nextThread->stack, nextThread->stack_size); +#endif + SWITCH (oldThread, nextThread); +#ifdef __SANITIZE_ADDRESS__ + __sanitizer_finish_switch_fiber (currentThread->fake_stack, NULL, NULL); +#endif + + DEBUG ('t', "Now in thread %p \"%s\"\n", currentThread, currentThread->getName ()); + + // If the old thread gave up the processor because it was finishing, + // we need to delete its carcass. Note we cannot delete the thread + // before now (for example, in Thread::Finish()), because up to this + // point, we were still running on the old thread's stack! + if (threadToBeDestroyed != NULL) + { + Thread *destroying = threadToBeDestroyed; + threadToBeDestroyed = NULL; + delete destroying; + } + +#ifdef USER_PROGRAM + if (currentThread->space != NULL) + { // if there is an address space + currentThread->RestoreUserState (); // to restore, do it. + currentThread->space->RestoreState (); + } +#endif +} + +//---------------------------------------------------------------------- +// Scheduler::Print +// Print the scheduler state -- in other words, the contents of +// the ready list. For debugging. +//---------------------------------------------------------------------- +void +Scheduler::Print () +{ + printf ("Ready list contents:\n"); + readyList->Mapcar (ThreadPrint); +} diff --git a/code/threads/scheduler.h b/code/threads/scheduler.h new file mode 100644 index 0000000..4f463f9 --- /dev/null +++ b/code/threads/scheduler.h @@ -0,0 +1,39 @@ +// scheduler.h +// Data structures for the thread dispatcher and scheduler. +// Primarily, the list of threads that are ready to run. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef SCHEDULER_H +#define SCHEDULER_H + +#include "copyright.h" +#include "list.h" +#include "thread.h" + +// The following class defines the scheduler/dispatcher abstraction -- +// the data structures and operations needed to keep track of which +// thread is running, and which threads are ready but not running. + +class Scheduler:public dontcopythis +{ + public: + Scheduler (); // Initialize list of ready threads + void Stop (); // Prevent further context switches + ~Scheduler (); // De-allocate ready list + + void ReadyToRun (Thread * thread); // Thread can be dispatched. + Thread *FindNextToRun (); // Dequeue first thread on the ready + // list, if any, and return thread. + void Run (Thread * nextThread); // Cause nextThread to start running + void Print (); // Print contents of ready list + + private: + List * readyList; // queue of threads that are ready to run, + // but not running + bool halted; // Whether we should prevent context switches +}; + +#endif // SCHEDULER_H diff --git a/code/threads/switch.S b/code/threads/switch.S new file mode 100644 index 0000000..0a81379 --- /dev/null +++ b/code/threads/switch.S @@ -0,0 +1,555 @@ +/* switch.s + * Machine dependent context switch routines. DO NOT MODIFY THESE! + * + * Context switching is inherently machine dependent, since + * the registers to be saved, how to set up an initial + * call frame, etc, are all specific to a processor architecture. + * + * This file currently supports the following architectures: + * DEC MIPS + * SUN SPARC + * HP PA-RISC + * Intel x86 (Linux + Solaris) + * Mac OS X PowerPC + * + * We define two routines for each architecture: + * + * ThreadRoot(InitialPC, InitialArg, WhenDonePC, StartupPC) + * InitialPC - The program counter of the procedure to run + * in this thread. + * InitialArg - The single argument to the thread. + * WhenDonePC - The routine to call when the thread returns. + * StartupPC - Routine to call when the thread is started. + * + * ThreadRoot is called from the SWITCH() routine to start + * a thread for the first time. + * + * SWITCH(oldThread, newThread) + * oldThread - The current thread that was running, where the + * CPU register state is to be saved. + * newThread - The new thread to be run, where the CPU register + * state is to be loaded from. + */ + +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#include "copyright.h" +#include "switch.h" + +#if defined(SOLARIS) || defined(MAC_OS) +/* Those systems prepend a _ to symbol names */ +#define ThreadRoot _ThreadRoot +#define SWITCH _SWITCH +#endif + +#ifdef HOST_MIPS + +/* Symbolic register names */ +#define z $0 /* zero register */ +#define a0 $4 /* argument registers */ +#define a1 $5 +#define s0 $16 /* callee saved */ +#define s1 $17 +#define s2 $18 +#define s3 $19 +#define s4 $20 +#define s5 $21 +#define s6 $22 +#define s7 $23 +#define sp $29 /* stack pointer */ +#define fp $30 /* frame pointer */ +#define ra $31 /* return address */ + + .text + .align 2 + + .globl ThreadRoot + .ent ThreadRoot,0 +ThreadRoot: + or fp,z,z # Clearing the frame pointer here + # makes gdb backtraces of thread stacks + # end here (I hope!) + + jal StartupPC # call startup procedure + move a0, InitialArg + jal InitialPC # call main procedure + jal WhenDonePC # when we re done, call clean up procedure + + # NEVER REACHED + .end ThreadRoot + + # a0 -- pointer to old Thread + # a1 -- pointer to new Thread + .globl SWITCH + .ent SWITCH,0 +SWITCH: + sw sp, SP(a0) # save new stack pointer + sw s0, S0(a0) # save all the callee-save registers + sw s1, S1(a0) + sw s2, S2(a0) + sw s3, S3(a0) + sw s4, S4(a0) + sw s5, S5(a0) + sw s6, S6(a0) + sw s7, S7(a0) + sw fp, FP(a0) # save frame pointer + sw ra, PC(a0) # save return address + + lw sp, SP(a1) # load the new stack pointer + lw s0, S0(a1) # load the callee-save registers + lw s1, S1(a1) + lw s2, S2(a1) + lw s3, S3(a1) + lw s4, S4(a1) + lw s5, S5(a1) + lw s6, S6(a1) + lw s7, S7(a1) + lw fp, FP(a1) + lw ra, PC(a1) # load the return address + + j ra + .end SWITCH +#endif /* HOST_MIPS */ + +#ifdef HOST_SPARC + +/* NOTE! These files appear not to exist on Solaris -- + * you need to find where (the SPARC-specific) MINFRAME, ST_FLUSH_WINDOWS, ... + * are defined. (I don't have a Solaris machine, so I have no way to tell.) + */ +#ifndef SOLARIS +#include +#include +#else +#include +#define MINFRAME 256 +#define STACK_ALIGN 32L + +#endif + +.seg "text" + +/* SPECIAL to the SPARC: + * The first two instruction of ThreadRoot are skipped because + * the address of ThreadRoot is made the return address of SWITCH() + * by the routine Thread::StackAllocate. SWITCH() jumps here on the + * "ret" instruction which is really at "jmp %o7+8". The 8 skips the + * two nops at the beginning of the routine. + */ +.globl ThreadRoot +ThreadRoot: + nop ; nop /* These 2 nops are skipped because we are called + * with a jmp+8 instruction. */ + clr %fp /* Clearing the frame pointer makes gdb backtraces + * of thread stacks end here. */ + /* Currently the arguments are in out registers we + * save them into local registers so they won't be + * trashed during the calls we make. */ + mov InitialPC, %l0 + mov InitialArg, %l1 + mov WhenDonePC, %l2 + /* Execute the code: + * call StartupPC(); + * call InitialPC(InitialArg); + * call WhenDonePC(); + */ + call StartupPC,0 + nop + call %l0, 1 + mov %l1, %o0 /* Using delay slot to setup argument to InitialPC */ + call %l2, 0 + nop + /* WhenDonePC call should never return. If it does + * we execute a trap into the debugger. */ + ta ST_BREAKPOINT + +.globl SWITCH +SWITCH: + save %sp, -MINFRAME, %sp + st %fp, [%i0] + st %i0, [%i0+I0] + st %i1, [%i0+I1] + st %i2, [%i0+I2] + st %i3, [%i0+I3] + st %i4, [%i0+I4] + st %i5, [%i0+I5] + st %i7, [%i0+I7] + ta ST_FLUSH_WINDOWS + nop + mov %i1, %l0 + ld [%l0+I0], %i0 + ld [%l0+I1], %i1 + ld [%l0+I2], %i2 + ld [%l0+I3], %i3 + ld [%l0+I4], %i4 + ld [%l0+I5], %i5 + ld [%l0+I7], %i7 + ld [%l0], %i6 + ret + restore + +#endif /* HOST_SPARC */ + +#ifdef HOST_PPC + + .globl ThreadRoot +ThreadRoot: + mr r30, r1 + + stw r5, 128(r1) + stw r6, 132(r1) + stw r7, 136(r1) +#Call startupPC + mtctr r8 + bctrl + mr r2, r31 +# Call InitialPC with InitialArg + lwz r3, 132(r1) + lwz r5, 128(r1) + mtctr r5 + bctrl +# Call WhenDonePC + lwz r7, 136(r1) + mtctr r7 + bctrl + + mr r31, r2 + + .globl SWITCH +SWITCH: + mflr r0 + + stw r0,4(r3) + stw r1,0(r3) + stw r2,8(r3) + stw r3,12(r3) + stw r5,20(r3) + stw r6,24(r3) + stw r7,28(r3) + stw r8,32(r3) + stw r9,36(r3) + stw r10,40(r3) + stw r11,44(r3) + stw r12,48(r3) + stw r13,52(r3) + stw r14,56(r3) + stw r15,60(r3) + stw r16,64(r3) + stw r17,68(r3) + stw r18,72(r3) + stw r19,76(r3) + stw r20,80(r3) + stw r21,84(r3) + stw r22,88(r3) + stw r23,92(r3) + stw r24,96(r3) + stw r25,100(r3) + stw r26,104(r3) + stw r27,108(r3) + stw r28,112(r3) + stw r29,116(r3) + stw r30,120(r3) + stw r31,124(r3) + + lwz r0,4(r4) + lwz r1,0(r4) + lwz r2,8(r4) + lwz r3,12(r4) + lwz r5,20(r4) + lwz r6,24(r4) + lwz r7,28(r4) + lwz r8,32(r4) + lwz r9,36(r4) + lwz r10,40(r4) + lwz r11,44(r4) + lwz r12,48(r4) + lwz r13,52(r4) + lwz r14,56(r4) + lwz r15,60(r4) + lwz r16,64(r4) + lwz r17,68(r4) + lwz r18,72(r4) + lwz r19,76(r4) + lwz r20,80(r4) + lwz r21,84(r4) + lwz r22,88(r4) + lwz r23,92(r4) + lwz r24,96(r4) + lwz r25,100(r4) + lwz r26,104(r4) + lwz r27,108(r4) + lwz r28,112(r4) + lwz r29,116(r4) + lwz r30,120(r4) + lwz r31,124(r4) + + mtlr r0 + blr + +#endif /* HOST_PPC */ + +#ifdef HOST_SNAKE + + ;rp = r2, sp = r30 + ;arg0 = r26, arg1 = r25, arg2 = r24, arg3 = r23 + + .SPACE $TEXT$ + .SUBSPA $CODE$ +ThreadRoot + .PROC + .CALLINFO CALLER,FRAME=0 + .ENTRY + + .CALL + ble 0(%r6) ;call StartupPC + or %r31, 0, %rp ;put return address in proper register + or %r4, 0, %arg0 ;load InitialArg + .CALL ;in=26 + ble 0(%r3) ;call InitialPC + or %r31, 0, %rp ;put return address in proper register + .CALL + ble 0(%r5) ;call WhenDonePC + .EXIT + or %r31, 0, %rp ;shouldn't really matter - doesn't return + + .PROCEND + + +SWITCH + .PROC + .CALLINFO CALLER,FRAME=0 + .ENTRY + + ; save process state of oldThread + stw %sp, SP(%arg0) ;save stack pointer + stw %r3, S0(%arg0) ;save callee-save registers + stw %r4, S1(%arg0) + stw %r5, S2(%arg0) + stw %r6, S3(%arg0) + stw %r7, S4(%arg0) + stw %r8, S5(%arg0) + stw %r9, S6(%arg0) + stw %r10, S7(%arg0) + stw %r11, S8(%arg0) + stw %r12, S9(%arg0) + stw %r13, S10(%arg0) + stw %r14, S11(%arg0) + stw %r15, S12(%arg0) + stw %r16, S13(%arg0) + stw %r17, S14(%arg0) + stw %r18, S15(%arg0) + stw %rp, PC(%arg0) ;save program counter + + ; restore process state of nextThread + ldw SP(%arg1), %sp ;restore stack pointer + ldw S0(%arg1), %r3 ;restore callee-save registers + ldw S1(%arg1), %r4 + ldw S2(%arg1), %r5 + ldw S3(%arg1), %r6 + ldw S4(%arg1), %r7 + ldw S5(%arg1), %r8 + ldw S6(%arg1), %r9 + ldw S7(%arg1), %r10 + ldw S8(%arg1), %r11 + ldw S9(%arg1), %r12 + ldw S10(%arg1), %r13 + ldw S11(%arg1), %r14 + ldw S12(%arg1), %r15 + ldw S13(%arg1), %r16 + ldw S14(%arg1), %r17 + ldw PC(%arg1), %rp ;save program counter + bv 0(%rp) + .EXIT + ldw S15(%arg1), %r18 + + .PROCEND + + .EXPORT SWITCH,ENTRY,PRIV_LEV=3,RTNVAL=GR + .EXPORT ThreadRoot,ENTRY,PRIV_LEV=3,RTNVAL=GR + +#endif + +#ifdef HOST_i386 + + .text + .align 2 + +/* void ThreadRoot( void ) +** +** expects the following registers to be initialized: +** eax points to startup function (interrupt enable) +** edx contains inital argument to thread function +** esi points to thread function +** edi point to Thread::Finish() +*/ + + .globl ThreadRoot +ThreadRoot: + .cfi_startproc + .cfi_undefined rip + xorl %ebp,%ebp + pushl InitialArg + call *StartupPC + call *InitialPC + call *WhenDonePC + + /* NOT REACHED*/ + movl %ebp,%esp + popl %ebp + ret + .cfi_endproc + + + +/* void SWITCH( thread *t1, thread *t2 ) +** +** on entry, stack looks like this: +** 8(esp) -> thread *t2 +** 4(esp) -> thread *t1 +** (esp) -> return address +** +** we push the current eax on the stack so that we can use it as +** a pointer to t1, this decrements esp by 4, so when we use it +** to reference stuff on the stack, we add 4 to the offset. +*/ + .comm _eax_save,4 + + .globl SWITCH +SWITCH: + + movl %eax,_eax_save /* save the value of eax */ + movl 4(%esp),%eax /* move pointer to t1 into eax */ + movl %ebx,_EBX(%eax) /* save registers */ + movl %ecx,_ECX(%eax) + movl %edx,_EDX(%eax) + movl %esi,_ESI(%eax) + movl %edi,_EDI(%eax) + movl %ebp,_EBP(%eax) + movl %esp,_ESP(%eax) /* save stack pointer */ + movl _eax_save,%ebx /* get the saved value of eax */ + movl %ebx,_EAX(%eax) /* store it */ + movl 0(%esp),%ebx /* get return address from stack into ebx */ + movl %ebx,_PC(%eax) /* save it into the pc storage */ + + movl 8(%esp),%eax /* move pointer to t2 into eax */ + + movl _EAX(%eax),%ebx /* get new value for eax into ebx */ + movl %ebx,_eax_save /* save it */ + movl _EBX(%eax),%ebx /* retore old registers */ + movl _ECX(%eax),%ecx + movl _EDX(%eax),%edx + movl _ESI(%eax),%esi + movl _EDI(%eax),%edi + movl _EBP(%eax),%ebp + movl _ESP(%eax),%esp /* restore stack pointer */ + movl _PC(%eax),%eax /* restore return address into eax */ + movl %eax,0(%esp) /* copy over the ret address on the stack */ + movl _eax_save,%eax + + ret + +#endif + +#ifdef HOST_x86_64 + + .text + .align 2 + +/* void ThreadRoot( void ) +** +** expects the following registers to be initialized: +** rcx points to startup function (interrupt enable) +** rdx contains inital argument to thread function +** rsi points to thread function +** rdi point to Thread::Finish() +*/ + + .globl ThreadRoot +ThreadRoot: + .cfi_startproc + .cfi_undefined rip + xorq %rbp,%rbp + subq $8,%rsp /* Alignment for SSE */ + pushq WhenDonePC + pushq InitialPC + pushq InitialArg + call *StartupPC + movq (%rsp),%rdi + movq 8(%rsp),InitialPC + call *InitialPC + movq 16(%rsp),WhenDonePC + call *WhenDonePC + + /* NOT REACHED*/ + popq InitialArg + popq InitialPC + popq WhenDonePC + addq $8, %rsp + movq %rbp,%rsp + popq %rbp + ret + .cfi_endproc + + + +/* void SWITCH( thread *t1, thread *t2 ) +** +** on entry, stack looks like this: +** (rsp) -> return address +** +** rdi contains thread *t1 +** rsi contains thread *t2 +** +** we push the current rax on the stack so that we can use it as +** a pointer to t1, this decrements rsp by 8, so when we use it +** to reference stuff on the stack, we add 8 to the offset. +*/ + .comm _rax_save,8 + + .globl SWITCH +SWITCH: + + movq %rax,_rax_save(%rip) /* save the value of rax */ + movq %rdi,%rax /* move pointer to t1 into rax */ + movq %rbx,_RBX(%rax) /* save registers */ + movq %rcx,_RCX(%rax) + movq %rdx,_RDX(%rax) + movq %rsi,_RSI(%rax) + movq %rdi,_RDI(%rax) + movq %r12,_R12(%rax) + movq %r13,_R13(%rax) + movq %r14,_R14(%rax) + movq %r15,_R15(%rax) + movq %rbp,_RBP(%rax) + movq %rsp,_RSP(%rax) /* save stack pointer */ + movq _rax_save(%rip),%rbx /* get the saved value of rax */ + movq %rbx,_RAX(%rax) /* store it */ + movq 0(%rsp),%rbx /* get return address from stack into rbx */ + movq %rbx,_PC(%rax) /* save it into the pc storage */ + + movq %rsi,%rax /* move pointer to t2 into rax */ + + movq _RAX(%rax),%rbx /* get new value for rax into rbx */ + movq %rbx,_rax_save(%rip) /* save it */ + movq _RBX(%rax),%rbx /* retore old registers */ + movq _RCX(%rax),%rcx + movq _RDX(%rax),%rdx + movq _RSI(%rax),%rsi + movq _RDI(%rax),%rdi + movq _R12(%rax),%r12 + movq _R13(%rax),%r13 + movq _R14(%rax),%r14 + movq _R15(%rax),%r15 + movq _RBP(%rax),%rbp + movq _RSP(%rax),%rsp /* restore stack pointer */ + movq _PC(%rax),%rax /* restore return address into rax */ + movq %rax,0(%rsp) /* copy over the ret address on the stack */ + movq _rax_save(%rip),%rax + + ret + +#endif diff --git a/code/threads/switch.h b/code/threads/switch.h new file mode 100644 index 0000000..bbf9a55 --- /dev/null +++ b/code/threads/switch.h @@ -0,0 +1,223 @@ +/* switch.h + * Definitions needed for implementing context switching. + * + * Context switching is inherently machine dependent, since + * the registers to be saved, how to set up an initial + * call frame, etc, are all specific to a processor architecture. + * + * This file currently supports the DEC MIPS and SUN SPARC architectures. + */ + +/* + Copyright (c) 1992-1993 The Regents of the University of California. + All rights reserved. See copyright.h for copyright notice and limitation + of liability and disclaimer of warranty provisions. + */ + +#ifndef SWITCH_H +#define SWITCH_H + +#include "copyright.h" + +#ifdef HOST_MIPS + +/* Registers that must be saved during a context switch. + * These are the offsets from the beginning of the Thread object, + * in bytes, used in switch.s + */ +#define SP 0 +#define S0 4 +#define S1 8 +#define S2 12 +#define S3 16 +#define S4 20 +#define S5 24 +#define S6 28 +#define S7 32 +#define FP 36 +#define PC 40 + +/* To fork a thread, we set up its saved register state, so that + * when we switch to the thread, it will start running in ThreadRoot. + * + * The following are the initial registers we need to set up to + * pass values into ThreadRoot (for instance, containing the procedure + * for the thread to run). The first set is the registers as used + * by ThreadRoot; the second set is the locations for these initial + * values in the Thread object -- used in Thread::AllocateStack(). + */ + +#define InitialPC s0 +#define InitialArg s1 +#define WhenDonePC s2 +#define StartupPC s3 + +#define PCState (PC/4-1) +#define FPState (FP/4-1) +#define InitialPCState (S0/4-1) +#define InitialArgState (S1/4-1) +#define WhenDonePCState (S2/4-1) +#define StartupPCState (S3/4-1) + +#endif // HOST_MIPS + +#ifdef HOST_SPARC + +/* Registers that must be saved during a context switch. See comment above. */ +#define I0 4 +#define I1 8 +#define I2 12 +#define I3 16 +#define I4 20 +#define I5 24 +#define I6 28 +#define I7 32 + +/* Aliases used for clearing code. */ +#define FP I6 +#define PC I7 + +/* Registers for ThreadRoot. See comment above. */ +#define InitialPC %o0 +#define InitialArg %o1 +#define WhenDonePC %o2 +#define StartupPC %o3 + +#define PCState (PC/4-1) +#define InitialPCState (I0/4-1) +#define InitialArgState (I1/4-1) +#define WhenDonePCState (I2/4-1) +#define StartupPCState (I3/4-1) +#endif // HOST_SPARC + +#ifdef HOST_SNAKE + +/* Registers that must be saved during a context switch. See comment above. */ +#define SP 0 +#define S0 4 +#define S1 8 +#define S2 12 +#define S3 16 +#define S4 20 +#define S5 24 +#define S6 28 +#define S7 32 +#define S8 36 +#define S9 40 +#define S10 44 +#define S11 48 +#define S12 52 +#define S13 56 +#define S14 60 +#define S15 64 +#define PC 68 + +/* Registers for ThreadRoot. See comment above. */ +#define InitialPC %r3 /* S0 */ +#define InitialArg %r4 +#define WhenDonePC %r5 +#define StartupPC %r6 + +#define PCState (PC/4-1) +#define InitialPCState (S0/4-1) +#define InitialArgState (S1/4-1) +#define WhenDonePCState (S2/4-1) +#define StartupPCState (S3/4-1) +#endif // HOST_SNAKE + +#ifdef HOST_PPC + +#define R0 4 +#define R1 0 // SP must be the first field of the Thread struct! +#define R2 8 +#define R3 12 +#define R4 16 +#define R5 20 +#define R6 24 +#define R7 28 +#define R8 32 +#define R9 36 +#define R10 40 +#define R11 44 +#define R12 48 +#define R13 52 +#define R14 56 +#define R15 60 +#define R16 64 +#define R17 68 +#define R18 72 + +/* Registers for ThreadRoot. See comment above. */ +#define InitialPC %r5 +#define InitialArg %r6 +#define WhenDonePC %r7 +#define StartupPC %r8 + +/* WARNING: SP is not part of machineState! */ +#define PCState 0 +#define InitialPCState 4 +#define InitialArgState 5 +#define WhenDonePCState 6 +#define StartupPCState 7 + +#endif // HOST_PPC + +#ifdef HOST_i386 + +/* the offsets of the registers from the beginning of the thread object */ +#define _ESP 0 +#define _EAX 4 +#define _EBX 8 +#define _ECX 12 +#define _EDX 16 +#define _EBP 20 +#define _ESI 24 +#define _EDI 28 +#define _PC 32 + +/* These definitions are used in Thread::AllocateStack(). */ +#define PCState (_PC/4-1) +#define FPState (_EBP/4-1) +#define InitialPCState (_ESI/4-1) +#define InitialArgState (_EDX/4-1) +#define WhenDonePCState (_EDI/4-1) +#define StartupPCState (_ECX/4-1) + +#define InitialPC %esi +#define InitialArg %edx +#define WhenDonePC %edi +#define StartupPC %ecx +#endif + +#ifdef HOST_x86_64 + +/* the offsets of the registers from the beginning of the thread object */ +#define _RSP 0 +#define _RAX 8 +#define _RBX 16 +#define _RCX 24 +#define _RDX 32 +#define _RBP 40 +#define _RSI 48 +#define _RDI 56 +#define _R12 64 +#define _R13 72 +#define _R14 80 +#define _R15 88 +#define _PC 96 + +/* These definitions are used in Thread::AllocateStack(). */ +#define PCState (_PC/8-1) +#define FPState (_RBP/8-1) +#define InitialPCState (_RSI/8-1) +#define InitialArgState (_RDX/8-1) +#define WhenDonePCState (_RDI/8-1) +#define StartupPCState (_RCX/8-1) + +#define InitialPC %rsi +#define InitialArg %rdx +#define WhenDonePC %rdi +#define StartupPC %rcx +#endif + +#endif // SWITCH_H diff --git a/code/threads/synch.cc b/code/threads/synch.cc new file mode 100644 index 0000000..9d91ed0 --- /dev/null +++ b/code/threads/synch.cc @@ -0,0 +1,164 @@ +// synch.cc +// Routines for synchronizing threads. Three kinds of +// synchronization routines are defined here: semaphores, locks +// and condition variables (the implementation of the last two +// are left to the reader). +// +// Any implementation of a synchronization routine needs some +// primitive atomic operation. We assume Nachos is running on +// a uniprocessor, and thus atomicity can be provided by +// turning off interrupts. While interrupts are disabled, no +// context switch can occur, and thus the current thread is guaranteed +// to hold the CPU throughout, until interrupts are reenabled. +// +// Because some of these routines might be called with interrupts +// already disabled (Semaphore::V for one), instead of turning +// on interrupts at the end of the atomic operation, we always simply +// re-set the interrupt state back to its original value (whether +// that be disabled or enabled). +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "synch.h" +#include "system.h" + +//---------------------------------------------------------------------- +// Semaphore::Semaphore +// Initialize a semaphore, so that it can be used for synchronization. +// +// "debugName" is an arbitrary name, useful for debugging. +// "initialValue" is the initial value of the semaphore. +//---------------------------------------------------------------------- + +Semaphore::Semaphore (const char *debugName, int initialValue) +{ + name = debugName; + value = initialValue; + queue = new List; +} + +//---------------------------------------------------------------------- +// Semaphore::Semaphore +// De-allocate semaphore, when no longer needed. Assume no one +// is still waiting on the semaphore! +//---------------------------------------------------------------------- + +Semaphore::~Semaphore () +{ + delete queue; + queue = NULL; + value = -1; +} + +//---------------------------------------------------------------------- +// Semaphore::P +// Wait until semaphore value > 0, then decrement. Checking the +// value and decrementing must be done atomically, so we +// need to disable interrupts before checking the value. +// +// Note that Thread::Sleep assumes that interrupts are disabled +// when it is called. +//---------------------------------------------------------------------- + +void +Semaphore::P () +{ + IntStatus oldLevel = interrupt->SetLevel (IntOff); // disable interrupts + + ASSERT(value >= 0); + + while (value == 0) + { // semaphore not available + queue->Append ((void *) currentThread); // so go to sleep + currentThread->Sleep (); + } + value--; // semaphore available, + // consume its value + + (void) interrupt->SetLevel (oldLevel); // re-enable interrupts +} + +//---------------------------------------------------------------------- +// Semaphore::V +// Increment semaphore value, waking up a waiter if necessary. +// As with P(), this operation must be atomic, so we need to disable +// interrupts. Scheduler::ReadyToRun() assumes that threads +// are disabled when it is called. +//---------------------------------------------------------------------- + +void +Semaphore::V () +{ + Thread *thread; + IntStatus oldLevel = interrupt->SetLevel (IntOff); + + ASSERT(value >= 0); + + thread = (Thread *) queue->Remove (); + if (thread != NULL) // make thread ready, consuming the V immediately + scheduler->ReadyToRun (thread); + value++; + (void) interrupt->SetLevel (oldLevel); +} + +// Dummy functions -- so we can compile our later assignments +// Note -- without a correct implementation of Condition::Wait(), +// the test case in the network assignment won't work! +Lock::Lock (const char *debugName) +{ + (void) debugName; + /* TODO */ + ASSERT(FALSE); +} + +Lock::~Lock () +{ +} +void +Lock::Acquire () +{ + /* TODO */ + ASSERT(FALSE); +} +void +Lock::Release () +{ + /* TODO */ + ASSERT(FALSE); +} + +Condition::Condition (const char *debugName) +{ + (void) debugName; + /* TODO */ + ASSERT(FALSE); +} + +Condition::~Condition () +{ +} +void +Condition::Wait (Lock * conditionLock) +{ + (void) conditionLock; + /* TODO */ + ASSERT (FALSE); +} + +void +Condition::Signal (Lock * conditionLock) +{ + (void) conditionLock; + /* TODO */ + ASSERT(FALSE); +} +void +Condition::Broadcast (Lock * conditionLock) +{ + (void) conditionLock; + /* TODO */ + ASSERT(FALSE); +} diff --git a/code/threads/synch.h b/code/threads/synch.h new file mode 100644 index 0000000..deb7878 --- /dev/null +++ b/code/threads/synch.h @@ -0,0 +1,148 @@ +// synch.h +// Data structures for synchronizing threads. +// +// Three kinds of synchronization are defined here: semaphores, +// locks, and condition variables. The implementation for +// semaphores is given; for the latter two, only the procedure +// interface is given -- they are to be implemented as part of +// the first assignment. +// +// Note that all the synchronization objects take a "name" as +// part of the initialization. This is solely for debugging purposes. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// synch.h -- synchronization primitives. + +#ifndef SYNCH_H +#define SYNCH_H + +#include "copyright.h" +#include "thread.h" +#include "list.h" + +// The following class defines a "semaphore" whose value is a non-negative +// integer. The semaphore has only two operations P() and V(): +// +// P() -- waits until value > 0, then decrement +// +// V() -- increment, waking up a thread waiting in P() if necessary +// +// Note that the interface does *not* allow a thread to read the value of +// the semaphore directly -- even if you did read the value, the +// only thing you would know is what the value used to be. You don't +// know what the value is now, because by the time you get the value +// into a register, a context switch might have occurred, +// and some other thread might have called P or V, so the true value might +// now be different. + +class Semaphore:public dontcopythis +{ + public: + Semaphore (const char *debugName, int initialValue); // set initial value + ~Semaphore (); // de-allocate semaphore + const char *getName () + { + return name; + } // debugging assist + + void P (); // these are the only operations on a semaphore + void V (); // they are both *atomic* + + private: + const char *name; // useful for debugging + int value; // semaphore value, always >= 0 + List *queue; // threads waiting in P() for the value to be > 0 +}; + +// The following class defines a "lock". A lock can be BUSY or FREE. +// There are only two operations allowed on a lock: +// +// Acquire -- wait until the lock is FREE, then set it to BUSY +// +// Release -- set lock to be FREE, waking up a thread waiting +// in Acquire if necessary +// +// In addition, by convention, only the thread that acquired the lock +// may release it. As with semaphores, you can't read the lock value +// (because the value might change immediately after you read it). + +class Lock:public dontcopythis +{ + public: + Lock (const char *debugName); // initialize lock to be FREE + ~Lock (); // deallocate lock + const char *getName () + { + return name; + } // debugging assist + + void Acquire (); // these are the only operations on a lock + void Release (); // they are both *atomic* + + bool isHeldByCurrentThread (); // true if the current thread + // holds this lock. Useful for + // checking in Release, and in + // Condition variable ops below. + + private: + const char *name; // for debugging + // plus some other stuff you'll need to define +}; + +// The following class defines a "condition variable". A condition +// variable does not have a value, but threads may be queued, waiting +// on the variable. These are only operations on a condition variable: +// +// Wait() -- release the lock, relinquish the CPU until signaled, +// then re-acquire the lock +// +// Signal() -- wake up a thread, if there are any waiting on +// the condition +// +// Broadcast() -- wake up all threads waiting on the condition +// +// All operations on a condition variable must be made while +// the current thread has acquired a lock. Indeed, all accesses +// to a given condition variable must be protected by the same lock. +// In other words, mutual exclusion must be enforced among threads calling +// the condition variable operations. +// +// In Nachos, condition variables are assumed to obey *Mesa*-style +// semantics. When a Signal or Broadcast wakes up another thread, +// it simply puts the thread on the ready list, and it is the responsibility +// of the woken thread to re-acquire the lock (this re-acquire is +// taken care of within Wait()). By contrast, some define condition +// variables according to *Hoare*-style semantics -- where the signalling +// thread gives up control over the lock and the CPU to the woken thread, +// which runs immediately and gives back control over the lock to the +// signaller when the woken thread leaves the critical section. +// +// The consequence of using Mesa-style semantics is that some other thread +// can acquire the lock, and change data structures, before the woken +// thread gets a chance to run. + +class Condition:public dontcopythis +{ + public: + Condition (const char *debugName); // initialize condition to + // "no one waiting" + ~Condition (); // deallocate the condition + const char *getName () + { + return (name); + } + + void Wait (Lock * conditionLock); // these are the 3 operations on + // condition variables; releasing the + // lock and going to sleep are + // *atomic* in Wait() + void Signal (Lock * conditionLock); // conditionLock must be held by + void Broadcast (Lock * conditionLock); // the currentThread for all of + // these operations + + private: + const char *name; + // plus some other stuff you'll need to define +}; +#endif // SYNCH_H diff --git a/code/threads/synchlist.cc b/code/threads/synchlist.cc new file mode 100644 index 0000000..2ce667a --- /dev/null +++ b/code/threads/synchlist.cc @@ -0,0 +1,101 @@ +// synchlist.cc +// Routines for synchronized access to a list. +// +// Implemented by surrounding the List abstraction +// with synchronization routines. +// +// Implemented in "monitor"-style -- surround each procedure with a +// lock acquire and release pair, using condition signal and wait for +// synchronization. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "synchlist.h" + +//---------------------------------------------------------------------- +// SynchList::SynchList +// Allocate and initialize the data structures needed for a +// synchronized list, empty to start with. +// Elements can now be added to the list. +//---------------------------------------------------------------------- + +SynchList::SynchList () +{ + list = new List (); + lock = new Lock ("list lock"); + listEmpty = new Condition ("list empty cond"); +} + +//---------------------------------------------------------------------- +// SynchList::~SynchList +// De-allocate the data structures created for synchronizing a list. +//---------------------------------------------------------------------- + +SynchList::~SynchList () +{ + delete list; + list = NULL; + delete lock; + lock = NULL; + delete listEmpty; + listEmpty = NULL; +} + +//---------------------------------------------------------------------- +// SynchList::Append +// Append an "item" to the end of the list. Wake up anyone +// waiting for an element to be appended. +// +// "item" is the thing to put on the list, it can be a pointer to +// anything. +//---------------------------------------------------------------------- + +void +SynchList::Append (void *item) +{ + lock->Acquire (); // enforce mutual exclusive access to the list + list->Append (item); + listEmpty->Signal (lock); // wake up a waiter, if any + lock->Release (); +} + +//---------------------------------------------------------------------- +// SynchList::Remove +// Remove an "item" from the beginning of the list. Wait if +// the list is empty. +// Returns: +// The removed item. +//---------------------------------------------------------------------- + +void * +SynchList::Remove () +{ + void *item; + + lock->Acquire (); // enforce mutual exclusion + while (list->IsEmpty ()) + listEmpty->Wait (lock); // wait until list isn't empty + item = list->Remove (); + ASSERT (item != NULL); + lock->Release (); + return item; +} + +//---------------------------------------------------------------------- +// SynchList::Mapcar +// Apply function to every item on the list. Obey mutual exclusion +// constraints. +// +// "func" is the procedure to be applied. +//---------------------------------------------------------------------- + +void +SynchList::Mapcar (VoidFunctionPtr func) +{ + lock->Acquire (); + list->Mapcar (func); + lock->Release (); +} diff --git a/code/threads/synchlist.h b/code/threads/synchlist.h new file mode 100644 index 0000000..bb17123 --- /dev/null +++ b/code/threads/synchlist.h @@ -0,0 +1,43 @@ +// synchlist.h +// Data structures for synchronized access to a list. +// +// Implemented by surrounding the List abstraction +// with synchronization routines. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef SYNCHLIST_H +#define SYNCHLIST_H + +#include "copyright.h" +#include "list.h" +#include "synch.h" + +// The following class defines a "synchronized list" -- a list for which: +// these constraints hold: +// 1. Threads trying to remove an item from a list will +// wait until the list has an element on it. +// 2. One thread at a time can access list data structures + +class SynchList:public dontcopythis +{ + public: + SynchList (); // initialize a synchronized list + ~SynchList (); // de-allocate a synchronized list + + void Append (void *item); // append item to the end of the list, + // and wake up any thread waiting in remove + void *Remove (); // remove the first item from the front of + // the list, waiting if the list is empty + // apply function to every item in the list + void Mapcar (VoidFunctionPtr func); + + private: + List * list; // the unsynchronized list + Lock *lock; // enforce mutual exclusive access to the list + Condition *listEmpty; // wait in Remove if the list is empty +}; + +#endif // SYNCHLIST_H diff --git a/code/threads/system.cc b/code/threads/system.cc new file mode 100644 index 0000000..ef3da5f --- /dev/null +++ b/code/threads/system.cc @@ -0,0 +1,269 @@ +// system.cc +// Nachos initialization and cleanup routines. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "system.h" +#include +#ifdef __GLIBC__ +#include +#endif + +// This defines *all* of the global data structures used by Nachos. +// These are all initialized and de-allocated by this file. + +Thread *currentThread; // the thread we are running now +Thread *threadToBeDestroyed; // the thread that just finished +Scheduler *scheduler; // the ready list +Interrupt *interrupt; // interrupt status +Statistics *stats; // performance metrics +Timer *timer; // the hardware timer device, + // for invoking context switches + +#ifdef FILESYS_NEEDED +FileSystem *fileSystem; +#endif + +#ifdef FILESYS +SynchDisk *synchDisk; +#endif + +#ifdef USER_PROGRAM // requires either FILESYS or FILESYS_STUB +Machine *machine; // user program memory and registers +#endif + +#ifdef NETWORK +PostOffice *postOffice; +#endif + + +// External definition, to allow us to take a pointer to this function +extern void Cleanup (); + + +//---------------------------------------------------------------------- +// TimerInterruptHandler +// Interrupt handler for the timer device. The timer device is +// set up to interrupt the CPU periodically (once every TimerTicks). +// This routine is called each time there is a timer interrupt, +// with interrupts disabled. +// +// Note that instead of calling Yield() directly (which would +// suspend the interrupt handler, not the interrupted thread +// which is what we wanted to context switch), we set a flag +// so that once the interrupt handler is done, it will appear as +// if the interrupted thread called Yield at the point it is +// was interrupted. +// +// "dummy" is because every interrupt handler takes one argument, +// whether it needs it or not. +//---------------------------------------------------------------------- +static void +TimerInterruptHandler (void *dummy) +{ + (void) dummy; + if (interrupt->getStatus () != IdleMode) + interrupt->YieldOnReturn (); +} + +//---------------------------------------------------------------------- +// Initialize +// Initialize Nachos global data structures. Interpret command +// line arguments in order to determine flags for the initialization. +// +// "argc" is the number of command line arguments (including the name +// of the command) -- ex: "nachos -d +" -> argc = 3 +// "argv" is an array of strings, one for each command line argument +// ex: "nachos -d +" -> argv = {"nachos", "-d", "+"} +//---------------------------------------------------------------------- +void +Initialize (int argc, char **argv) +{ + int argCount; + const char *debugArgs = ""; + bool randomYield = FALSE; + +#ifdef USER_PROGRAM + bool debugUserProg = FALSE; // single step user program +#endif +#ifdef FILESYS_NEEDED + bool format = FALSE; // format disk +#endif +#ifdef NETWORK + double rely = 1; // network reliability + int netname = 0; // UNIX socket name +#endif + + setlocale(LC_CTYPE,""); + +#ifdef __GLIBC__ +#if (__GLIBC__ > 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 4) + /* Make free() fill freed memory, to trap bad code */ + mallopt(M_PERTURB, 0xfefefefe); +#endif +#endif + ThrashStack(); + + for (argc--, argv++; argc > 0; argc -= argCount, argv += argCount) + { + argCount = 1; + if (!strcmp (*argv, "-d")) + { + if (argc == 1) + debugArgs = "+"; // turn on all debug flags + else + { + debugArgs = *(argv + 1); + argCount = 2; + } + } + else if (!strcmp (*argv, "-rs")) + { + int seed; + ASSERT (argc > 1); + seed = atoi (*(argv + 1)); + if (seed == 0) + { + fprintf(stderr,"-rs option needs a seed value\n"); + exit(EXIT_FAILURE); + } + RandomInit (seed); // initialize pseudo-random + // number generator + randomYield = TRUE; + argCount = 2; + } +#ifdef USER_PROGRAM + if (!strcmp (*argv, "-s")) + debugUserProg = TRUE; +#endif +#ifdef FILESYS_NEEDED + if (!strcmp (*argv, "-f")) + format = TRUE; +#endif +#ifdef NETWORK + if (!strcmp (*argv, "-l")) + { + ASSERT (argc > 1); + rely = atof (*(argv + 1)); + argCount = 2; + } + else if (!strcmp (*argv, "-m")) + { + ASSERT (argc > 1); + netname = atoi (*(argv + 1)); + argCount = 2; + } +#endif + } + + DebugInit (debugArgs); // initialize DEBUG messages + stats = new Statistics (); // collect statistics + interrupt = new Interrupt; // start up interrupt handling + scheduler = new Scheduler (); // initialize the ready queue + if (randomYield) // start the timer (if needed) + timer = new Timer (TimerInterruptHandler, 0, randomYield); + + threadToBeDestroyed = NULL; + + // We didn't explicitly allocate the current thread we are running in. + // But if it ever tries to give up the CPU, we better have a Thread + // object to save its state. + currentThread = new Thread ("main"); + currentThread->SetMain (); + + interrupt->Enable (); + CallOnUserAbort (Cleanup); // if user hits ctl-C + +#ifdef USER_PROGRAM + machine = new Machine (debugUserProg); // this must come first +#endif + +#ifdef FILESYS + synchDisk = new SynchDisk ("DISK"); +#endif + +#ifdef FILESYS_NEEDED + fileSystem = new FileSystem (format); +#endif + +#ifdef NETWORK + postOffice = new PostOffice (netname, rely, 10); +#endif +} + +//---------------------------------------------------------------------- +// Cleanup +// Nachos is halting. De-allocate global data structures. +//---------------------------------------------------------------------- +void +Cleanup () +{ + static int cleaning; + + if (cleaning) { + printf ("\nCtrl-C while cleaning, stopping here hard.\n"); + Exit (0); + } + cleaning = 1; + + printf ("\nCleaning up...\n"); + + /* Allow more interrupts but prevent other threads from continuing to use + * the system while we are waiting for the last interrupts */ + if (scheduler) + scheduler->Stop(); + if (interrupt) + interrupt->Enable(); + +#ifdef NETWORK + if (postOffice) { + delete postOffice; + postOffice = NULL; + } +#endif + +#ifdef USER_PROGRAM + if (machine) { + delete machine; + machine = NULL; + } +#endif + +#ifdef FILESYS_NEEDED + if (fileSystem) { + delete fileSystem; + fileSystem = NULL; + } +#endif + +#ifdef FILESYS + if (synchDisk) { + delete synchDisk; + synchDisk = NULL; + } +#endif + + if (timer) { + delete timer; + timer = NULL; + } + if (scheduler) { + delete scheduler; + scheduler = NULL; + } + if (interrupt) { + delete interrupt; + interrupt = NULL; + } + if (stats) { + delete stats; + stats = NULL; + } + + ThreadList.Remove(currentThread); + + Exit (0); +} diff --git a/code/threads/system.h b/code/threads/system.h new file mode 100644 index 0000000..3cb2097 --- /dev/null +++ b/code/threads/system.h @@ -0,0 +1,52 @@ +// system.h +// All global variables used in Nachos are defined here. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef SYSTEM_H +#define SYSTEM_H + +#include "copyright.h" +#include "utility.h" +#include "thread.h" +#include "scheduler.h" +#include "interrupt.h" +#include "stats.h" +#include "timer.h" + +// Initialization and cleanup routines +extern void Initialize (int argc, char **argv); // Initialization, + // called before anything else +extern void Cleanup (); // Cleanup, called when + // Nachos is done. + +extern Thread *currentThread; // the thread holding the CPU +extern Thread *threadToBeDestroyed; // the thread that just finished +extern Scheduler *scheduler; // the ready list +extern Interrupt *interrupt; // interrupt status +extern Statistics *stats; // performance metrics +extern Timer *timer; // the hardware alarm clock + +#ifdef USER_PROGRAM +#include "machine.h" +extern Machine *machine; // user program memory and registers +#endif + +#ifdef FILESYS_NEEDED // FILESYS or FILESYS_STUB +#include "filesys.h" +extern FileSystem *fileSystem; +#endif + +#ifdef FILESYS +#include "synchdisk.h" +extern SynchDisk *synchDisk; +#endif + +#ifdef NETWORK +#include "post.h" +extern PostOffice *postOffice; +#endif + +#endif // SYSTEM_H diff --git a/code/threads/thread.cc b/code/threads/thread.cc new file mode 100644 index 0000000..82ced0f --- /dev/null +++ b/code/threads/thread.cc @@ -0,0 +1,533 @@ +// thread.cc +// Routines to manage threads. There are four main operations: +// +// Start -- create a thread to run a procedure concurrently +// with the caller (this is done in two steps -- first +// allocate the Thread object, then call Start on it) +// Finish -- called when the forked procedure finishes, to clean up +// Yield -- relinquish control over the CPU to another ready thread +// Sleep -- relinquish control over the CPU, but thread is now blocked. +// In other words, it will not run again, until explicitly +// put back on the ready queue. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "thread.h" +#include "switch.h" +#include "synch.h" +#include "system.h" +#include "valgrind.h" +#ifdef USER_PROGRAM +#include "machine.h" +#endif +#ifdef __SANITIZE_ADDRESS__ +#include +#include +#endif + + +#define STACK_FENCEPOST 0xdeadbeef // this is put at the top of the + // execution stack, for detecting + // stack overflows + +//---------------------------------------------------------------------- +// ThreadList +// List of all threads, for debugging +//---------------------------------------------------------------------- +List ThreadList; + +//---------------------------------------------------------------------- +// Thread::Thread +// Initialize a thread control block, so that we can then call +// Thread::Start. +// +// "threadName" is an arbitrary string, useful for debugging. +//---------------------------------------------------------------------- + +Thread::Thread (const char *threadName) +{ + name = threadName; + stackTop = NULL; + stack = NULL; + + valgrind_id = 0; + +#ifdef __SANITIZE_ADDRESS__ + fake_stack = NULL; +#endif + stack_size = 0; + main_stack = 0; + + status = JUST_CREATED; +#ifdef USER_PROGRAM + space = NULL; + + // must be explicitly set to 0 since when Enabling interrupts, + // DelayedLoad is called !!! + userRegisters[LoadReg] = 0; + userRegisters[LoadValueReg] = 0; +#endif + ThreadList.Append(this); +} + +//---------------------------------------------------------------------- +// Thread::SetMain +// Configures the thread as representing the main thread, i.e. the thread +// initially created by the OS, with an OS-provided stack. +//---------------------------------------------------------------------- + +void +Thread::SetMain (void) +{ +#ifdef __SANITIZE_ADDRESS__ + pthread_attr_t attr; + void *addr; + + pthread_getattr_np (pthread_self (), &attr); + pthread_attr_getstack (&attr, &addr, &stack_size); + pthread_attr_destroy (&attr); + stack = (unsigned long *) addr; +#endif + + main_stack = 1; + + setStatus (RUNNING); +} + +//---------------------------------------------------------------------- +// Thread::~Thread +// De-allocate a thread. +// +// NOTE: the current thread *cannot* delete itself directly, +// since it is still running on the stack that we need to delete. +// +// NOTE: if this is the main thread, we can't delete the stack +// because we didn't allocate it -- we got it automatically +// as part of starting up Nachos. +//---------------------------------------------------------------------- + +Thread::~Thread () +{ + DEBUG ('t', "Deleting thread %p \"%s\"\n", this, name); + + ASSERT (this != currentThread); + if (!main_stack && stack != NULL) { + DeallocBoundedArray ((char *) stack, StackSize * sizeof (unsigned long)); + VALGRIND_STACK_DEREGISTER (valgrind_id); + stack = NULL; + } + ThreadList.Remove(this); +} + +//---------------------------------------------------------------------- +// ThrashStack +// Fill the stack with bogus values, to avoid getting 0 values only by luck +//---------------------------------------------------------------------- +void +ThrashStack(void) +{ + char c[StackSize]; + memset(c, 0x02, sizeof(c)); +} + +//---------------------------------------------------------------------- +// Thread::Start +// Invoke (*func)(arg), allowing caller and callee to execute +// concurrently. +// +// NOTE: although our definition allows only a single integer argument +// to be passed to the procedure, it is possible to pass multiple +// arguments by making them fields of a structure, and passing a pointer +// to the structure as "arg". +// +// Implemented as the following steps: +// 1. Allocate a stack +// 2. Initialize the stack so that a call to SWITCH will +// cause it to run the procedure +// 3. Put the thread on the ready queue +// +// "func" is the procedure to run concurrently. +// "arg" is a single argument to be passed to the procedure. +//---------------------------------------------------------------------- + +void +Thread::Start (VoidFunctionPtr func, void *arg) +{ + DEBUG ('t', "Starting thread %p \"%s\" with func = %p, arg = %p\n", + this, name, func, arg); + + ASSERT(status == JUST_CREATED); + StackAllocate (func, arg); + + IntStatus oldLevel = interrupt->SetLevel (IntOff); + scheduler->ReadyToRun (this); // ReadyToRun assumes that interrupts + // are disabled! + (void) interrupt->SetLevel (oldLevel); +} + +//---------------------------------------------------------------------- +// Thread::CheckOverflow +// Check a thread's stack to see if it has overrun the space +// that has been allocated for it. If we had a smarter compiler, +// we wouldn't need to worry about this, but we don't. +// +// NOTE: Nachos will not catch all stack overflow conditions. +// In other words, your program may still crash because of an overflow. +// +// If you get bizarre results (such as seg faults where there is no code) +// then you *may* need to increase the stack size. You can avoid stack +// overflows by not putting large data structures on the stack. +// Don't do this: void foo() { int bigArray[10000]; ... } +//---------------------------------------------------------------------- + +void +Thread::CheckOverflow () +{ + if (!main_stack && stack != NULL) +#ifdef HOST_SNAKE // Stacks grow upward on the Snakes + ASSERT (stack[StackSize - 1] == STACK_FENCEPOST); +#else + ASSERT (*stack == (unsigned long) STACK_FENCEPOST); +#endif +} + +//---------------------------------------------------------------------- +// Thread::Finish +// Called by ThreadRoot when a thread is done executing the +// forked procedure. +// +// NOTE: we don't immediately de-allocate the thread data structure +// or the execution stack, because we're still running in the thread +// and we're still on the stack! Instead, we set "threadToBeDestroyed", +// so that Scheduler::Run() will call the destructor, once we're +// running in the context of a different thread. +// +// NOTE: we disable interrupts, so that we don't get a time slice +// between setting threadToBeDestroyed, and going to sleep. +//---------------------------------------------------------------------- + +// +void +Thread::Finish () +{ + (void) interrupt->SetLevel (IntOff); + ASSERT (this == currentThread); + + DEBUG ('t', "Finishing thread %p \"%s\"\n", this, getName ()); + + // LB: Be careful to guarantee that no thread to be destroyed + // is ever lost + ASSERT (threadToBeDestroyed == NULL); + // End of addition + + threadToBeDestroyed = currentThread; + Sleep (); // invokes SWITCH + // not reached +} + +//---------------------------------------------------------------------- +// Thread::Yield +// Relinquish the CPU if any other thread is ready to run. +// If so, put the thread on the end of the ready list, so that +// it will eventually be re-scheduled. +// +// NOTE: returns immediately if no other thread on the ready queue. +// Otherwise returns when the thread eventually works its way +// to the front of the ready list and gets re-scheduled. +// +// NOTE: we disable interrupts, so that looking at the thread +// on the front of the ready list, and switching to it, can be done +// atomically. On return, we re-set the interrupt level to its +// original state, in case we are called with interrupts disabled. +// +// Similar to Thread::Sleep(), but a little different. +//---------------------------------------------------------------------- + +void +Thread::Yield () +{ + Thread *nextThread; + IntStatus oldLevel = interrupt->SetLevel (IntOff); + + ASSERT (this == currentThread); + + DEBUG ('t', "Yielding thread %p \"%s\"\n", this, getName ()); + + nextThread = scheduler->FindNextToRun (); + if (nextThread != NULL) + { + scheduler->ReadyToRun (this); + scheduler->Run (nextThread); + } + (void) interrupt->SetLevel (oldLevel); +} + +//---------------------------------------------------------------------- +// Thread::Sleep +// Relinquish the CPU, because the current thread is blocked +// waiting on a synchronization variable (Semaphore, Lock, or Condition). +// Eventually, some thread will wake this thread up, and put it +// back on the ready queue, so that it can be re-scheduled. +// +// NOTE: if there are no threads on the ready queue, that means +// we have no thread to run. "Interrupt::Idle" is called +// to signify that we should idle the CPU until the next I/O interrupt +// occurs (the only thing that could cause a thread to become +// ready to run). +// +// NOTE: we assume interrupts are already disabled, because it +// is called from the synchronization routines which must +// disable interrupts for atomicity. We need interrupts off +// so that there can't be a time slice between pulling the first thread +// off the ready list, and switching to it. +//---------------------------------------------------------------------- +void +Thread::Sleep () +{ + Thread *nextThread; + + ASSERT (this == currentThread); + ASSERT (interrupt->getLevel () == IntOff); + + DEBUG ('t', "Sleeping thread %p \"%s\"\n", this, getName ()); + + status = BLOCKED; + while ((nextThread = scheduler->FindNextToRun ()) == NULL) + interrupt->Idle (); // no one to run, wait for an interrupt + + scheduler->Run (nextThread); // returns when we've been signalled +} + +//---------------------------------------------------------------------- +// ThreadFinish, InterruptEnable, ThreadPrint +// Dummy functions because C++ does not allow a pointer to a member +// function. So in order to do this, we create a dummy C function +// (which we can pass a pointer to), that then simply calls the +// member function. +//---------------------------------------------------------------------- + +static void +ThreadFinish () +{ + currentThread->Finish (); +} +static void +InterruptEnable () +{ + interrupt->Enable (); +} + +// LB: This function has to be called on starting a new thread to set +// up the pagetable correctly. This was missing from the original +// version. Credits to Clement Menier for finding this bug! + +void +SetupThreadState () +{ +#ifdef __SANITIZE_ADDRESS__ + __sanitizer_finish_switch_fiber (currentThread->fake_stack, NULL, NULL); +#endif + + // LB: Similar to the second part of Scheduler::Run. This has to be + // done each time a thread is scheduled, either by SWITCH, or by + // getting created. + + if (threadToBeDestroyed != NULL) + { + delete threadToBeDestroyed; + threadToBeDestroyed = NULL; + } + +#ifdef USER_PROGRAM + + // LB: Now, we have to simulate the RestoreUserState/RestoreState + // part of Scheduler::Run + + // Be very careful! We have no information about the thread which is + // currently running at the time this function is called. Actually, + // there is no reason why the running thread should have the same + // pageTable as the thread just being created. + + // This is definitely the case as soon as several *processes* are + // running together. + + if (currentThread->space != NULL) + { // if there is an address space + // LB: Actually, the user state is void at that time. Keep this + // action for consistency with the Scheduler::Run function + currentThread->RestoreUserState (); // to restore, do it. + currentThread->space->RestoreState (); + } + +#endif // USER_PROGRAM + + // LB: The default level for interrupts is IntOn. + InterruptEnable (); + +} + +// End of addition + +void +ThreadPrint (void *arg) +{ + Thread *t = (Thread *) arg; + t->Print (); +} + +//---------------------------------------------------------------------- +// Thread::StackAllocate +// Allocate and initialize a kernel execution stack. The stack is +// initialized with an initial stack frame for ThreadRoot, which: +// enables interrupts +// calls (*func)(arg) +// calls Thread::Finish +// +// "func" is the procedure to be forked +// "arg" is the parameter to be passed to the procedure +//---------------------------------------------------------------------- + +void +Thread::StackAllocate (VoidFunctionPtr func, void *arg) +{ + stack = (unsigned long *) AllocBoundedArray (StackSize * sizeof (unsigned long)); + stack_size = StackSize * sizeof (unsigned long); + valgrind_id = VALGRIND_STACK_REGISTER(stack, stack + StackSize); + +#ifdef HOST_SNAKE + // HP stack works from low addresses to high addresses + stackTop = stack + 16; // HP requires 64-byte frame marker + stack[StackSize - 1] = STACK_FENCEPOST; +#else + // other archs stack works from high addresses to low addresses +#ifdef HOST_SPARC + // SPARC stack must contains at least 1 activation record to start with. + stackTop = stack + StackSize - 96; +#elif defined(HOST_PPC) + stackTop = stack + StackSize - 192; +#elif defined(HOST_i386) + stackTop = stack + StackSize - 4; // -4 for the return address +#elif defined(HOST_x86_64) + stackTop = stack + StackSize - 16; // room for the return address, and align for SSE* +#elif defined(HOST_MIPS) + stackTop = stack + StackSize; // no special need +#else +#error uh?? +#endif + *stack = STACK_FENCEPOST; +#endif // stack direction + + memset(&machineState, 0, sizeof(machineState)); + + machineState[PCState] = (unsigned long) ThreadRoot; + + // LB: It is not sufficient to enable interrupts! + // A more complex function has to be called here... + // machineState[StartupPCState] = (int) InterruptEnable; + machineState[StartupPCState] = (unsigned long) SetupThreadState; + // End of modification + + machineState[InitialPCState] = (unsigned long) func; + machineState[InitialArgState] = (unsigned long) arg; + machineState[WhenDonePCState] = (unsigned long) ThreadFinish; +} + +#ifdef USER_PROGRAM +#include "machine.h" + +//---------------------------------------------------------------------- +// Thread::SaveUserState +// Save the CPU state of a user program on a context switch. +// +// Note that a user program thread has *two* sets of CPU registers -- +// one for its state while executing user code, one for its state +// while executing kernel code. This routine saves the former. +//---------------------------------------------------------------------- + +void +Thread::SaveUserState () +{ + for (int i = 0; i < NumTotalRegs; i++) + userRegisters[i] = machine->ReadRegister (i); +} + +//---------------------------------------------------------------------- +// Thread::RestoreUserState +// Restore the CPU state of a user program on a context switch. +// +// Note that a user program thread has *two* sets of CPU registers -- +// one for its state while executing user code, one for its state +// while executing kernel code. This routine restores the former. +//---------------------------------------------------------------------- + +void +Thread::RestoreUserState () +{ + for (int i = 0; i < NumTotalRegs; i++) + machine->WriteRegister (i, userRegisters[i]); +} + +//---------------------------------------------------------------------- +// Thread::DumpThreadState +// Draw the states for this thread +//---------------------------------------------------------------------- +void +Thread::DumpThreadState(FILE *output, int ptr_x, int ptr_y, unsigned virtual_x, unsigned virtual_y, unsigned blocksize) +{ + if (this == currentThread) + machine->DumpRegs(output, ptr_x, ptr_y, virtual_x, virtual_y, blocksize); + else + { + machine->DumpReg(output, userRegisters[PCReg], "PC", "#000000", ptr_x, ptr_y, virtual_x, virtual_y, blocksize); + machine->DumpReg(output, userRegisters[StackReg], "SP", "#000000", ptr_x, ptr_y, virtual_x, virtual_y, blocksize); + } +} + +//---------------------------------------------------------------------- +// DumpThreadsState +// Draw the states for threads belonging to a given address space +//---------------------------------------------------------------------- +void +DumpThreadsState(FILE *output, AddrSpace *space, unsigned ptr_x, unsigned virtual_x, unsigned virtual_y, unsigned blocksize) +{ + ListElement *element; + ptr_x += 4*blocksize; + unsigned nthreads = ThreadList.Length(); + + unsigned y_offset; + unsigned ptr_y; + if (nthreads == 1) + { + y_offset = 0; + ptr_y = virtual_y; + } + else + { + y_offset = (blocksize/2) / (nthreads-1); + ptr_y = virtual_y - (blocksize/4); + } + + for (element = ThreadList.FirstElement(); + element; + element = element->next) + { + Thread *thread = (Thread *) element->item; + if (thread->space != space) + continue; + + thread->DumpThreadState(output, ptr_x, ptr_y, virtual_x, virtual_y, blocksize); + + // Offset names a bit on the left + ptr_x -= blocksize; + + // And offset lines a bit down + ptr_y += y_offset; + } +} + +#endif + + diff --git a/code/threads/thread.h b/code/threads/thread.h new file mode 100644 index 0000000..c78bf18 --- /dev/null +++ b/code/threads/thread.h @@ -0,0 +1,180 @@ +// thread.h +// Data structures for managing threads. A thread represents +// sequential execution of code within a program. +// So the state of a thread includes the program counter, +// the processor registers, and the execution stack. +// +// Note that because we allocate a fixed size stack for each +// thread, it is possible to overflow the stack -- for instance, +// by recursing to too deep a level. The most common reason +// for this occuring is allocating large data structures +// on the stack. For instance, this will cause problems: +// +// void foo() { int buf[1000]; ...} +// +// Instead, you should allocate all data structures dynamically: +// +// void foo() { int *buf = new int[1000]; ...} +// +// +// Bad things happen if you overflow the stack, and in the worst +// case, the problem may not be caught explicitly. Instead, +// the only symptom may be bizarre segmentation faults. (Of course, +// other problems can cause seg faults, so that isn't a sure sign +// that your thread stacks are too small.) +// +// One thing to try if you find yourself with seg faults is to +// increase the size of thread stack -- StackSize. +// +// In this interface, forking a thread takes two steps. +// We must first allocate a data structure for it: "t = new Thread". +// Only then can we do the fork: "t->fork(f, arg)". +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef THREAD_H +#define THREAD_H + +#include "copyright.h" +#include "utility.h" +#include "list.h" + +#ifdef USER_PROGRAM +#include "machine.h" +#include "addrspace.h" +#endif + +// CPU register state to be saved on context switch. +// The SPARC and MIPS only need 10 registers, but the PPC needs 32. +// For simplicity, this is just the max over all architectures. +#define MachineStateSize 32 + + +// Size of the thread's private execution stack. +// WATCH OUT IF THIS ISN'T BIG ENOUGH!!!!! +#define StackSize (8 * 1024) // in words + + +// Thread state +enum ThreadStatus +{ JUST_CREATED, RUNNING, READY, BLOCKED }; + +// external function, dummy routine whose sole job is to call Thread::Print +extern void ThreadPrint (void *arg); + +// The following class defines a "thread control block" -- which +// represents a single thread of execution. +// +// Every thread has: +// an execution stack for activation records ("stackTop" and "stack") +// space to save CPU registers while not running ("machineState") +// a "status" (running/ready/blocked) +// +// Some threads also belong to a user address space; threads +// that only run in the kernel have a NULL address space. + +class Thread:public dontcopythis +{ + private: + // NOTE: DO NOT CHANGE the order of these first two members. + // THEY MUST be in this position for SWITCH to work. + unsigned long *stackTop; // the current kernel stack pointer + unsigned long machineState[MachineStateSize]; // all kernel registers except for stackTop + + public: + Thread (const char *debugName); // initialize a Thread + void SetMain (void); // initialize Thread as main thread + ~Thread (); // deallocate a Thread + // NOTE -- thread being deleted + // must not be running when delete + // is called + + // basic thread operations + + void Start (VoidFunctionPtr func, void *arg); // Make thread run (*func)(arg) + void Yield (); // Relinquish the CPU if any + // other thread is runnable + void Sleep (); // Put the thread to sleep and + // relinquish the processor + void Finish (); // The thread is done executing + + void CheckOverflow (); // Check if thread has + // overflowed its stack + void setStatus (ThreadStatus st) + { + status = st; + } + const char *getName () + { + return (name); + } + void Print () + { + printf ("%s, ", name); + } + +#ifdef USER_PROGRAM + void DumpThreadState(FILE *output, int ptr_x, int ptr_y, unsigned virtual_x, unsigned virtual_y, unsigned blocksize); + // Draw the state for thread +#endif + + // some of the private data for this class is listed above + + unsigned long *stack; // Bottom of the stack + size_t stack_size; // Stack size + // NULL if this is the main thread + // (If NULL, don't deallocate stack) + unsigned int valgrind_id; // valgrind ID for the stack +#ifdef __SANITIZE_ADDRESS__ + void *fake_stack; // Fake stack of libasan +#endif + private: + int main_stack; // Whether this is the main stack provided by OS + ThreadStatus status; // ready, running or blocked + const char *name; + + void StackAllocate (VoidFunctionPtr func, void *arg); + // Allocate a stack for thread. + // Used internally by Start() + +#ifdef USER_PROGRAM +// A thread running a user program actually has *two* sets of CPU registers -- +// one for its state while executing user code, one for its state +// while executing kernel code. + + int userRegisters[NumTotalRegs]; // user-level CPU register state + + public: + void SaveUserState (); // save user-level register state + void RestoreUserState (); // restore user-level register state + + AddrSpace *space; // Address space this thread is running in. +#endif +}; + +void ThrashStack(void); + +extern List ThreadList; + +#ifdef USER_PROGRAM +void DumpThreadsState(FILE *output, AddrSpace *space, unsigned ptr_x, unsigned virtual_x, unsigned virtual_y, unsigned blocksize); + // Draw the states for threads +#endif + +// Magical machine-dependent routines, defined in switch.s + +extern "C" +{ +// First frame on thread execution stack; +// enable interrupts +// call "func" +// (when func returns, if ever) call ThreadFinish() + void ThreadRoot (); + +// Stop running oldThread and start running newThread + void SWITCH (Thread * oldThread, Thread * newThread); +} + +#endif // THREAD_H diff --git a/code/threads/threadtest.cc b/code/threads/threadtest.cc new file mode 100644 index 0000000..b4b4fde --- /dev/null +++ b/code/threads/threadtest.cc @@ -0,0 +1,52 @@ +// threadtest.cc +// Simple test case for the threads assignment. +// +// Create two threads, and have them context switch +// back and forth between themselves by calling Thread::Yield, +// to illustratethe inner workings of the thread system. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "system.h" + +//---------------------------------------------------------------------- +// SimpleThread +// Loop 10 times, yielding the CPU to another ready thread +// each iteration. +// +// "which" is simply a number identifying the thread, for debugging +// purposes. +//---------------------------------------------------------------------- + +void +SimpleThread (void *arg) +{ + int which = (long) arg; + int num; + + for (num = 0; num < 10; num++) + { + printf ("*** thread %d looped %d times\n", which, num); + currentThread->Yield (); + } +} + +//---------------------------------------------------------------------- +// ThreadTest +// Set up a ping-pong between two threads, by forking a thread +// to call SimpleThread, and then calling SimpleThread ourselves. +//---------------------------------------------------------------------- + +void +ThreadTest () +{ + DEBUG ('t', "Entering SimpleTest\n"); + + Thread *t = new Thread ("forked thread"); + + t->Start (SimpleThread, (void*) 1); + SimpleThread (0); +} diff --git a/code/threads/utility.cc b/code/threads/utility.cc new file mode 100644 index 0000000..d4fdc00 --- /dev/null +++ b/code/threads/utility.cc @@ -0,0 +1,115 @@ +// utility.cc +// Debugging routines. Allows users to control whether to +// print DEBUG statements, based on a command line argument. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "utility.h" + +#include + +// this seems to be dependent on how the compiler is configured. +// if you have problems with va_start, try both of these alternatives +#if defined(HOST_SNAKE) || defined(HOST_SPARC) || defined(HOST_i386) || defined(HOST_PPC) || defined(HOST_x86_64) +#include +#else +#include "/usr/include/stdarg.h" +#endif + +static const char *enableFlags = NULL; // controls which DEBUG messages are printed + +//---------------------------------------------------------------------- +// SetColor +// Set the color for subsequent printouts +// +// This assumes that the TTY recognizes ANSI colors +//---------------------------------------------------------------------- +void +SetColor (FILE *output, enum AnsiColor color) +{ + if (isatty(fileno(output))) + fprintf(output, "\e[%dm", 30 + color); +} + +//---------------------------------------------------------------------- +// SetBold +// Set bold attribute for subsequent printouts +// +// This assumes that the TTY recognizes ANSI colors +//---------------------------------------------------------------------- +void +SetBold (FILE *output) +{ + if (isatty(fileno(output))) + fprintf(output, "\e[1m"); +} + +//---------------------------------------------------------------------- +// ClearColor +// Clear the color to default for subsequent printouts +// +// This assumes that the TTY recognizes ANSI colors +//---------------------------------------------------------------------- +void +ClearColor (FILE *output) +{ + if (isatty(fileno(output))) + fprintf(output, "\e[0m"); +} + +//---------------------------------------------------------------------- +// DebugInit +// Initialize so that only DEBUG messages with a flag in flagList +// will be printed. +// +// If the flag is "+", we enable all DEBUG messages. +// +// "flagList" is a string of characters for whose DEBUG messages are +// to be enabled. +//---------------------------------------------------------------------- + +void +DebugInit (const char *flagList) +{ + enableFlags = flagList; +} + +//---------------------------------------------------------------------- +// DebugIsEnabled +// Return TRUE if DEBUG messages with "flag" are to be printed. +//---------------------------------------------------------------------- + +bool +DebugIsEnabled (char flag) +{ + if (enableFlags != NULL) + return (strchr (enableFlags, flag) != 0) + || (strchr (enableFlags, '+') != 0); + else + return FALSE; +} + +//---------------------------------------------------------------------- +// DEBUG +// Print a debug message, if flag is enabled. Like printf, +// only with an extra argument on the front. +//---------------------------------------------------------------------- + +void +DEBUG (char flag, const char *format, ...) +{ + if (DebugIsEnabled (flag)) + { + va_list ap; + // You will get an unused variable message here -- ignore it. + va_start (ap, format); + SetColor(stdout, ColorMagenta); + vfprintf (stdout, format, ap); + ClearColor(stdout); + va_end (ap); + fflush (stdout); + } +} diff --git a/code/threads/utility.h b/code/threads/utility.h new file mode 100644 index 0000000..f56ad1a --- /dev/null +++ b/code/threads/utility.h @@ -0,0 +1,111 @@ +// utility.h +// Miscellaneous useful definitions, including debugging routines. +// +// The debugging routines allow the user to turn on selected +// debugging messages, controllable from the command line arguments +// passed to Nachos (-d). You are encouraged to add your own +// debugging flags. The pre-defined debugging flags are: +// +// '+' -- turn on all debug messages +// 't' -- thread system +// 's' -- semaphores, locks, and conditions +// 'i' -- interrupt emulation +// 'm' -- machine emulation (USER_PROGRAM) +// 'd' -- disk emulation (FILESYS) +// 'f' -- file system (FILESYS) +// 'a' -- address spaces (USER_PROGRAM) +// 'n' -- network emulation (NETWORK) +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef UTILITY_H +#define UTILITY_H + +#include "copyright.h" + +// Miscellaneous useful routines + +#include // Boolean values +#include // min + +// Divide and either round up or down +#define divRoundDown(n,s) ((n) / (s)) +#define divRoundUp(n,s) (((n) / (s)) + ((((n) % (s)) > 0) ? 1 : 0)) + +enum AnsiColor { + ColorBlack, + ColorRed, + ColorGreen, + ColorYellow, + ColorBlue, + ColorMagenta, + ColorCyan, + ColorWhite, +}; + +// This declares the type "VoidFunctionPtr" to be a "pointer to a +// function taking an integer argument and returning nothing". With +// such a function pointer (say it is "func"), we can call it like this: +// +// (*func) (17); +// +// This is used by Thread::Start and for interrupt handlers, as well +// as a couple of other places. + +typedef void (*VoidFunctionPtr) (void *arg); +typedef void (*VoidFunctionPtr2) (void *arg, void *arg2); +typedef void (*VoidNoArgFunctionPtr) (); + + +// Include interface that isolates us from the host machine system library. +// Requires definition of bool, and VoidFunctionPtr +#include "sysdep.h" + +// Interface to debugging routines. + +extern void DebugInit (const char *flags); // enable printing debug messages + +extern bool DebugIsEnabled (char flag); // Is this debug flag enabled? + +extern void DEBUG (char flag, const char *format, ...); // Print debug message + // if flag is enabled + +extern void SetColor(FILE *output, enum AnsiColor color); +extern void SetBold(FILE *output); +extern void ClearColor(FILE *output); + +//---------------------------------------------------------------------- +// ASSERT +// If condition is false, print a message and dump core. +// Useful for documenting assumptions in the code. +// +// NOTE: needs to be a #define, to be able to print the location +// where the error occurred. +//---------------------------------------------------------------------- +#define ASSERT(condition) do { \ + if (!(condition)) { \ + SetColor(stderr, ColorRed); \ + SetBold(stderr); \ + fprintf(stderr, "Assertion %s failed: line %d, file \"%s:%d\"\n", \ + #condition, \ + __LINE__, __FILE__, __LINE__); \ + ClearColor(stderr); \ + fflush(stderr); \ + Abort(); \ + } \ + } while(0) + +/* A lot of classes should not be allowed to be copied, e.g. it doesn't make + * sense to copy a Semaphore. To enforce this, inherit from this class. */ + +class dontcopythis { + private: + dontcopythis (const dontcopythis &); + dontcopythis& operator= (const dontcopythis&); + public: + dontcopythis() {}; +}; + +#endif /* UTILITY_H */ diff --git a/code/userprog/Makefile b/code/userprog/Makefile new file mode 100644 index 0000000..aaf54de --- /dev/null +++ b/code/userprog/Makefile @@ -0,0 +1,24 @@ +# NOTE: this is a GNU Makefile. You must use "gmake" rather than "make". +# +# Makefile for the multiprogramming assignment +# Defines set up assuming multiprogramming is done before the file system. +# If not, use the "filesys first" defines below. +# +# +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS_STUB +INCPATH = -I../bin -I../filesys -I../userprog -I../threads -I../machine +C_OFILES = $(THREAD_O) $(USERPROG_O) + +# if file system done first! +# DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS +# INCPATH = -I../bin -I../filesys -I../userprog -I../threads -I../machine +# C_OFILES = $(THREAD_O) $(USERPROG_O) $(FILESYS_O) + +include ../Makefile.common +include ../Makefile.dep + + diff --git a/code/userprog/addrspace.cc b/code/userprog/addrspace.cc new file mode 100644 index 0000000..4b96a5c --- /dev/null +++ b/code/userprog/addrspace.cc @@ -0,0 +1,294 @@ +// addrspace.cc +// Routines to manage address spaces (executing user programs). +// +// In order to run a user program, you must: +// +// 1. link with the -N -T 0 option +// 2. run coff2noff to convert the object file to Nachos format +// (Nachos object code format is essentially just a simpler +// version of the UNIX executable object code format) +// 3. load the NOFF file into the Nachos file system +// (if you haven't implemented the file system yet, you +// don't need to do this last step) +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "system.h" +#include "addrspace.h" +#include "noff.h" +#include "syscall.h" +#include "new" + +//---------------------------------------------------------------------- +// SwapHeader +// Do little endian to big endian conversion on the bytes in the +// object file header, in case the file was generated on a little +// endian machine, and we're now running on a big endian machine. +//---------------------------------------------------------------------- + +static void +SwapHeader (NoffHeader * noffH) +{ + noffH->noffMagic = WordToHost (noffH->noffMagic); + noffH->code.size = WordToHost (noffH->code.size); + noffH->code.virtualAddr = WordToHost (noffH->code.virtualAddr); + noffH->code.inFileAddr = WordToHost (noffH->code.inFileAddr); + noffH->initData.size = WordToHost (noffH->initData.size); + noffH->initData.virtualAddr = WordToHost (noffH->initData.virtualAddr); + noffH->initData.inFileAddr = WordToHost (noffH->initData.inFileAddr); + noffH->uninitData.size = WordToHost (noffH->uninitData.size); + noffH->uninitData.virtualAddr = + WordToHost (noffH->uninitData.virtualAddr); + noffH->uninitData.inFileAddr = WordToHost (noffH->uninitData.inFileAddr); +} + +//---------------------------------------------------------------------- +// AddrSpaceList +// List of all address spaces, for debugging +//---------------------------------------------------------------------- +List AddrSpaceList; + +//---------------------------------------------------------------------- +// AddrSpace::AddrSpace +// Create an address space to run a user program. +// Load the program from a file "executable", and set everything +// up so that we can start executing user instructions. +// +// Assumes that the object code file is in NOFF format. +// +// First, set up the translation from program memory to physical +// memory. For now, this is really simple (1:1), since we are +// only uniprogramming, and we have a single unsegmented page table +// +// "executable" is the file containing the object code to load into memory +//---------------------------------------------------------------------- + +AddrSpace::AddrSpace (OpenFile * executable) +{ + unsigned int i, size; + + executable->ReadAt (&noffH, sizeof (noffH), 0); + if ((noffH.noffMagic != NOFFMAGIC) && + (WordToHost (noffH.noffMagic) == NOFFMAGIC)) + SwapHeader (&noffH); + /* Check that this is really a MIPS program */ + ASSERT (noffH.noffMagic == NOFFMAGIC); + +// how big is address space? + size = noffH.code.size + noffH.initData.size + noffH.uninitData.size + UserStacksAreaSize; // we need to increase the size + // to leave room for the stack + numPages = divRoundUp (size, PageSize); + size = numPages * PageSize; + + // check we're not trying + // to run anything too big -- + // at least until we have + // virtual memory + if (numPages > NumPhysPages) + throw std::bad_alloc(); + + DEBUG ('a', "Initializing address space, num pages %d, total size 0x%x\n", + numPages, size); +// first, set up the translation + pageTable = new TranslationEntry[numPages]; + for (i = 0; i < numPages; i++) + { + pageTable[i].physicalPage = i; // for now, phys page # = virtual page # + pageTable[i].valid = TRUE; + pageTable[i].use = FALSE; + pageTable[i].dirty = FALSE; + pageTable[i].readOnly = FALSE; // if the code segment was entirely on + // a separate page, we could set its + // pages to be read-only + } + +// then, copy in the code and data segments into memory + if (noffH.code.size > 0) + { + DEBUG ('a', "Initializing code segment, at 0x%x, size 0x%x\n", + noffH.code.virtualAddr, noffH.code.size); + executable->ReadAt (&(machine->mainMemory[noffH.code.virtualAddr]), + noffH.code.size, noffH.code.inFileAddr); + } + if (noffH.initData.size > 0) + { + DEBUG ('a', "Initializing data segment, at 0x%x, size 0x%x\n", + noffH.initData.virtualAddr, noffH.initData.size); + executable->ReadAt (& + (machine->mainMemory + [noffH.initData.virtualAddr]), + noffH.initData.size, noffH.initData.inFileAddr); + } + + DEBUG ('a', "Area for stacks at 0x%x, size 0x%x\n", + size - UserStacksAreaSize, UserStacksAreaSize); + + pageTable[0].valid = FALSE; // Catch NULL dereference + + AddrSpaceList.Append(this); +} + +//---------------------------------------------------------------------- +// AddrSpace::~AddrSpace +// Dealloate an address space. Nothing for now! +//---------------------------------------------------------------------- + +AddrSpace::~AddrSpace () +{ + delete [] pageTable; + pageTable = NULL; + + AddrSpaceList.Remove(this); +} + +//---------------------------------------------------------------------- +// AddrSpace::InitRegisters +// Set the initial values for the user-level register set. +// +// We write these directly into the "machine" registers, so +// that we can immediately jump to user code. Note that these +// will be saved/restored into the currentThread->userRegisters +// when this thread is context switched out. +//---------------------------------------------------------------------- + +void +AddrSpace::InitRegisters () +{ + int i; + + for (i = 0; i < NumTotalRegs; i++) + machine->WriteRegister (i, 0); + + // Initial program counter -- must be location of "Start" + machine->WriteRegister (PCReg, USER_START_ADDRESS); + + // Need to also tell MIPS where next instruction is, because + // of branch delay possibility + machine->WriteRegister (NextPCReg, machine->ReadRegister(PCReg) + 4); + + // Set the stack register to the end of the address space, where we + // allocated the stack; but subtract off a bit, to make sure we don't + // accidentally reference off the end! + machine->WriteRegister (StackReg, numPages * PageSize - 16); + DEBUG ('a', "Initializing stack register to 0x%x\n", + numPages * PageSize - 16); +} + +//---------------------------------------------------------------------- +// AddrSpace::Dump +// Dump program layout as SVG +//---------------------------------------------------------------------- + +static void +DrawArea(FILE *output, unsigned sections_x, unsigned virtual_x, + unsigned y, unsigned blocksize, + struct segment *segment, const char *name) +{ + if (segment->size == 0) + return; + + ASSERT((segment->virtualAddr % PageSize == 0)); + ASSERT((segment->size % PageSize == 0)); + unsigned page = segment->virtualAddr / PageSize; + unsigned end = (segment->virtualAddr + segment->size) / PageSize; + + fprintf(output, "\n", + sections_x, y - end * blocksize, + virtual_x - sections_x, (end - page) * blocksize); + + fprintf(output, "%s\n", + sections_x, y - page * blocksize, blocksize, name); +} + +unsigned +AddrSpace::Dump(FILE *output, unsigned addr_x, unsigned sections_x, unsigned virtual_x, unsigned virtual_width, + unsigned physical_x, unsigned virtual_y, unsigned y, + unsigned blocksize) +{ + unsigned ret = machine->DumpPageTable(output, pageTable, numPages, + addr_x, virtual_x, virtual_width, physical_x, virtual_y, y, blocksize); + + DrawArea(output, sections_x, virtual_x, virtual_y, blocksize, &noffH.code, "code"); + DrawArea(output, sections_x, virtual_x, virtual_y, blocksize, &noffH.initData, "data"); + DrawArea(output, sections_x, virtual_x, virtual_y, blocksize, &noffH.uninitData, "bss"); + + DumpThreadsState(output, this, sections_x, virtual_x, virtual_y, blocksize); + + return ret; +} + +//---------------------------------------------------------------------- +// AddrSpace::AddrSpacesRoom +// Return how much room is needed for showing address spaces +//---------------------------------------------------------------------- + +unsigned +AddrSpacesRoom(unsigned blocksize) +{ + ListElement *element; + unsigned room = 0; + + for (element = AddrSpaceList.FirstElement (); + element; + element = element->next) { + AddrSpace *space = (AddrSpace*) element->item; + room += machine->PageTableRoom(space->NumPages(), blocksize); + } + + return room; +} + +//---------------------------------------------------------------------- +// AddrSpace::DumpAddrSpaces +// Dump all address spaces +//---------------------------------------------------------------------- + +void +DumpAddrSpaces(FILE *output, + unsigned addr_x, unsigned sections_x, unsigned virtual_x, unsigned virtual_width, + unsigned physical_x, unsigned y, unsigned blocksize) +{ + ListElement *element; + unsigned virtual_y = y; + + /* TODO: sort by physical page addresses to avoid too much mess */ + for (element = AddrSpaceList.FirstElement (); + element; + element = element->next) { + AddrSpace *space = (AddrSpace*) element->item; + virtual_y -= space->Dump(output, addr_x, sections_x, virtual_x, virtual_width, physical_x, virtual_y, y, blocksize); + } +} + +//---------------------------------------------------------------------- +// AddrSpace::SaveState +// On a context switch, save any machine state, specific +// to this address space, that needs saving. +// +// For now, nothing! +//---------------------------------------------------------------------- + +void +AddrSpace::SaveState () +{ +} + +//---------------------------------------------------------------------- +// AddrSpace::RestoreState +// On a context switch, restore the machine state so that +// this address space can run. +// +// For now, tell the machine where to find the page table. +//---------------------------------------------------------------------- + +void +AddrSpace::RestoreState () +{ + machine->currentPageTable = pageTable; + machine->currentPageTableSize = numPages; +} diff --git a/code/userprog/addrspace.h b/code/userprog/addrspace.h new file mode 100644 index 0000000..2c319f6 --- /dev/null +++ b/code/userprog/addrspace.h @@ -0,0 +1,53 @@ +// addrspace.h +// Data structures to keep track of executing user programs +// (address spaces). +// +// For now, we don't keep any information about address spaces. +// The user level CPU state is saved and restored in the thread +// executing the user program (see thread.h). +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef ADDRSPACE_H +#define ADDRSPACE_H + +#include "copyright.h" +#include "filesys.h" +#include "translate.h" +#include "noff.h" +#include "list.h" + +#define UserStacksAreaSize 1024 // increase this as necessary! + +class AddrSpace:public dontcopythis +{ + public: + AddrSpace (OpenFile * executable); // Create an address space, + // initializing it with the program + // stored in the file "executable" + ~AddrSpace (); // De-allocate an address space + + void InitRegisters (); // Initialize user-level CPU registers, + // before jumping to user code + + void SaveState (); // Save/restore address space-specific + void RestoreState (); // info on a context switch + + unsigned Dump(FILE *output, unsigned addr_s, unsigned sections_x, unsigned virtual_x, unsigned virtual_width, + unsigned physical_x, unsigned virtual_y, unsigned y, + unsigned blocksize); + // Dump program layout as SVG + unsigned NumPages() { return numPages; } + + private: + NoffHeader noffH; // Program layout + + TranslationEntry * pageTable; // Page table + unsigned int numPages; // Number of pages in the page table +}; + +extern List AddrspaceList; + +#endif // ADDRSPACE_H diff --git a/code/userprog/bitmap.cc b/code/userprog/bitmap.cc new file mode 100644 index 0000000..ef81c04 --- /dev/null +++ b/code/userprog/bitmap.cc @@ -0,0 +1,172 @@ +// bitmap.c +// Routines to manage a bitmap -- an array of bits each of which +// can be either on or off. Represented as an array of integers. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "bitmap.h" + +// Definitions helpful for representing a bitmap as an array of integers +#define BitsInByte 8 +#define BitsInWord 32 + +//---------------------------------------------------------------------- +// BitMap::BitMap +// Initialize a bitmap with "nitems" bits, so that every bit is clear. +// it can be added somewhere on a list. +// +// "nitems" is the number of bits in the bitmap. +//---------------------------------------------------------------------- + +BitMap::BitMap (int nitems) +{ + numBits = nitems; + numWords = divRoundUp (numBits, BitsInWord); + map = new unsigned int[numWords]; + for (int i = 0; i < numBits; i++) + Clear (i); +} + +//---------------------------------------------------------------------- +// BitMap::~BitMap +// De-allocate a bitmap. +//---------------------------------------------------------------------- + +BitMap::~BitMap () +{ + delete [] map; + map = NULL; +} + +//---------------------------------------------------------------------- +// BitMap::Mark +// Set the "nth" bit in a bitmap. +// +// "which" is the number of the bit to be set. +//---------------------------------------------------------------------- + +void +BitMap::Mark (int which) +{ + ASSERT (which >= 0 && which < numBits); + map[which / BitsInWord] |= 1 << (which % BitsInWord); +} + +//---------------------------------------------------------------------- +// BitMap::Clear +// Clear the "nth" bit in a bitmap. +// +// "which" is the number of the bit to be cleared. +//---------------------------------------------------------------------- + +void +BitMap::Clear (int which) +{ + ASSERT (which >= 0 && which < numBits); + map[which / BitsInWord] &= ~(1 << (which % BitsInWord)); +} + +//---------------------------------------------------------------------- +// BitMap::Test +// Return TRUE if the "nth" bit is set. +// +// "which" is the number of the bit to be tested. +//---------------------------------------------------------------------- + +bool +BitMap::Test (int which) +{ + ASSERT (which >= 0 && which < numBits); + + if (map[which / BitsInWord] & (1 << (which % BitsInWord))) + return TRUE; + else + return FALSE; +} + +//---------------------------------------------------------------------- +// BitMap::Find +// Return the number of the first bit which is clear. +// As a side effect, set the bit (mark it as in use). +// (In other words, find and allocate a bit.) +// +// If no bits are clear, return -1. +//---------------------------------------------------------------------- + +int +BitMap::Find () +{ + for (int i = 0; i < numBits; i++) + if (!Test (i)) + { + Mark (i); + return i; + } + return -1; +} + +//---------------------------------------------------------------------- +// BitMap::NumClear +// Return the number of clear bits in the bitmap. +// (In other words, how many bits are unallocated?) +//---------------------------------------------------------------------- + +int +BitMap::NumClear () +{ + int count = 0; + + for (int i = 0; i < numBits; i++) + if (!Test (i)) + count++; + return count; +} + +//---------------------------------------------------------------------- +// BitMap::Print +// Print the contents of the bitmap, for debugging. +// +// Could be done in a number of ways, but we just print the #'s of +// all the bits that are set in the bitmap. +//---------------------------------------------------------------------- + +void +BitMap::Print () +{ + printf ("Bitmap set:\n"); + for (int i = 0; i < numBits; i++) + if (Test (i)) + printf ("%d, ", i); + printf ("\n"); +} + +// These aren't needed until the FILESYS assignment + +//---------------------------------------------------------------------- +// BitMap::FetchFromFile +// Initialize the contents of a bitmap from a Nachos file. +// +// "file" is the place to read the bitmap from +//---------------------------------------------------------------------- + +void +BitMap::FetchFrom (OpenFile * file) +{ + file->ReadAt (map, numWords * sizeof (unsigned), 0); +} + +//---------------------------------------------------------------------- +// BitMap::WriteBack +// Store the contents of a bitmap to a Nachos file. +// +// "file" is the place to write the bitmap to +//---------------------------------------------------------------------- + +void +BitMap::WriteBack (OpenFile * file) +{ + file->WriteAt (map, numWords * sizeof (unsigned), 0); +} diff --git a/code/userprog/bitmap.h b/code/userprog/bitmap.h new file mode 100644 index 0000000..58a4a79 --- /dev/null +++ b/code/userprog/bitmap.h @@ -0,0 +1,61 @@ +// bitmap.h +// Data structures defining a bitmap -- an array of bits each of which +// can be either on or off. +// +// Represented as an array of unsigned integers, on which we do +// modulo arithmetic to find the bit we are interested in. +// +// The bitmap can be parameterized with with the number of bits being +// managed. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef BITMAP_H +#define BITMAP_H + +#include "copyright.h" +#include "utility.h" +#include "openfile.h" + +// The following class defines a "bitmap" -- an array of bits, +// each of which can be independently set, cleared, and tested. +// +// Most useful for managing the allocation of the elements of an array -- +// for instance, disk sectors, or main memory pages. +// Each bit represents whether the corresponding sector or page is +// in use or free. + +class BitMap:public dontcopythis +{ + public: + BitMap (int nitems); // Initialize a bitmap, with "nitems" bits + // initially, all bits are cleared. + ~BitMap (); // De-allocate bitmap + + void Mark (int which); // Set the "nth" bit + void Clear (int which); // Clear the "nth" bit + bool Test (int which); // Is the "nth" bit set? + int Find (); // Return the # of a clear bit, and as a side + // effect, set the bit. + // If no bits are clear, return -1. + int NumClear (); // Return the number of clear bits + + void Print (); // Print contents of bitmap + + // These aren't needed until FILESYS, when we will need to read and + // write the bitmap to a file + void FetchFrom (OpenFile * file); // fetch contents from disk + void WriteBack (OpenFile * file); // write contents to disk + + private: + int numBits; // number of bits in the bitmap + int numWords; // number of words of bitmap storage + // (rounded up if numBits is not a + // multiple of the number of bits in + // a word) + unsigned int *map; // bit storage +}; + +#endif // BITMAP_H diff --git a/code/userprog/exception.cc b/code/userprog/exception.cc new file mode 100644 index 0000000..c2986db --- /dev/null +++ b/code/userprog/exception.cc @@ -0,0 +1,137 @@ +// exception.cc +// Entry point into the Nachos kernel from user programs. +// There are two kinds of things that can cause control to +// transfer back to here from user code: +// +// syscall -- The user code explicitly requests to call a procedure +// in the Nachos kernel. Right now, the only function we support is +// "Halt". +// +// exceptions -- The user code does something that the CPU can't handle. +// For instance, accessing memory that doesn't exist, arithmetic errors, +// etc. +// +// Interrupts (which can also cause control to transfer from user +// code into the Nachos kernel) are handled elsewhere. +// +// For now, this only handles the Halt() system call. +// Everything else core dumps. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "system.h" +#include "syscall.h" + +//---------------------------------------------------------------------- +// UpdatePC : Increments the Program Counter register in order to resume +// the user program immediately after the "syscall" instruction. +//---------------------------------------------------------------------- +static void +UpdatePC () +{ + int pc = machine->ReadRegister (PCReg); + machine->WriteRegister (PrevPCReg, pc); + pc = machine->ReadRegister (NextPCReg); + machine->WriteRegister (PCReg, pc); + pc += 4; + machine->WriteRegister (NextPCReg, pc); +} + + +//---------------------------------------------------------------------- +// ExceptionHandler +// Entry point into the Nachos kernel. Called when a user program +// is executing, and either does a syscall, or generates an addressing +// or arithmetic exception. +// +// For system calls, the following is the calling convention: +// +// system call code -- r2 +// arg1 -- r4 +// arg2 -- r5 +// arg3 -- r6 +// arg4 -- r7 +// +// The result of the system call, if any, must be put back into r2. +// +// And don't forget to increment the pc before returning. (Or else you'll +// loop making the same system call forever! +// +// "which" is the kind of exception. The list of possible exceptions +// are in machine.h. +//---------------------------------------------------------------------- + +void +ExceptionHandler (ExceptionType which) +{ + int type = machine->ReadRegister (2); + int address = machine->registers[BadVAddrReg]; + + switch (which) + { + case SyscallException: + { + switch (type) + { + case SC_Halt: + { + DEBUG ('s', "Shutdown, initiated by user program.\n"); + interrupt->Powerdown (); + break; + } + default: + { + printf("Unimplemented system call %d\n", type); + ASSERT(FALSE); + } + } + + // Do not forget to increment the pc before returning! + UpdatePC (); + break; + } + + case PageFaultException: + if (!address) { + printf("NULL dereference at PC %x!\n", machine->registers[PCReg]); + ASSERT (FALSE); + } else { + printf ("Page Fault at address %x at PC %x\n", address, machine->registers[PCReg]); + ASSERT (FALSE); // For now + } + break; + + case ReadOnlyException: + printf ("Read-Only at address %x at PC %x\n", address, machine->registers[PCReg]); + ASSERT (FALSE); // For now + break; + + case BusErrorException: + printf ("Invalid physical address at address %x at PC %x\n", address, machine->registers[PCReg]); + ASSERT (FALSE); // For now + break; + + case AddressErrorException: + printf ("Invalid address %x at PC %x\n", address, machine->registers[PCReg]); + ASSERT (FALSE); // For now + break; + + case OverflowException: + printf ("Overflow at PC %x\n", machine->registers[PCReg]); + ASSERT (FALSE); // For now + break; + + case IllegalInstrException: + printf ("Illegal instruction at PC %x\n", machine->registers[PCReg]); + ASSERT (FALSE); // For now + break; + + default: + printf ("Unexpected user mode exception %d %d %x at PC %x\n", which, type, address, machine->registers[PCReg]); + ASSERT (FALSE); + break; + } +} diff --git a/code/userprog/progtest.cc b/code/userprog/progtest.cc new file mode 100644 index 0000000..5725dbd --- /dev/null +++ b/code/userprog/progtest.cc @@ -0,0 +1,106 @@ +// progtest.cc +// Test routines for demonstrating that Nachos can load +// a user program and execute it. +// +// Also, routines for testing the Console hardware device. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#include "copyright.h" +#include "system.h" +#include "console.h" +#include "addrspace.h" +#include "synch.h" + +//---------------------------------------------------------------------- +// StartProcess +// Run a user program. Open the executable, load it into +// memory, and jump to it. +//---------------------------------------------------------------------- + +void +StartProcess (char *filename) +{ + OpenFile *executable = fileSystem->Open (filename); + AddrSpace *space; + + if (executable == NULL) + { + SetColor (stdout, ColorRed); + SetBold (stdout); + printf ("Unable to open file %s\n", filename); + ClearColor (stdout); + return; + } + space = new AddrSpace (executable); + currentThread->space = space; + + delete executable; // close file + + space->InitRegisters (); // set the initial register values + space->RestoreState (); // load page table register + + machine->DumpMem ("memory.svg"); + machine->Run (); // jump to the user progam + ASSERT (FALSE); // machine->Run never returns; + // the address space exits + // by doing the syscall "exit" +} + +// Data structures needed for the console test. Threads making +// I/O requests wait on a Semaphore to delay until the I/O completes. + +static Console *console; +static Semaphore *readAvail; +static Semaphore *writeDone; + +//---------------------------------------------------------------------- +// ConsoleInterruptHandlers +// Wake up the thread that requested the I/O. +//---------------------------------------------------------------------- + +static void +ReadAvailHandler (void *arg) +{ + (void) arg; + readAvail->V (); +} +static void +WriteDoneHandler (void *arg) +{ + (void) arg; + writeDone->V (); +} + +//---------------------------------------------------------------------- +// ConsoleTest +// Test the console by echoing characters typed at the input onto +// the output. Stop when the user types a 'q'. +//---------------------------------------------------------------------- + +void +ConsoleTest (const char *in, const char *out) +{ + char ch; + + readAvail = new Semaphore ("read avail", 0); + writeDone = new Semaphore ("write done", 0); + console = new Console (in, out, ReadAvailHandler, WriteDoneHandler, NULL); + + for (;;) + { + readAvail->P (); // wait for character to arrive + ch = console->RX (); + console->TX (ch); // echo it! + writeDone->P (); // wait for write to finish + if (ch == 'q') { + printf ("Nothing more, bye!\n"); + break; // if q, quit + } + } + delete console; + delete readAvail; + delete writeDone; +} diff --git a/code/userprog/progtest.h b/code/userprog/progtest.h new file mode 100644 index 0000000..af0a1f0 --- /dev/null +++ b/code/userprog/progtest.h @@ -0,0 +1,18 @@ +// progtest.h +// Declarations for test routines for demonstrating that Nachos can load a +// user program and execute it. +// +// Also, routines for testing the Console hardware device. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. + +#ifndef PROGTEST_H +#define PROGTEST_H + +extern void StartProcess (char *filename); + +extern void ConsoleTest (const char *in, const char *out); + +#endif // PROGTEST_H diff --git a/code/userprog/syscall.h b/code/userprog/syscall.h new file mode 100644 index 0000000..6c9e6e3 --- /dev/null +++ b/code/userprog/syscall.h @@ -0,0 +1,135 @@ +/* syscalls.h + * Nachos system call interface. These are Nachos kernel operations + * that can be invoked from user programs, by trapping to the kernel + * via the "syscall" instruction. + * + * This file is included by user programs and by the Nachos kernel. + * + * Copyright (c) 1992-1993 The Regents of the University of California. + * All rights reserved. See copyright.h for copyright notice and limitation + * of liability and disclaimer of warranty provisions. + */ + +#ifndef SYSCALLS_H +#define SYSCALLS_H + +#include "copyright.h" + +#define USER_START_ADDRESS 0x80 + +/* system call codes -- used by the stubs to tell the kernel which system call + * is being asked for + */ +#define SC_Halt 0 +#define SC_Exit 1 +#define SC_Exec 2 +#define SC_Join 3 +#define SC_Create 4 +#define SC_Open 5 +#define SC_Read 6 +#define SC_Write 7 +#define SC_Close 8 +#define SC_Fork 9 +#define SC_Yield 10 + +#ifdef IN_USER_MODE + +// LB: This part is read only on compiling the test/*.c files. +// It is *not* read on compiling test/start.S + + +/* The system call interface. These are the operations the Nachos + * kernel needs to support, to be able to run user programs. + * + * Each of these is invoked by a user program by simply calling the + * procedure; an assembly language stub stuffs the system call code + * into a register, and traps to the kernel. The kernel procedures + * are then invoked in the Nachos kernel, after appropriate error checking, + * from the system call entry point in exception.cc. + */ + +/* Stop Nachos, and print out performance stats */ +void Halt () __attribute__ ((__noreturn__)); + + +/* Address space control operations: Exit, Exec, and Join */ + +/* This user program is done (status = 0 means exited normally). */ +void Exit (int status) __attribute__ ((__noreturn__)); + +/* A unique identifier for an executing user program (address space) */ +typedef int SpaceId; + +/* Run the executable, stored in the Nachos file "name", and return the + * address space identifier + */ +SpaceId Exec (const char *name); + +/* Only return once the the user program "id" has finished. + * Return the exit status. + */ +int Join (SpaceId id); + + +/* File system operations: Create, Open, Read, Write, Close + * These functions are patterned after UNIX -- files represent + * both files *and* hardware I/O devices. + * + * If this assignment is done before doing the file system assignment, + * note that the Nachos file system has a stub implementation, which + * will work for the purposes of testing out these routines. + */ + +/* A unique identifier for an open Nachos file. */ +typedef int OpenFileId; + +/* when an address space starts up, it has two open files, representing + * keyboard input and display output (in UNIX terms, stdin and stdout). + * Read and Write can be used directly on these, without first opening + * the console device. + */ + +#define ConsoleInput 0 +#define ConsoleOutput 1 + +/* Create a Nachos file, with "name" */ +void Create (const char *name); + +/* Open the Nachos file "name", and return an "OpenFileId" that can + * be used to read and write to the file. + */ +OpenFileId Open (const char *name); + +/* Write "size" bytes from "buffer" to the open file. */ +void Write (const void *buffer, int size, OpenFileId id); + +/* Read "size" bytes from the open file into "buffer". + * Return the number of bytes actually read -- if the open file isn't + * long enough, or if it is an I/O device, and there aren't enough + * characters to read, return whatever is available (for I/O devices, + * you should always wait until you can return at least one character). + */ +int Read (void *buffer, int size, OpenFileId id); + +/* Close the file, we're done reading and writing to it. */ +void Close (OpenFileId id); + + + +/* User-level thread operations: Fork and Yield. To allow multiple + * threads to run within a user program. + */ + +/* Fork a thread to run a procedure ("func") in the *same* address space + * as the current thread. + */ +void Fork (void (*func) ()); + +/* Yield the CPU to another runnable thread, whether in this address space + * or not. + */ +void Yield (); + +#endif // IN_USER_MODE + +#endif /* SYSCALL_H */ diff --git a/code/vm/Makefile b/code/vm/Makefile new file mode 100644 index 0000000..5d57a5b --- /dev/null +++ b/code/vm/Makefile @@ -0,0 +1,26 @@ +# NOTE: this is a GNU Makefile. You must use "gmake" rather than "make". +# +# Makefile for the virtual memory assignment +# Defines set up assuming the virtual memory assignment is done before +# the file system assignment. If not, use the "filesys first" +# defines below. +# +# Also, if you want to simplify the translation so it assumes +# only linear page tables, don't define USE_TLB. +# +# Copyright (c) 1992 The Regents of the University of California. +# All rights reserved. See copyright.h for copyright notice and limitation +# of liability and disclaimer of warranty provisions. + +DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS_STUB -DVM -DUSE_TLB +INCPATH = -I../filesys -I../bin -I../vm -I../userprog -I../threads -I../machine +C_OFILES = $(THREAD_O) $(USERPROG_O) $(VM_O) + +# if file sys done first! +# DEFINES = -DUSER_PROGRAM -DFILESYS_NEEDED -DFILESYS -DVM -DUSE_TLB +# INCPATH = -I../vm -I../bin -I../filesys -I../userprog -I../threads -I../machine +# C_OFILES = $(THREAD_O) $(USERPROG_O) $(FILESYS_O) $(VM_O) + +include ../Makefile.common +include ../Makefile.dep + diff --git a/nachos.ps.gz b/nachos.ps.gz new file mode 100644 index 0000000..8a679bc Binary files /dev/null and b/nachos.ps.gz differ