Initial version

This commit is contained in:
Yorick Barbanneau 2021-10-11 22:27:00 +02:00
commit 6f405265a5
102 changed files with 14486 additions and 0 deletions

18
COPYRIGHT Normal file
View file

@ -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.

365
README Normal file
View file

@ -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.

65
bin/nachos_arch Executable file
View file

@ -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

62
bin/nachos_sys Executable file
View file

@ -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

62
code/Makefile Normal file
View file

@ -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

145
code/Makefile.common Normal file
View file

@ -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 *\~ \#*\#

61
code/Makefile.dep Normal file
View file

@ -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

49
code/bin/Makefile Normal file
View file

@ -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

49
code/bin/coff.h Normal file
View file

@ -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 */
};

126
code/bin/coff2flat.c Normal file
View file

@ -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 <filehdr.h>
#include <aouthdr.h>
#include <scnhdr.h>
#include <reloc.h>
#include <syms.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
/* 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 <coffFileName> <flatFileName>\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);
}

236
code/bin/coff2noff.c Normal file
View file

@ -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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#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 <coffFileName> <noffFileName>\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);
}

219
code/bin/d.c Normal file
View file

@ -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;
}
}
}

172
code/bin/disasm.c Normal file
View file

@ -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 <stdio.h>
#include <filehdr.h>
#include <scnhdr.h>
#include <syms.h>
#include <ldfcn.h>
#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; i<hd->s_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<texthead.s_size; i += 4 )
{
dis1(pc);
pc = pc + 4;
}
}
dis1(xpc)
int xpc;
{
register int instr;
instr = fetch(pc);
dump_ascii(instr, pc);
printf("\n");
}

107
code/bin/encode.h Normal file
View file

@ -0,0 +1,107 @@
/*
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"
/* normal opcodes */
#define I_SPECIAL 000
#define I_BCOND 001
#define I_J 002
#define I_JAL 003
#define I_BEQ 004
#define I_BNE 005
#define I_BLEZ 006
#define I_BGTZ 007
#define I_ADDI 010
#define I_ADDIU 011
#define I_SLTI 012
#define I_SLTIU 013
#define I_ANDI 014
#define I_ORI 015
#define I_XORI 016
#define I_LUI 017
#define I_COP0 020
#define I_COP1 021
#define I_COP2 022
#define I_COP3 023
#define I_LB 040
#define I_LH 041
#define I_LWL 042
#define I_LW 043
#define I_LBU 044
#define I_LHU 045
#define I_LWR 046
#define I_SB 050
#define I_SH 051
#define I_SWL 052
#define I_SW 053
#define I_SWR 056
#define I_LWC0 060
#define I_LWC1 061
#define I_LWC2 062
#define I_LWC3 063
#define I_SWC0 070
#define I_SWC1 071
#define I_SWC2 072
#define I_SWC3 073
/* special opcodes */
#define I_SLL 000
#define I_SRL 002
#define I_SRA 003
#define I_SLLV 004
#define I_SRLV 006
#define I_SRAV 007
#define I_JR 010
#define I_JALR 011
#define I_SYSCALL 014
#define I_BREAK 015
#define I_MFHI 020
#define I_MTHI 021
#define I_MFLO 022
#define I_MTLO 023
#define I_MULT 030
#define I_MULTU 031
#define I_DIV 032
#define I_DIVU 033
#define I_ADD 040
#define I_ADDU 041
#define I_SUB 042
#define I_SUBU 043
#define I_AND 044
#define I_OR 045
#define I_XOR 046
#define I_NOR 047
#define I_SLT 052
#define I_SLTU 053
/* bcond opcodes */
#define I_BLTZ 000
#define I_BGEZ 001
#define I_BLTZAL 020
#define I_BGEZAL 021
/* whole instructions */
#define I_NOP 000

574
code/bin/execute.c Normal file
View file

