AcknowledgmentSoftware developed for this mini-project, namely mtuThread, was sponsored by National Science Foundation. Click here to learn more about this project. Major developers include Xianlong Huang, Ping Chen, Budirijanto Purnomo, J. R. Lewis III, and Tin-Tin Yu. Of course, not all of their works appeared in this scaled-down and simplified version. |
The language of choice is ANSI C.
|
The first argument is a function to be run as a thread. The second argument is the size of the stack for running this thread. You can always use the default value THREAD_SIZE for this project. The third argument is either THREAD_NORMAL or THREAD_SUSPENDED. THREAD_NORMAL means that the created thread will start running once it is created, while THREAD_SUSPENDED means the created thread will be suspended until it is resumed by another thread. The fourth and fifth arguments are equivalent to argc and argv, respectively, used in a main program. When a thread is run, it should take the same action as in a main program for retrieving the number of arguments argc and each of the arguments in the argument list argv. However, since all programs for this project are easy, we only use argc, the fourth argument, for passing an integer to a thread. See sample programs for the details. This function returns a structure of type THREAD_t that contains vital information of the created thread. Otherwise, it returns mtu_ERROR.THREAD_t THREAD_CREATE(void (*)(), int, int, int, char **);
int THREAD_EXIT(void);
Since this system uses a non-preemptive scheduling policy, THREAD_YIELD() is needed to ensure a fair use of the CPU.void THREAD_YIELD(void);
THREAD_t THREAD_SELF(void);
The only argument of this function is a variable of type THREAD_t indicating the thread to be joined.int THREAD_JOIN(THREAD_t ID);
The only argument of this function is a variable of type THREAD_t indicating the thread to be suspended. The thread to be suspended should not be a suspended one; otherwise, mtu_ERROR will be returned. If successful, this function returns mtu_NORMAL.int THREAD_SUSPEND(THREAD_t ID);
The only argument of this function is a variable of type THREAD_t indicating the thread to be resumed. The thread to be resumed must be a suspended one; otherwise, mtu_ERROR will be returned. If successful, this function returns mtu_NORMAL.int THREAD_RESUME(THREAD_t ID);
All functions are in file new_mtuThreadTop.c. Note that implementing functions THREAD_JOIN(), THREAD_SUSPEND() and THREAD_RESUME() is your job. As a result, only dummy templates are provided in file new_mtuThreadTop.c.
There are four functions for mutex locks:
If a mutex lock is created successfully, this lock is returned as a function value of type MUTEX_t; otherwise, this function returns mtu_ERROR.MUTEX_t MUTEX_INIT(void);
If mutex lock Lock is destroyed successfully, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.int MUTEX_DESTROY(MUTEX_t Lock);
If mutex lock Lock is locked successfully, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.int MUTEX_LOCK(MUTEX_t Lock);
If mutex lock Lock is unlocked successfully, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR. Note that only the owner of a lock (i.e., the thread that locked the lock previously) can unlock a lock.int MUTEX_UNLOCK(MUTEX_t Lock);
Full implementations of these four functions are in file mtuThreadMUTEX.c. Read the code carefully because you will need it for implementing semaphores.
There are four functions for counting semaphores:
If a semaphore with initial value count is created successfully, this semaphore is returned as a function value of type SEM_t; otherwise, this function returns mtu_ERROR.SEM_t SEMAPHORE_INIT(int count);
If semaphore Semaphore is destroyed successfully, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.int SEMAPHORE_DESTROY(SEM_t Semaphore);
If semaphore Semaphore is signaled successfully, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.int SEMAPHORE_SIGNAL(SEM_t Semaphore);
If waiting on semaphore Semaphore is successful, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.int SEMAPHORE_WAIT(SEM_t Semaphore);
Only dummy templates of these four functions are in file new_mtuThreadSEM.c, because it is your job to implement semaphores. Note that type SEM_t is defined as a pointer to a structure SEMAPHORE. A possible data structure is provided to you in file mtuThread.h. Feel free to modify it for your implementation.
There are four functions for channels. This is a simplified and scaled-down version of ThreadMentor.
If a channel is created successfully, it is returned as a function value of type CHANNEL_t; otherwise, this function returns mtu_ERROR. The first argument name provides a name to the channel, the second argument type should be ASYNCHRONOUS, and the third argument mode should be MANYTOMANY. Therefore, channels in this project are many-to-many asynchronous channels with infinite capacity. Here, MANYTOMANY means every thread can send messages to and receive messages from the same channel (i.e., non-blocking send and blocking receive), and the sender may receive the message it just sent!CHANNEL_t CHAN_INIT(const char *name, int type, int mode)
If channel channel is destroyed successfully, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.CHAN_DESTROY(CHANNEL_t channel);
The data pointed at by stuff of size size is sent to channel Channel. If this send is successful, this function returns mtu_NORMAL; otherwise, this function returns mtu_ERROR.int CHAN_SEND(CHANNEL_t Channel, void *stuff, int size)
If a message is received from channel Channel successfully, it is stored into *output and returns mtu_NORMAL. Otherwise, this function returns mtu_ERROR. The size of the received message is returned in size.int CHAN_RECV(CHANNEL_t Channel, void **output, int *size)
Only four dummy templates of these four functions are provided to you in file new_mtuThreadCHAN.c, because it is your job to implement channels. Note that type CHANNEL_t is defined as a pointer to a structure CHANNEL. A possible data structure is provided to you in file mtuThread.h. Feel free to modify it for your implementation.
More importantly, channel should use non-bloking send and blocking receive. You may arrange the messages in the FIFO order.
The most important function is THREAD_CREATE() for creating threads. Function THREAD_CREATE() has the following prototype:
THREAD_t THREAD_CREATE(
void (*Entry)(), /* function to be run as a thread*/
int Size, /* stack size for this thread */
int status, /* running or suspended? */
int Argc, /* # of arguments to be passed */
char **Argv); /* argument list */
The first argument is a pointer to a function that will run as a thread.
In fact, when you call
THREAD_CREATE(), the first
argument is simply the function name.
Note that this function has a return type of
void,
which means it does not return anything.
When this function runs as a thread, the size of the stack space assigned to
it is specified by the second argument.
You may use
THREAD_SIZE, which is defined in
mtuThread.h, for this project.
The third argument specifies if the created thread should be suspended.
If it should be suspended, use
THREAD_SUSPENDED; otherwise,
use THREAD_NORMAL. The last two
arguments, Argc and
Argv, are exactly the same
as they do in a main program (i.e., retrieving arguments from a command
line). More precisely, you should
collect the arguments to be passed to a thread into an argument list and
passed as Argv.
The number of arguments, which is an integer, is passed as
Argc.
In this project, since we only pass a single integer to a thread, we shall
ignore Argv and only use
Argc for passing an integer.
Therefore, Argc does not have
the same meaning as mentioned above.
The following is an example in file pingpong.c. It has of two threads, Ping() and Pong().
#include <stdio.h>
#include <stdlib.h>
#include "mtuThread.h"
void Ping(int); /* thread prototypes */
void Pong(int);
SEM_t StopPing, StopPong;
int Count;
void Ping(int ID)
{
while (1) { /* loop forever */
SEMAPHORE_WAIT(StopPing); /* stop until released */
printf("Ping-"); /* print Ping- */
SEMAPHORE_SIGNAL(StopPong); /* release Pong */
}
THREAD_EXIT();
}
void Pong(int ID)
{
do { /* loop for 'Count' times */
SEMAPHORE_WAIT(StopPong); /* stop until released */
printf("Pong\n"); /* append Pong to Ping- */
SEMAPHORE_SIGNAL(StopPing); /* release Ping */
Count--;
} while (Count > 0); /* done if Count = 0 */
THREAD_EXIT();
}
int main(int argc, char *argv[])
{
THREAD_t THREAD_Ping, THREAD_Pong;
Count = 10;
if (argc != 2) {
printf("Use %s #-of-iterations\n", argv[0]);
printf("No. of iterations is set to %d\n", Count);
}
else
Count = abs(atoi(argv[1]));
StopPing = SEMAPHORE_INIT(1); /* Ping goes first */
StopPong = SEMAPHORE_INIT(0); /* Pong stops until informed*/
THREAD_Ping = THREAD_CREATE(Ping, THREAD_SIZE, THREAD_NORMAL,
1, (char **) 0);
THREAD_Pong = THREAD_CREATE(Pong, THREAD_SIZE, THREAD_NORMAL,
2, (char **) 0);
THREAD_JOIN(THREAD_Pong); /* can only join Pong() */
return 0;
}
This example first creates two semaphores
StopPing and
StopPong. Then, two threads are
created running functions
Ping() and
Pong(). Note that
(1) we use default stack size
THREAD_SIZE;
(2) both threads will run once they are
created since THREAD_NORMAL
is used;
(3) the fourth argument is 1
(resp., 2) for the thread running
Ping()
(i.e., Pong());
(4) the fifth argument is
(char **) 0 since no
argument list is passed to the created threads;
and
(5) function
THREAD_CREATE()
returns the information structure of the created thread into a
variable of type THREAD_t,
which is used for calling
THREAD_JOIN().
The remaining of this program is clear since we have encountered it a number
of times in class. Note that threads
Pong() terminates itself after
Count iterations; however,
Ping() loops forever. This may
cause a problem. After
Pong() is done,
Ping() might not be able to
run further because the thread that can signal it (i.e., thread
Pong()) has terminated. This
system will detect that no thread is in the
ready queue and yet the system is still running! Hence, it might
tell you that a deadlock is likely to occur, although you know this is not
the case. The scheduler written in this way is to show
you that a scheduler indeed can warn you for possible deadlocks.
Please refer to philosophers (philo.c) , philosophers with four seats (philo-4.c), and philosophers with a righty (philo-w.c) for further examples that use mutex locks and semaphores.
The next example shows the use of channels to solve the producer and consumer problem. See buffer.c for the details.
#include <stdio.h>
#include <stdlib.h>
#include "mtuThread.h"
void Producer(int);
void Consumer(int);
CHANNEL_t Chan;
void Producer(int ID)
{
int i, data;
for (i = 0; ; i++) {
data = ID*1000 + i;
CHAN_SEND(Chan, &data, sizeof(int));
printf("Producer %d has sent data %d\n", ID, data);
THREAD_YIELD();
}
THREAD_EXIT();
}
void Consumer(int ID)
{
int *data, size;
while (1) {
CHAN_RECV(Chan, (void**) &data, &size);
printf(" Consumer %d received %d\n", ID, *data);
THREAD_YIELD();
}
THREAD_EXIT();
}
int main(int argc, char *argv[])
{
THREAD_t Producer1, Producer2, Consumer1, Consumer2;
int i;
char ch;
printf("Use Ctrl-C to kill this program\n");
printf("Hit any key to continue... ");
ch = getchar();
printf("\n");
Chan = CHAN_INIT("Channel", ASYNCHRONOUS, MANYTOMANY);
Producer1 = THREAD_CREATE(Producer, THREAD_SIZE, THREAD_NORMAL,
1, (char **) 0);
Producer2 = THREAD_CREATE(Producer, THREAD_SIZE, THREAD_NORMAL,
2, (char **) 0);
Consumer1 = THREAD_CREATE(Consumer, THREAD_SIZE, THREAD_NORMAL,
1, (char **) 0);
Consumer2 = THREAD_CREATE(Consumer, THREAD_SIZE, THREAD_NORMAL,
2, (char **) 0);
THREAD_JOIN(Producer1);
return 0;
}
This program creates two producers and two consumers. The producers
keep sending integer messages to channel
Chan, while consumers
keep receiving integer mail messages from channel
Chan. Note the use of
THREAD_YIELD().
Program psort.c shows a very interesting way of using channels. This program uses multiple threads to sort integers. Please refer to the program file for the details. In this program, 10 threads (the sorters), S1, S2, ..., S10 are created. Each of these threads, say Si, has a channel inChan shared with its predecessor Si-1, and a channel outChan shared with its successor Si+1. There is a generator G and a terminator T. The generator has a channel shared with S1 and the terminator T has a channel shared with S10.
The generator G sends ten random non-negative integers into the channel shared with S1. After that, G sends an end message END, indicating that there is no more data so that the job ends.
For each sorter, say Si, it first receives a number from its predecessor via its inChan and memorizes it. Then, Si receives a new number via inChan. If this new number is an END, there is no more incoming numbers and the number memorized by Si is printed. Then, Si passes END to its neighbor and returns. If the new number is not an END, we have two cases to consider:
The job for terminator T is simple. It could only receive the END message. (Why?) Once T receives the END message, it exits the whole program. Please convince yourself that the output from sorters is in increasing order.
The following lists the places where your work is required.
As you can see from the filenames, all programs are written in C. Consequently, you should use ANSI C rather than C++ for this mini-project. Without observing this requirement, it is likely we may not be able to compile and test your programs successfully, and you may not receive proper credit for your work.
Write a program smokers-sem.c that only uses semaphores to solve the smokers problem. When the agent has supplied ingredients n times, the system should stop running, where n should be taken from the command line. Your program should not have race conditions, busy waiting, and deadlock.
Write a program exchange.c that only uses channel for communication. Your main program should generate and display ten random numbers and put them into the global array. Finally, you have to find some way to display the sorted result.
where xyz is a program name (e.g., exchange). Note that since we will use our original version of alter1.c, alter2.c, pingpong.c, philo.c, philo-4.c, philo-w.c, buffer.c and psort.c to test your implementation, any changes you made to these programs will have no effect. Therefore, you should not make any change to these sample programs. We will also run your smokers-sem.c, exchange.c and ring-leader.c under our correct implementation to see if you have submitted correct solutions.gcc xyz.c mtuThread.c -o xyz xyz
Two correct implementations of this system are provided in binary files mtuThread-Linux.o and mtuThread-Solaris.o, you may copy any of these to mtuThread.o in order to check your implementation with the following:
gcc xyz.c mtuThread.o -o xyz xyz
Note that the -D compiler command line switch can be used to select a version to compile your program on Fedora Linux or on Solaris. The default (i.e., no -D compiler switch used) is Fedora Linux. Read the source files to figure it out how to compile on Solaris.
By the way, we expect you to use the gcc compiler.
Click here to see the grading sheet.
In addition to running test programs above, we will also use other programs. The correctness of your implementation will be evaluated with all test programs.
You will see the following files in your directory:gzip -d proj.tar.gz tar xvf proj.tar
The length of your writing is not proportional to the number of points you will receive.enscript -2r -G README
You should enclose any place where you made a change and/or addition with #ifdef-#endif as follows:
#ifdef CHANGED
put your changes/modifications/additions here
#endif
In this way, any modification/addition to the original files
can be found easily. When you submit your program, don't forget to define
CHANGED at the very beginning of
mtuThread.h.
However, if you only modify
new_mtuThread*.c, this mechanism is not
required.