The hardware simulator code and your operating system code combine to form the simulation program. However, it's perhaps easier to understand the simulation program if the hardware and operating system code are separate parts of the simulation program. As with a real computing system, the connection between the hardware and the operating system occurs at the interrupt handlers.
Unlike a real computing system, however, the operating system in the simulation program does not run on the hardware it controls. The operating system executes as native C++ code, while user processes execute on the simulated hardware. This may be a bit confusing at first, particularly for those that have written native operating systems, but this approach is much simpler than writing a native operating system, and the fundamental concepts used in each are the same.
/export/home/class/cs-438-505project directory, which should be accessible from any CS Lab machine.
The subdirectories under the project directory are
.dskare for disks; files ending in
.ttyare for terminals. The files associated with programming assignment i start with
.hfiles included via the
The subdirectory names
SunOS are identical with the output
uname -s command in the respective systems. You can use this to
automatically set the paths properly based on the machine you're logged into.
For example, in
will automatically include the proper binary directory in your
The subdirectories under
SunOS have the same structure:
g++-libsim.a. Under Solairs, the simulator library is built using both g++ and CC, and is called
The simulator libaray contains all the code needed to build a simulator,
g++ -o my-sim /export/home/class/cs-438-505/SunOS/lib/g++-libsim.a
builds a simulator called
my-sim (the simulator won't do much because it
contains only the default interrupt handlers, which don't do much).
CC compilers on Solaris are not compatible; the object
code and libraries they produce are not interchangable. To avoid problems, use
CC; don't mix them. You shoud also match the simulator
library to the compiler you used; use
Primary Store is the only public member of the system class; only Primary Store can be directly accessed from outside the system class. The remaining members of the system class are private members and cannot be directly accessed from outside the system class. The private members of the system class are and are accessed indirectly through Primary Store, as described in various sections of the hardware architecture description.
The simulator creates a single of instance of the hardware architecture, which
is stored in
mass, a global variable of type
class system. The
system class has three public members: its constructor, an
initialize-and-execute routine, and a pointer to an object of type
memory. The first two public members should not be accessed by operating
system code; only the pointer to memory is properly a public member. See
system.h for details.
The memory class defines the
store() public member
functions to read and write Primary Store. There are a few other public
members available to help simplify code; see
memory.h for details.
The include file
system.h also defines a number of named constants that
act as parameters for the hardware architecture; such parameters include
Primary Store size, the way Primary Store's partitioned into System, User, and
Device Space, and so on. See
system.h for details. The actual value of
the named constants may change at any time; it is important that your code use
the named constants and not the actual values.
passing in the identifying code for the interrupt. Your code takes over, and
what happens after that is entirely up to you. When your interrupt handler
returns, either by a
return statement or by falling off the end of the
interrupt handler, the simulator resumes executing the user process.
This see-sawing between the user process and the operating system continues
until there are no more user processes to execute, at which point the operating
system halts the system.
The interrupts that occur may or may not be a result of the executing user process. If the user process makes a system call, then it will generate a system-call interrupt. If, however, the operating system starts a disk read to bring in the next user program, then the resulting disk-io interrupt has nothing to do with the executing user process.
When the simulator starts, it calls
interrupt_handler() with the
identifying code for a reboot interrupt.
The simulator and my implementation of the operating system were developed
g++ compiler on both Linux and Solaris. The
compiler also works, but doesn't work well with
files. If anyone wants to work with
CC should let me know and I'll make
CC-compatible object files available.
debugp()output procedure for printing debugging messages to
std::cerr. The advantage to using
debugp()over printing to
std::cerr) is that you can use the
-Doption on the simulator command line to control which
debugp()calls generate output.
debugp() prototype is
void debugp(int flags, const char * fmt, . . .)
Except for the integer
debugp() is similar to the
printf() statement in Unix.
flags argument determines whether a particular
debugp() call will
result in output. There are 32 flags, named
debugp() call produces output only if one of the flags
given in its
flags argument appears as part of a
-D option on the
simulator command line. For example, the
debugp(dbp_f29, "The time is %d.\n", read_clock());
would produce output if the flag
dbp_f29 appeared as part of a
option on the simulator command line; if the flag
dbp_f29 didn't appear as
part of a
-D option, then the
debugp() call would produce no output.
Individual flags can be combined using addition (
+) or bit-wise or
|). For example, the
debugp(dbp_f29 | dbp_f20, "The time is %d.\n", read_clock());
would produce output if either
dbp_f20 appeared in a
The simulator uses some of the flags in its own debugging output statements.
The simulator started with flag
dbp_f00 and allocates sequentially higher
flags. To avoid conflicts over flag use, your operating system should start
dbp_f31 and allocate sequentially lower flags. See
for details. (The simulator also defines the flags to have meaningful names;
unfortunately, other flag users are stuck with the
The remaining arguments to
debugp() are similar to the arguments in
There are differences, however, between the formatting specifiers used in
debugp() recognizes fewer specifiers than does
debugp() formatting specifiers are simpler than they are
debugp() re-interprets some of the common specifiers an a way differently than
The formatting specifiers recognized by
c- Print a character.
d- Print a decimal integer.
R- Print a response code as text. The value corresponding to a
%Rformat specifier should be one of the
status::responsescodes defined in
system.h. For example,
%Rprints the response code bad_address as the text "bad address".
s- Print a string; this is not a
char *string but a
<string>string. The value corresponding to a
%sformat specifier should be a pointer to the string; for example
debugp(dbp_f30, "name: %s.\n", &str);
If you try to pass a string, you'll get a compilation error.
x- Print a hexadecimal integer.
%- Print a
Other options are available upon request.
debugp() formatting specifier is just the character
% followed by one of
the characters given in the previous table. For example, the formatting
%d would print a decimal integer. The field width, pad
character, justification, and other options that can be specified in the
formatting specifiers used by
printf() are not recognized by
The simulator library should appear after the list of your files;
g++tries to resolve all undefined names when it encounters a library, and if the simulator library appears before some of your files,
g++may use the default code given in the library rather than the code given in your files.
Your-files-here is a list of the files containing your operating-system
implementation. You should have at least one file, and may have as many files
as you feel necessary. If you're building simulators by hand - that is,
without a make file - you should list the source versions (the
versions) of your files; this insures that your object files are created using
the most recent versions of the simulator. If you build your simulator using a
make file, and you've got the compilation dependencies set correctly, you can
use the object versions (the
.o versions) of your files.
Using a make file is simpler and saves you some time because it avoids unnecessary compilations. However, if you get the compilation dependencies wrong, you could build a simulator using obsolete code. Compiling by hand is slower, because you're always recompiling everything every time, but it makes sure you're creating a simulator using up-to-date code. The miscellaneous project directory contains an example make file you can adapt to build your simulator.
Your-options-here is a list of
g++ options. See the
page (there is no
g++ man page) for a full list of options; two important
-goption, but the debugger won't have access to all the information it would have had had
-gbeen used. If you are creating object files, they too must be compiled with
-gto provide extra information; the files in the simulation library have been compiled with
g++the location of the simulator include files. The
-Ioption is needed only when compiling
my-sim, you can run it by typing
my-sim(if you get an error message like
my-sim: not found, try typing
By default, the simulator looks in its current directory for the
terminal simulation file
tty and the disk simulation file
dsk. You can use the
-d dfile and
-t tfile simulator
options to use the file named dfile as the disk simulation file and the
file named tfile as the terminal simulation file. The simulator only
opens a device simulation file if the device is actually used during a
The simulator does not change the device simulation files; the same files may
be used repeatedly, independent of the i-o operations performed on the device.
At the end of a simulation, the simulator creates the files
results.tty. The file
represents the state of the disk at the end of the simulation; the
results.tty contains all the data sent to the terminal during the
simulation. If a device is not written to during a simulation, the
associated results file is not created.
-Dflag-list command-line option controls which of the
writes output to
std::cerr. The argument flag-list is a comma
separated list of debugging-print flags; spaces should not appear in the list.
As a convenience, the
dbp_ prefix may be dropped from the flags appearing
-Df10,f20 is equivalent to
debugp() call includes one of the listed flags in its
then it will write to
std::cerr, otherwise, it produces no output. For
example, given the
debugp(dbp_f17 | dbp_f23, "The time is %d.\n", read_clock());
The command-line options
-Df17 would cause the call to
produce output, while
-Df30 would not.
The simulator defines some flags, which you may find useful:
disk_io- output disk i-o information.
tty_io- output terminal i-o information.
addrt- output virtual addresses. Virtual addresses are output in binary as two-byte values in big-endian order. The addresses are written to the file named
address-tracein the directory in which the simulator is running.
ins_cnts- output instruction counts on system halt.
intr- output interrupt information.
These flags may be mixed with the
dbp_ flags in a
-D option, as in
This page last modified on 13 November 2004.