@ -0,0 +1,574 @@
/*
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 <stdio.h>
#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<argc; ++j )
{
strcpy((mem-memoffset)+ai, argv[j]);
store(aci, ai);
aci += 4;
ai += strlen(argv[j]) + 1;
}
for ( ; ; )
{
++icount;
xpc = pc; pc = npc; npc = pc + 4;
instr = ifetch(xpc);
Reg[0] = 0; /* Force r0 = 0 */
if ( instr != 0 ) /* eliminate no-ops */
{
switch ( (instr>>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<NNN; ++i )
{
I += hists[h][i];
printf("%d\t%d\t%5.2f%%\t%5.2f%%\n",
i, hists[h][i],
(double) 100*hists[h][i] / htotal[h],
(double) 100*I/htotal[h]);
}
printf("oflo %d:\t%d/%d\t%5.2f%%\n",
h, hoflo[h], htotal[h],
(double) 100*hoflo[h] / htotal[h]);
}
}
int numadds=1, numsubs=1, numsuccesses, numcarries;
int addtable[33][33];
int subtable[33][33];
char fmt[] = "%6d";
char fmt2[] = "------";
patable(tab)
int tab[33][33];
{
int i, j;
printf(" |");
for ( j=0; j<33; ++j )
printf(fmt, j);
putchar('\n');
printf(" |");
for ( j=0; j<33; ++j )
printf(fmt2);
putchar('\n');
for ( i=0; i<33; ++i )
{
printf("%2d|", i);
for ( j=0; j<33; ++j )
printf(fmt, tab[i][j]);
putchar('\n');
}
}
printstatistics()
{
/*
printhist();
/*
printf("numjmpls = %d / %d = %5.2f%%\n",
numjmpls, arch1cycles, 100.0*numjmpls/arch1cycles);
printf("numadds = %d, numsubs = %d, numcycles = %d, frac = %5.2f%%\n",
numadds, numsubs,
arch1cycles, (double) 100 * (numadds+numsubs) / arch1cycles);
printf("numsuccesses = %d (%5.2f%%) numcarries = %d\n",
numsuccesses, 100.0*numsuccesses/(numadds+numsubs), numcarries);
/*
hprint();
printf("\nADD table:\n");patable(addtable);
printf("\nSUB table:\n");patable(subtable);
*/
}
#define NNNN (64)
static int hist[NNNN];
henter(n)
int n;
{
if ( 0 <= n && n < NNNN )
++hist[n];
}
printhist()
{
int i;
for ( i=0; i<NNNN; ++i )
printf("%d %d\n", i, hist[i]);
}

21
code/bin/instr.h Normal file
View file

@ -0,0 +1,21 @@
/*
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"
/* Instruction formats */
#define rd(i) (((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))

31
code/bin/int.h Normal file
View file

@ -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)))

167
code/bin/main.c Normal file
View file

@ -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 <stdio.h>
#include <filehdr.h>
#include <scnhdr.h>
#include <syms.h>
#include <ldfcn.h>
#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<head.s_size; ++i ) \
*(char *) ((mem-memoffset)+pc++) = getc(fp); \
if (pc-memoffset >= 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);
}

28
code/bin/noff.h Normal file
View file

@ -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 */

142
code/bin/opstrings.c Normal file
View file

@ -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",
};

256
code/bin/out.c Normal file
View file

@ -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 <filehdr.h>
#include <aouthdr.h>
#include <scnhdr.h>
#include <reloc.h>
#include <syms.h>
#include <stdio.h>
#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<symhdr.iextMax; ++i) {
read_struct(f,symbols[i]);
}
if (symhdr.issExtMax > MAXSSPACE) {
printf("too large a string space.\n");
exit(1);
}
fseek(f,symhdr.cbSsExtOffset,0);
fread(sspace,1,symhdr.issExtMax,f);
for (i=0; i<filehdr.f_nscns; ++i) {
print_section(i);
}
printf("External Symbols:\nValue\t Type\t\tStorage Class\tName\n");
for (i=0; i < MAXSYMS && i < symhdr.iextMax; ++i) {
SYMR *sym = &symbols[i].asym;
if (sym->sc == 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 <varargs.h>
/*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 = &section[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");
}

143
code/bin/system.c Normal file
View file

@ -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 <stdio.h>
#include <syscall.h>
#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 */
}

23
code/filesys/Makefile Normal file
View file

@ -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

198
code/filesys/directory.cc Normal file
View file

@ -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;
}

83
code/filesys/directory.h Normal file
View file

@ -0,0 +1,83 @@
// directory.h
// Data structures to manage a UNIX-like directory of file names.
//
// A directory is a table of pairs: <file name, sector #>,
// 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:
// <file name, file header location>
int FindIndex(const char *name); // Find the index into the directory
// table corresponding to "name"
};
#endif // DIRECTORY_H

150
code/filesys/filehdr.cc Normal file
View file

@ -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;
}

66
code/filesys/filehdr.h Normal file
View file

@ -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

342
code/filesys/filesys.cc Normal file
View file

@ -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;
}

98
code/filesys/filesys.h Normal file
View file

@ -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

185
code/filesys/fstest.cc Normal file
View file

@ -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();
}

197
code/filesys/openfile.cc Normal file
View file

