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 may 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 this function 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:
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.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 following is an example in file pingpong.c. It has two threads, Ping() and Pong().
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.#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; }
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.
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().#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; }
Program psort.c shows a very interesting way of using channels. This program uses multiple threads to sort non-negative integers. 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 prints its number, passes END to its neighbor, and exits. 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. 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:
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.#ifdef CHANGED put your changes/modifications/additions here #endif