ringbuf.c Specification
You'll probably want to keep a copy of this page open in your browser for reference.
You will be writing a program named ringbuf in a file called ringbuf.c.
We have provided a Makefile and starter code for
you; you should familiarize yourself with the
starter code—especially the sections marked with NEEDSWORK
comments, which are the places where you will need to make changes.
Be sure to document the names of both team members in comments at the top of the file.
Useful information for your implementation appears later on this page.
Your program must be implemented using POSIX Threads (pthreads). There will be two threads: a producer and a consumer. The producer will read information from standard input and place it into the ring buffer. The consumer will extract information from the buffer and perform certain operations.
Important Rules
-
You may NOT use semaphores of any type to implement your solution. This includes implementing a semaphore construct yourself by building on more primitive thread constructs. You also may not implement a solution that uses any type of polling, regardless of whether or not the polling wastes the CPU. (In other words, your implementation cannot repeatedly check whether the buffer is full or empty, then “wait a while” before checking again. If your producer is capable of seeing two buffer-full conditions in a row without inserting anything in the buffer, or if your consumer can see two buffer-empty conditions without removing anything, you have implemented polling and must come up with a different solution.)
-
The main program we have provided creates a thread that runs the consumer and then calls the producer's procedure directly; this effectively makes the main thread the producer thread. After the consumer terminates, the main thread collects it with
pthread_joinand exits. (In an alternate design, the main program could create and collect two new threads.) -
The main program also creates an error-checking mutex. If you create more mutexes, imitate the
pthread_mutex_initcalls inmain(). Do not usePTHREAD_MUTEX_INITIALIZER; it will create a mutex that ignores errors and make your program much harder to debug. -
All library and system calls should be error-checked. If an error occurs, print an informative message and terminate the program.
CRITICALLY IMPORTANT: Do not wait until you have debugged your program before adding error checking. The most common cause of bugs in this (and any) assignment is mistakes in invoking system calls. If you error-check every call as you write it, you'll quickly discover those mistakes. If you don't, you'll waste hours of debugging before you figure out what you did wrong.
-
Note that the
pthread_functions return errors differently than most system calls. See the example error handling inmain()and imitate it throughout your program.
The Shared Buffer
The producer and consumer will communicate through a shared buffer
that has 10 slots (the size is set by a #define so that it's easy
to change). Each slot in the buffer has the following structure:
struct message {
int value; /* Value to be passed to consumer */
int consumer_sleep; /* Time (in ms) for consumer to sleep */
int line; /* Line number in input file */
int print_code; /* Output code; see below */
int quit; /* Nonzero if consumer should exit ("value" ignored) */
};
These fields have the following purposes:
value- The actual data to be passed to the consumer; in this example the consumer will sum the values passed in.
consumer_sleep- A time (expressed in milliseconds) that the consumer will expend in consuming the buffer entry.
line- The line number in the input file that this data came from. Line numbers start at 1, not zero!
print_code- A code indicating whether the consumer or producer should print a status report after consuming or producing this line.
quit- For all buffer entries except the last, this value should be zero.
For the last entry, it should be nonzero. The consumer should
not look at any of the other fields in the message if
quitis nonzero.
Besides the shared buffer itself, you will need a number of auxiliary variables to keep track of the buffer status. These might include things such as the index of the next slot to be filled or emptied. You will also need some pthreads “conditions” and “mutexes.” The exact set is up to you.
The Producer
The basic task of the producer is to read one line at a time from the standard input. For each line, it will first sleep for a time given in the line, then pass the data to the consumer via the ring buffer. Finally, after the message has been placed in the ring buffer, the producer will optionally print a status message. Printing is slow, so the producer must not hold any mutexes while it's printing.
Each input line consists of four numbers, separated by spaces, as follows:
- The value to be passed to the consumer.
- An amount of time the producer should sleep, given in milliseconds. Note that the sleep must be done before placing information in the ring buffer.
- An amount of time the consumer should sleep, given in milliseconds.
- A “print code” integer that indicates what sorts of status lines should be printed.
The producer reads these four numbers using the C library function
scanf (see man scanf for more information. Feel free to do an
Internet search for examples of using scanf). When scanf fails,
either by returning an EOF indication (i.e., the return value from
scanf is the value EOF) or by returning fewer than four values,
the producer should enter one more message in the ring buffer,
without sleeping first. This message should contain a nonzero
quit field; the other fields will be ignored by the consumer.
The print codes are interpreted as follows:
| Code | Meaning |
|---|---|
0 |
No messages are printed for this input line. |
1 |
The producer generates a status message. |
2 |
The consumer generates a status message. |
3 |
Both the producer and consumer generate status messages. |
Note that the print codes can be viewed as a bit mask and thus they
can be interpreted using C's & operator.
The producer's status message should be generated after the data
has been passed to the consumer via the ring buffer. It must print
the value and line number from the input line, and be produced by
calling printf with exactly the following format argument:
"Produced %d from input line %d\n"
The Consumer
The consumer waits for messages to appear in the buffer, extracts them, and then processes them (the “processing” just involves adding the value to a running sum). Note that the consumer does not act on the message until after it has been removed from the buffer, so that the producer can continue to work while the consumer is processing the message.
If the extracted message has a nonzero quit field, the consumer
does not add the value to the sum, but simply prints the total it
has calculated, using the following printf format:
"Final sum is %d\n"
It then terminates its thread without sleeping.
Otherwise (quit is zero), the consumer first sleeps for the
specified time and then adds the value field to a running total
(initialized to zero), and finally optionally prints a status
message if the print_code is 2 or 3. The status message must be
generated by calling printf with exactly the following format
argument:
"Consumed %d from input line %d; sum = %d\n"
IMPORTANT The consumer must sleep before updating the running total, and before printing the status message. If you print before you sleep, your output won't match the samples.
Useful Information
You will need to use a number of Unix system and C library calls.
You can read the documentation on these calls by using man. For
example, to learn about pthread_mutex_lock, type man
pthread_mutex_lock. (Sometimes you need to specify the section of
the manual you want, for example with man 3 printf, but usually
that's not needed. The calls you will need to use are all
documented in sections 2 and 3 of the manual.) If some manual pages
are not available on wilkes.cs.hmc.edu, you can usually find them
online using an Internet search.
You should try to develop familiarity with the style of Unix manual pages. For example, many man pages have a “SEE ALSO” section at the bottom, which will lead you to useful related information.
NOTE Some of the pthreads manual pages are taken directly from
the Posix standard, which is written in a style that no rational
human can be expected to understand. If you encounter a manual page
that makes heavy use of the word “shall” and is otherwise
incomprehensible, we suggest that you search the Internet for a
clearer discussion. For example, we had good luck searching for
pthread_cond_wait example.
To make sure you get the best grade even if there are bugs in your
solution, we have provided the following line at the top of the
main function:
setlinebuf(stdout);
That line will ensure that when your program is run with standard
output redirected to a file; any partial output will appear even if
your program hangs. Speaking of that, you should test your program
with redirected output; redirection changes the timing and reveals some
bugs that won't appear if you only test with output to the terminal.
You can redirect the output with the > sign; for example,
./ringbuf < testinput1.txt > myoutput1.txt
(When logged in, completion status appears here.)