@ -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 <strings.h> /* 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();
}

97
code/filesys/openfile.h Normal file
View file

@ -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

112
code/filesys/synchdisk.cc Normal file
View file

@ -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();
}

53
code/filesys/synchdisk.h Normal file
View file

@ -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

16
code/filesys/test/big Normal file
View file

@ -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.

4
code/filesys/test/medium Normal file
View file

@ -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.

1
code/filesys/test/small Normal file
View file

@ -0,0 +1 @@
This is the spring of our discontent.

234
code/machine/console.cc Normal file
View file

@ -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 <langinfo.h>
#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);
}

85
code/machine/console.h Normal file
View file

@ -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 <stdio.h>
// 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

272
code/machine/disk.cc Normal file
View file

@ -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);
}

93
code/machine/disk.h Normal file
View file

@ -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

385
code/machine/interrupt.cc Normal file
View file

@ -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);
}

134
code/machine/interrupt.h Normal file
View file

@ -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

437
code/machine/machine.cc Normal file
View file

@ -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(" <return> execute one instruction\n");
printf(" <number> 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, "<text x=\"%d\" y=\"%u\" stroke=\"%s\" fill=\"%s\" font-size=\"%u\">%s</text>\n",
ptr_x, y - page * blocksize, color, color, blocksize, name);
fprintf(output, "<line x1=\"%d\" y1=\"%u\" x2=\"%u\" y2=\"%u\" "
"stroke=\"#808080\" stroke-width=\"5\"/>\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, "<text x=\"%u\" y=\"%u\" font-size=\"%u\">0x%04x</text>\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, "<rect x=\"%u\" y=\"%u\" "
"width=\"%u\" height=\"%u\" "
"fill=\"#%02x%02x%02x\" "
"stroke=\"#000000\" "
"stroke-width=\"1\"/>\n",
virtual_x + offset * blocksize,
virtual_y - page * blocksize - blocksize,
blocksize, blocksize,
r, g, b);
}
fprintf(output, "<line x1=\"%u\" y1=\"%u\" "
"x2=\"%u\" y2=\"%u\" "
"stroke=\"#000000\" "
"stroke-width=\"1\"/>\n",
virtual_x + virtual_width,
virtual_y - page * blocksize - blocksize/2,
physical_x,
y - e->physicalPage * blocksize - blocksize/2);
}
}
if (_pageTable == currentPageTable) {
fprintf(output, "<rect x=\"%u\" y=\"%u\" "
"width=\"%u\" height=\"%u\" "
"fill-opacity=\"0.0\" "
"stroke=\"#FF0000\" "
"stroke-width=\"2\"/>\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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
fprintf(output, "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox = \"0 0 %u %u\" version = \"1.1\">\n", width, height);
fprintf(output, "<rect x=\"%u\" y=\"%u\" "
"width=\"%u\" height=\"%u\" "
"fill=\"#ffffff\" "
"stroke=\"#000000\" "
"stroke-width=\"1\"/>\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, "<rect x=\"%u\" y=\"%u\" "
"width=\"%u\" height=\"%u\" "
"fill=\"#%02x%02x%02x\" "
"stroke=\"#000000\" "
"stroke-width=\"1\"/>\n",
physical_x + offset * blocksize,
height - page * blocksize - blocksize,
blocksize, blocksize,
r, g, b);
}
}
fprintf(output, "</svg>\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;
}

239
code/machine/machine.h Normal file
View file

@ -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

703
code/machine/mipssim.cc Normal file
View file

@ -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,
&registers[HiReg], &registers[LoReg]);
break;
case OP_MULTU:
Mult(registers[instr->rs], registers[instr->rt], FALSE,
&registers[HiReg], &registers[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;
}

229
code/machine/mipssim.h Normal file
View file

@ -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

139
code/machine/network.cc Normal file
View file

@ -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 <strings.h> /* 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;
}

101
code/machine/network.h Normal file
View file

@ -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

49
code/machine/stats.cc Normal file
View file

@ -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);
}

70
code/machine/stats.h Normal file
View file

@ -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

537
code/machine/sysdep.cc Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/un.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
// 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);
}

68
code/machine/sysdep.h Normal file
View file

@ -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 <stdlib.h> // for atoi, atof, abs
#include <stdio.h> // for printf, fprintf
#include <string.h> // for DEBUG, etc.
}
#endif // SYSDEP_H

85
code/machine/timer.cc Normal file
View file

@ -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;
}

49
code/machine/timer.h Normal file
View file

@ -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

267
code/machine/translate.cc Normal file
View file

@ -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 = &currentPageTable[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;
}

46
code/machine/translate.h Normal file
View file

@ -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:
// <virtual page #, physical page #>.
//
// 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

23
code/machine/valgrind.h Normal file
View file

@ -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 <valgrind/valgrind.h>
#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

23
code/network/Makefile Normal file
View file

@ -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

59
code/network/README Normal file
View file

@ -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...

72
code/network/nettest.cc Normal file
View file

@ -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();
}

347
code/network/post.cc Normal file
View file

@ -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 <strings.h> /* 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();
}

141
code/network/post.h Normal file
View file

@ -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

97
code/test/Makefile Normal file
View file

@ -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/\<zero\>/r0/g;s/\<at\>/r1/g;s/\<v0\>/r2/g;s/\<v1\>/r3/g;s/\<a0\>/r4/g;s/\<a1\>/r5/g;s/\<a2\>/r6/g;s/\<a3\>/r7/g;s/\<t0\>/r8/g;s/\<gp\>/r28/g;s/\<sp\>/r29/g;s/\<s8\>/r30/g;s/\<ra\>/r31/g;' > $@
$(PROGS): %: %.coff
../bin/coff2noff $< $@
# Cleaning rule
.PHONY: clean
clean:
rm -f core *.coff *.o *.s $(PROGS)

22
code/test/halt.c Normal file
View file

@ -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;
}

39
code/test/matmult.c Normal file
View file

@ -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 */
}

