All your work for this assignment will take place within the
threads
directory.
Although you might find it helpful to look
at files outside this directory, you should not make any
changes to files other than those in the threads
directory.
In general, if a source code file has the following message at the
beginning:
In this assignment, and in all subsequent assignments in this course, you will be given very detailed specifications concerning particular functions or classes you are to implement. Though you will generally have considerable flexibility in exactly how you implement these functions, it is extremely important that the names of these functions and their behaviors are exactly as specified. If an assignment tells you to implement a function or class with a particular name and/or prototype, do not implement something with a different name or prototype.
In this course, we are going to perform a great deal of the building and testing of your software automatically. We will write our own driver functions which we will link with your code. If you adhere exactly to the specifications, everything should work properly. If not, your code will require manual intervention to test, and the large number of students in the class will probably make this impossible for us.
One other thing that will make manual intervention necessary is if your code produces extraneous printout when it runs. You should therefore see to it that your code, as submitted, should produce no printout other than that originally present in Nachos, or that specifically indicated in the assignment. In addition, if the assignment specifies that you are to produce printout, your printout must appear exactly as specified. Do not add extraneous newlines, extra characters, or change in any way the format of the messages you are told to produce.
You will probably find it useful to incorporate debugging printout into your code during development and testing. This is OK, but all such code should use the NACHOSDEBUG
macro defined in threads/utility.h, so that
debugging printout is not produced unless a suitable command-line flag
is provided. Examples of the use of this macro appear throughout the
NACHOS code. Every time you modify a NACHOS file, you should surround all of your changes with preprocessor commands that make it easy to compile the code without your changes.
When the ``threads'' version of NACHOS is started, it initially
creates a single thread that begins executing the function
ThreadTest()
in the file threadtest.cc. This function
creates a new thread that calls the function
SimpleThread(1)
, and the original thread calls the
function SimpleThread(0)
. The two threads each execute
the loop in the SimpleThread()
function, in which they
yield control of the CPU back and forth five times.
Modify the function ThreadTest()
to take a single integer
argument n
, so that its prototype becomes:
ThreadTest()
should fork
n
new threads instead of just one.
Test your function on values of n
from zero to four.
You will have to change the file
main.cc
to supply
the required integer argument to ThreadTest()
.
Next, modify function SimpleThread()
to read
exactly as follows:
ThreadTest()
set to 0, 1, 2, 3, and 4.
Analyze and explain the results.
Put your explanation in the file threads/HW1_WRITEUP
.
The file synch.cc contains an
implementation of the semaphore operations
Semaphore::P()
and Semaphore::V()
.
Modify SimpleThread()
by introducing calls to the semaphore
operations, so that accesses to the shared variable are properly synchronized.
Try your synchronized version of SimpleThread()
with the
argument to ThreadTest()
set to 0, 1, 2, 3, and 4.
Access to the shared variables are properly synchronized if each
iteration of the loop increments the variable by exactly one and each
thread sees the same final value. These observations should hold when
random interrupts are turned on using the -rs
option It
may be necessary to implement a "barrier" in order to allow all
threads to wait for the last to exit the loop.
Using the #ifdef
/#endif
preprocessor construct,
bracket your synchronization code so that its compilation is controlled by
the preprocessor symbol HW1_SEMAPHORES
.
That is, if the preprocessor symbol HW1_SEMAPHORES
is defined,
the synchronization code should be included, otherwise the synchronization
code should not be included.
Recompile NACHOS both ways to make sure our "conditionalized" code works
properly.
Lock
operations that are missing from the file
synch.cc.
Locks are used to ensure mutual exclusion between threads.
At any time, a lock is either free, or else it is held
by a thread. At most one thread at a time can hold a lock.
If a thread tries to acquire a lock that is currently being held
by another thread, the requesting thread will block (sleep)
until the thread that holds the lock releases the lock. There are four missing functions you must implement:
Lock::Lock(char* debugName)
:
This constructor function initializes a lock object.
The debugName
argument is a string supplied by the
caller, which should just be stored into the new Lock
structure. Its purpose is simply to help distinguish various instances
of locks in debugging printout.
Lock::~Lock()
:
This function deallocates a lock object, when it is no longer needed.
void Lock::Acquire()
:
This function waits for a lock to become free and then acquires
the lock for the current thread.
void Lock::Release()
:
This function releases a lock that was previously acquired by the
current thread, and wakes up one of the threads
waiting for the lock.
Semaphore
code as a
guide when writing the Lock
code. Locks are very much
like semaphores with initial value 1.
Test your code by replacing the Semaphore
operations you
added to SimpleThread()
by corresponding
Lock
operations, and verifying that the demonstration
still works properly. Conditionalize your code so that its
complication is controlled by the preprocessor symbol
HW1_LOCKS
. (Be sure that you can still compile your code
with HW1_SEMAPHORES
defined to get the semaphore
demonstration.)
Condition
operations that are missing from the file
synch.cc.
Conditions are used to ensure proper synchronization among threads.
The specifications for this primitive appear in the synch.h
module.
There are five missing functions you must implement:
Condition::Condition(char* debugName)
:
This constructor function initializes a condition object.
The debugName
argument is a string supplied by the
caller, which should just be stored into the new Condition
structure. Its purpose is simply to help distinguish various instances
of conditions in debugging printout.
Condition::~Condition()
:
This function deallocates a condition object, when it is no longer needed.
void Condition::Wait(Lock* conditionLock)
:
This function waits for a condition to become free and then acquires
the condition for the current thread.
void Condition::Signal(Lock* conditionLock)
:
This function wakes up one of the threads that is waiting on the condition.
void Condition::Broadcast(Lock* conditionLock)
:
This function wakes up all threads that are waiting for the condition.
Semaphore
and Lock
code as a
guide when writing the Condition
code.
available[NMACHINES]
whose elements
are non-zero if the corresponding machines are available (NMACHINES
is a constant indicating how many machines there are in the laundromat), and a
semaphore nfree
that indicates how many machines are available.
The code to allocate and release machines is as follows:
available
array is initialized to all ones, and nfree
is initialized to NMACHINES.
threads/HW1_WRITEUP
.
When you submit your assignment, make sure that all preprocessor
directives (HW1_SEMAPHORES, HW1_LOCKS,
HW1_ELEVATOR, HW1_OFFICE
) are disabled. That is, your
submission should simply output the Nachos default. We will
automatically build your submission with different preprocessor
directives. In order to do that, it is necessary that you leave
the $(DEFINES)
variable in the compiler invocation in
Makefile.common
. You can add directives to the
compilation process as you like, but we will use the
DEFINES
variable to pass our defines to the
compiler. Note that you do not need to do anything right
away. Only when you substantially change the Makefile(s), it is
necessary for you to make sure that the DEFINES
variable can still be used to pass arguments to the compilation
process.
You have to provide a writeup in a file
You can turnin up to 3 times per project and not more than that!
The earlier versions will be discarded.
HW1_WRITEUP
in which you have to mention
which exercises you have been able to complete. And for the ones
that are incomplete, what is their current status. The
description of status for completed exercises or those that you
did not start should not be more than 1 line. However, when you
submit code that is not working properly (partial solution), make
sure that your documentation is very verbose so that the
TAs can understand what you have achieved and which parts are
missing. This is necessary to receive at least partial
credit. When you just submit something that does not work and give
no explanations, expect to receive no credit. Also include both group members' names
and a short note listing all office hours that both group
members can attend. Make sure that the writeup file is in the top
level code directory. You may or may not be asked to arrive during that
hour for a 10 minute interview (See
Note: only one turnin per group is accepted!