3. Setting Up

Don't forget to setup your environment by running:

% 3231

Your group account

You will do Assignment 1 as part of a two-person group. If you are not
yet in a group, post to the appropriate message board on the cs3231 forum to
find a partner. You must nominate your partner, and they must nominate
you, via the group nomination form (under "Administration" on the left-hand
side).

You will be notified by email when your group is created, which usually
happens 24–48 hours after the partners have nominated each other. Check
the group nomination page for your group number. A group account will have been
created for you in /home/osprjXXX, where XXX is your three-digit group
number. For example, if you are a member of group 103, your group account is
/home/osprj103.

Set up your group account

For assignment 0, you used the Subversion (SVN) revision control system to
keep track of changes and to produce a file that you could submit. For this
assignment, you will also use SVN. However, you have to do some extra set-up
because you will be collaborating with another person on the assignment.

Before you start, both you and your partner will need to modify your
umask so you and your partner to share the assignment files (if
you're interested, see man umask for details). Do this by modifying
your .profile in your home directory. Change the umask
command to be the following:

% umask 007

Now, whenever you log in, your umask will be set appropriately. Either
log out and log back in again now or run the command source
.profile to ensure your umask is set.

Obtain the assignment sources

Only one group member should do the following.

For this assignment, you will set up an SVN repository in your group
account directory (/home/osprjXXX). You may remember the repo
directory you created for assignment 0. For assignment 1, you will be creating
this repository in your group account directory. Initialise this repository
now:

% cd /home/osprjXXX
% svnadmin create repo

Once again, this repository directory will be completely maintained for
you by SVN. Now import the sources into your new repository in a similar
way to assignment 0:

4. Begin Your Assignment

Configure OS/161 for Assignment 1

Before proceeding further, configure your new sources.

% cd ~/cs3231/asst1-src
% ./configure

If you need to re-install the user-level utilities, do the following:

% bmake
% bmake install

We have provided you with a framework to run your solutions for
ASST1. This framework consists of driver code (found in kern/asst1)
and menu items you can use to execute your solutions from the OS/161
kernel boot menu.

You have to reconfigure your kernel before you can use this
framework. The procedure for configuring a kernel is the same as in
ASST0, except you will use the ASST1 configuration file:

% cd ~/cs3231/asst1-src/kern/conf
% ./config ASST1

You should now see an ASST1 directory in the compile directory.

Building for ASST1

When you built OS/161 for ASST0, you ran bmake from compile/ASST0. In
ASST1, you run bmake from (you guessed it) compile/ASST1.

% cd ../compile/ASST1
% bmake depend
% bmake
% bmake install

If you are told that the compile/ASST1 directory does not exist, make
sure you ran config for ASST1.
Run the resulting kernel:

Command Line Arguments to OS/161

Your solutions to ASST1 will be tested by running OS/161 with command
line arguments that correspond to the menu options in the OS/161 boot
menu.

IMPORTANT: Please DO NOT change these menu option strings!

Here are some examples of using command line args to select OS/161
menu items:

sys161 kernel "at;bt;q"

This is the same as starting up with sys161 kernel, then running "at"
at the menu prompt (invoking the array test), then when that finishes
running "bt" (bitmap test), then quitting by typing "q".

sys161 kernel "q"

This is the simplest example. This will start the kernel up, then quit
as soon as it's finished booting. Try it yourself with other menu
commands. Remember that the commands must be separated by semicolons
(";").

"Physical" Memory

HEADS UP!!!! Make sure you do the
following. Failing to do so will potentially lead to subtle
problems that will be very difficult to diagnose.

In order to execute the tests in this assignment, you will need more
than the 512 KiB of memory configured into System/161 by default. We
suggest that you allocate at least 2 MiB of RAM to System/161. This
configuration option is passed to the mainboard device with the
ramsize parameter in your ~/cs3231/root/sys161.conf
file. Make sure the mainboard device line looks like the following:

31 mainboard ramsize=2097152 cpus=1

Note: 2097152 bytes is 2 MiB.

A sample pre-configured sys161 configuration can be downloaded here:
sys161-asst.conf.

7. Coding Assignment

We know: you've been itching to get to the coding. Well, you've
finally arrived!

This is the assessable component of this
assignment.

The following problems will give you the opportunity to write some
fairly straightforward concurrent programs and get a more detailed
understanding of how to use concurrency mechanisms to solve
problems. We have provided you with basic driver code that starts a
predefined number of threads that execute a predefined activity (in
the form of calling functions that you must implement or modify).

Remember to specify a seed to use in the random number generator by
editing your sys161.conf file, and run your tests using Sys/161
command line args. It is much easier to debug initial problems when the
sequence of execution and context switches is reproducible.

When you configure your kernel for ASST1, the driver code and extra
menu options for executing your solutions are automatically compiled in.

Part 1: Concurrent Mathematics Problem

For the first problem, we ask you to solve a very simple mutual
exclusion problem. The code in kern/asst1/math.c counts from
0 to 10000 by starting several threads that increment a common
counter.

You will notice that as supplied, the code operates incorrectly and
produces results like 345 + 1 = 352.

Once the count of 10000 is reached, each thread signals the main thread
that it is finished and then exits. Once all adder() threads
exit, the main (math()) thread cleans up and exits.

Your Job

Your job is to modify math.c by placing synchronisation
primitives appropriately such that incrementing the counter works
correctly. The statistics printed should also be consistent with the
overall count.

Note that the number of increments each thread performs is dependent
on scheduling and hence will vary. However, the total should equal the
final count.

To test your solution, use the "1a" menu choice. Sample output from a
correct solution in included below.

Part 2: Bounded-buffer producer/consumer problem

Your second task in this assignment is to implement a solution to a standard
producer/consumer problem. In the producer/consumer problem one or more
producer threads put data into a fixed-sized buffer while one or more
consumer threads process information from the same buffer.

The code in kern/asst1/producerconsumer_driver.c starts up a number
of producer and consumer threads. The producer threads attempt to communicate
with the consumer threads by calling the producer_produce() function
with a data structure. In turn, the consumer threads attempt to receive
information from the producer threads by calling consumer_consume().
Unfortunately, these functions are currently unimplemented. Your job is to
implement them.

The files:

producerconsumer_driver.c: Starts the producer/consumer
simulation by creating appropriate producer and consumer threads that
will call producer_produce() and consumer_consume().
You are welcome to (in fact, you are encouraged to) modify this
simulation when testing your implementation, but remember that it will
be overwritten with a standard copy when your solution is tested.

producerconsumer_driver.h: Contains prototypes for the
functions in producerconsumer.c, as well as the description
of the data structure that is passed from producer to consumer
(uninterestingly named pc_data). This file will also be
overwritten when your solution is tested.

producerconsumer.c: Contains your implementation of
producer_produce() and consumer_consume(). It also
contains the functions producerconsumer_startup() and
producerconsumer_shutdown(), which you can implement to
initialise your data structure and any synchronisation primitives you
may need.

How to implement your solution

You must implement a data structure representing a buffer capable of
holding at least BUFFER_SIZE struct pc_data items. This means
that calling producer_produce() BUFFER_SIZE times should not block (or
overwrite existing items, of course), but calling producer_produce one
more time should block, until data has been removed from the
buffer using consumer_consume(). A simple way to implement this
data structure is to use an array, though you will of course have to use
appropriate synchronisation primitives to ensure that concurrent access
is handled safely.

Your data structure should function as a circular buffer with a first-in,
first-out policy.

Part 3: Paint shop synchronisation

You're in the middle of renovating your apartment and need to purchase
some paint. You arrive at your local paint shop to find the shop in
complete chaos. Customers are receiving empty cans of paint or paint
with weird colours, the staff are fighting over the various tints used
to colour the paint, orders are getting lost, cans of paint mixed up,
some customers are waiting forever for their can of paint, while
others seems to get all the service.

Being an operating system expert, you quickly realise that the
shop's problems are related to concurrency issues between the
customers and shop staff. You volunteer your services to provide a
solution to the shop's problems, reduce the chaos, and restore order
to the store.

The Basic Paint Shop

To provide a solution, you must come to terms with the basic elements
of the paint shop that you have to work with. The shop consists of a
set of tints (such as RED, GREEN, BLUE) used to colour paint. They are
mixed with paint by shop staff in various ways according to customer
requests.

Customers bring their own empty paint can, record the tints they
require on the can itself, and give the can to shop staff to mix the paint.
The basic elements are defined in kern/asst1/paintshop_driver.h.
The actions of customers and shop staff are defined in
kern/asst1/paintshop_driver.c. See the file for detailed comments.

Customers arrive with their paint can, write their
requested colour on the can in terms of tints, submit their can as
an order to the paint shop staff and wait.

Eventually their can returns with the requested contents (exactly
as requested), they paint until the can is empty, take a short
break, and do it all again until they have emptied the desired
number of cans, then they go home.

Shop Staff are only slightly more complicated than the
customers. They take orders, and if valid (not NULL),
they fill them and serve them. When all the customers have left,
the staff go home.

An NULL order signals that the staff member should go
home.

The function runpaintshop() is called via the menu in OS/161
(item 1c). runpaintshop() does the following:

It initialises all the tint containers to have served zero doses.

It calls paintshop_open(), a routine you will provide to
set up the shop.

It then creates some threads to run as shop staff, and some more
threads to run as customers. Note these threads obviously run
concurrently.

The driver thread then waits on a semaphore for all the staff and
customers to finish, after which we print out the tint statistics for
the day.

Finally, it calls paintshop_close(), a procedure you
provide to clean up when the shop has closed.

The function mix() takes a can and associated tint request
and "mixes" the tints into the can such that the content is exactly as
requested. The tints are represented by numbers, each number
corresponds to the tint container number (and colour). The meaning of
the tint numbers are defined in paintshop_driver.h.

You can assume that all the tint containers in the shop are
infinite in size and hence will never be empty.

Have a quick look through both paintshop_driver.c and
paintshop_driver.h to reinforce your understanding of what is
going on (well, at least what is expected to go on).

Your Job

Your job is to write the functions outlined in paintshop.c
(and potentially modify paintshop.h) that perform most of the
work. Each function is described in paintshop.c.

Generally, your solution must result in the following when
runpaintshop() is called during testing.

The shop being prepared for opening.

All customers having their orders served with the correctly
tinted paint in a can. "Correct" means that each corresponding entry
in the contents array contains what was originally requested
in the requested_colours array.

The shop staff all going home after all the customers are
finished.

The shop being suitably cleaned up afterwards (allocated memory
or locks, semaphores, etc being freed).

Statistics kept on tint usage are consistent with the orders made.

You can modify paintshop_driver.c and
paintshop_driver.h to test different scenarios (e.g vary the
number and colour of paint cans ordered), but
your solution must also work with an unmodified version of the
paintshop_driver.c file.

You will have to modify paintshop.c to implement your
solution. However, your modifications have the constraint that they
must still work with an original paintshop_driver.c.

For testing, we will replace paintshop_driver.c and
.h with logically equivalent versions that may vary the numbers
of participants, and the colours requested. We may also vary the
timing of various functions. A correct solution will work for all
variations we test. Sample output from a correct solution is included below.

Before Coding!!!!

You should have a very good idea of what your attempting to do before
you start. Concurrency problems are very difficult to debug, so it's
in your best interest that you convince yourself you have a correct
solution before you start.

The following questions may help you develop your solution.

What are the shared resources (e.g. tint containers)?

Who shares what resources?

Who produces what and who consumes what (e.g. customers produce
orders consumed by staff)?

What states can the various resources be in?

What do you need to keep a count of (e.g. number of customers in
the shop)?

How does your solution prevent deadlock or starvation?

Try to frame the problem in terms of resources requiring concurrency
control, and producer-consumer problems. A diagram may help you to
understand the problem.

It is reasonable to assume co-operative subsystems within an
OS. It is difficult (impossible) to defend against malicious code
within the operating system itself. However, one can still program
defensively in the interests of detecting invalid behaviour early
for debugging purposes. Such behaviour usually signals an internal
error, with a reasonable response being to panic(). In the
case of this assignment, one can assume customers and staff follow
the protocol.

Evaluating your solutions

Your solutions will be judged in terms of its correctness, conciseness,
clarity, and performance.

Performance will be judged in at least the following areas.

Do all the staff members participate?

Can staff mix in parallel if they do not require the same tints?

Do you define critical sections larger than needed?

Documenting your solutions

This is a compulsory component of this assignment. You must
write a small design document identifying the basic issues in all of
the concurrency problems in this assignment, and then describe your
solution to the problems you have identified. For example, detail
which data structures are shared, and what code forms a critical
section. The document must be plain ASCII text. We expect such a
document to be roughly 200–1000 words, i.e. clear and to the
point.

The document will be used to guide our markers in their evaluation
of your solution to the assignment. In the case of a poor results in
the functional testing combined with a poor design document, we will
base our assessment on these components alone. If you can't describe
your own solution clearly, you can't expect us to reverse engineer the
code to a poor and complex solution to the assignment.

Place your design document in design.txt (which we have created for
you) at the top of the source tree to OS/161 (i.e. in
~/cs3231/asst1-src/design.txt).

Also, please word wrap you design doc if your have not already
done so. You can use the unix fmt command to achieve this if
your editor cannot.

8. Generating Your Assignment Submission

As with assignment 0, you again will be submitting a diff of your
changes to the original tree.

You should first commit your changes back to the repository using
the following command. Note: You will have to supply a comment on your
changes. You also need to coordinate with your partner that the
changes you have (or potentially both have) made are committed
consistently by you and your partner, such that the repository
contains the work you want from both partners.

% cd ~/cs3231/asst1-src
% svn commit

If the above fails, you may need to run svn update to bring
your source tree up to date with commits made by your partner. If you
do this, you should double check and test your assignment prior to
submission.

Testing Your Submission

Look here for information on testing and
resubmitting your assignment.

Submitting Your Assignment

Now submit the diff as your assignment.

% cd ~
% give cs3231 asst1 asst1.diff

You're now done.

Even though the generated patch should represent all the changes you
have made to the supplied code, occasionally students do something
"ingenious". So always keep your Subversion repository so that we may
recover your assignment should something go wrong.