47
code/test/script Normal file
View file

@ -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 = .;
}

37
code/test/shell.c Normal file
View file

@ -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);
}
}
}

34
code/test/sort.c Normal file
View file

@ -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! */
}

155
code/test/start.S Normal file
View file

@ -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

17
code/threads/Makefile Normal file
View file

@ -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

14
code/threads/bool.h Normal file
View file

@ -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 <stdbool.h>
#define TRUE true
#define FALSE false
#endif

25
code/threads/copyright.h Normal file
View file

@ -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

332
code/threads/list.cc Normal file
View file

@ -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;
}

74
code/threads/list.h Normal file
View file

@ -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

190
code/threads/main.cc Normal file
View file

@ -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 <malloc.h>
// 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 <debugflags> -rs <random seed #> -z -h\n"
#ifdef USER_PROGRAM
" -s -x <nachos file> -c <consoleIn> <consoleOut>\n"
#endif
#ifdef FILESYS
" -f -cp <unix file> <nachos file>\n"
" -p <nachos file> -r <nachos file> -l -D -t\n"
#endif
#ifdef NETWORK
" -n <network reliability> -m <machine id>\n"
" -o <other machine id>\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...
}

182
code/threads/scheduler.cc Normal file
View file

@ -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 <sanitizer/asan_interface.h>
#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);
}

39
code/threads/scheduler.h Normal file
View file

@ -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

555
code/threads/switch.S Normal file
View file

@ -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 <sun4/trap.h>
#include <sun4/asm_linkage.h>
#else
#include <sys/trap.h>
#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

223
code/threads/switch.h Normal file
View file

@ -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

164
code/threads/synch.cc Normal file
View file

@ -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);
}

148
code/threads/synch.h Normal file
View file

@ -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

101
code/threads/synchlist.cc Normal file
View file

@ -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 ();
}

43
code/threads/synchlist.h Normal file
View file

@ -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

269
code/threads/system.cc Normal file
View file

@ -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 <locale.h>
#ifdef __GLIBC__
#include <malloc.h>
#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);
}

52
code/threads/system.h Normal file
View file

@ -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

533
code/threads/thread.cc Normal file
View file

@ -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 <pthread.h>
#include <sanitizer/asan_interface.h>
#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

180
code/threads/thread.h Normal file
View file

@ -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

View file

@ -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);
}

115
code/threads/utility.cc Normal file
View file

@ -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 <unistd.h>
// 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 <stdarg.h>
#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);
}
}

111
code/threads/utility.h Normal file
View file

@ -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 <bool.h> // Boolean values
#include <algorithm> // 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 */

24
code/userprog/Makefile Normal file
View file

@ -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

294
code/userprog/addrspace.cc Normal file
View file

@ -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, "<rect x=\"%u\" y=\"%u\" width=\"%u\" height=\"%u\" "
"fill=\"#ffffff\" "
"stroke=\"#000000\" stroke-width=\"1\"/>\n",
sections_x, y - end * blocksize,
virtual_x - sections_x, (end - page) * blocksize);
fprintf(output, "<text x=\"%u\" y=\"%u\" fill=\"#000000\" font-size=\"%u\">%s</text>\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;
}

53
code/userprog/addrspace.h Normal file
View file

@ -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

172
code/userprog/bitmap.cc Normal file
View file

@ -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);
}

61
code/userprog/bitmap.h Normal file
View file

@ -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

137
code/userprog/exception.cc Normal file
View file

@ -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;
}
}

106
code/userprog/progtest.cc Normal file
View file

@ -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;
}

18
code/userprog/progtest.h Normal file
View file

@ -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

135
code/userprog/syscall.h Normal file
View file

@ -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 */

Some files were not shown because too many files have changed in this diff Show more