Implement goroutines and channels for the Linux Intel 64 architecture.
void createGoroutine(void (*func)(void*),
void* arg);
This primitive creates a goroutine. The first parameter is a function that should be executed by the goroutine. The second parameter is a parameter to be passed to that function. A NULL first parameter causes a panic.
When a goroutine is created, it should be given a stack of 32768 bytes.
A FIFO list of active goroutines should be maintained. This list should be initialized upon the first call to a goroutine primitive and should initially contain the "main" goroutine, the goroutine created "automatically" when the program was created. When createGoroutine is called, the newly created goroutine should be placed on the end of the list. Note: a newly created goroutine does not start executing immediately. Rather it is simply placed at the end of the list of active goroutines.
void yieldGoroutine(void);
The yieldGoroutine primitive is non-standard and used for debugging purposes. When yieldGoroutine is called, the currently executing goroutine should be moved to the end of the list of active goroutines and the goroutine that is now at the front of the list should become the goroutine that is now executing. If there is only one active goroutine, then that goroutine continues executing.
void cleanupGoroutines(void);
This primitive frees the memory used by all goroutines, including ones that are blocked forever. The behavior is undefined if this primitive is not called by the main goroutine. The behavior is also undefined if any goroutine primitive is used after this primitive returns. Note: this primitive does not free memory used by channels.
A panic is issued by implementing and calling this function:
This function outputs an error message to stderr, where the message
string is formatted like in a call to printf.
(To support this function, you need to include
// print a formatted error message to stderr and terminate the program
void panic(char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
exit(-1);
}
stdarg.h
.)
When implementing any of the primitives for this assignment, if memory cannot be allocated, issue a panic.
void *makeChannel(int dataLength, int capacity);
Make a communication channel. The first argument is the size in bytes of the data elements to be sent down the channel. The second argument is how many data elements can be queued in the channel. (This is known as the capacity of the channel.) The function returns a handle for using the channel. A non-positive first argument causes a panic. A negative second argument causes a panic.
void sendChannel(void *channel,
void *fetchAddress);
Send a data element to a channel. The first argument is the handle for the channel. The second argument is a pointer to where the data element should be fetched. The calling goroutine will block if the channel is full. A send to a nil channel (handle is NULL) blocks forever. A send to a closed channel causes a panic. A NULL second argument causes a panic.
If a goroutine is waiting to receive from the channel, then the sender copies the data element directly to the first receiver and unblocks it.
If there is no waiting receiver and there is room in the channel buffer for a data element, then the sender copies the data element to the channel buffer.
If there is no waiting receiver and there is no room in the channel buffer, then the sender must block until either space becomes available in the channel or a receiver arrives.
If a goroutine is waiting to send to a channel, and the channel is closed while the goroutine is waiting, then a panic is issued when the goroutine is unblocked and resumes executing.
int receiveChannel(void *channel,
void *storeAddress);
Receive a data element from a channel. The first argument is the handle for the channel. The second argument is a pointer to where the data element should be stored. The calling goroutine will block if the channel is empty. The function returns a 1 if a transmitted data element is received and 0 otherwise. (This distinguishes a receive from a closed channel, which always returns a value of all 0 bits.) A receive from a nil channel (handle is NULL) blocks forever. A receive from a closed channel returns the zero value (all 0 bits) if all buffered values have already been received. A NULL second argument causes a panic.
void closeChannel(void *channel);
Close a channel. The first argument is the handle for a channel. A closed channel or a nil channel (handle is NULL) causes a panic. Goroutines who are blocked on a receive from the channel must be unblocked and given the zero value. Goroutines who are blocked on a send to the channel must be unblocked and when they resume execution they cause a panic.
int capChannel(void *channel);
Return the capacity of the channel. If the channel is nil (handle is NULL), 0 is returned. If the channel is closed, its capacity is still returned.
int lenChannel(void *channel);
Return the number of values stored in the channel (i.e. its length). If the channel is nil (handle is NULL), 0 is returned. If the channel is closed, its length is still returned. (There can be values in a closed channel that have not yet been received.)
void freeChannel(void *channel);
Free the memory for a channel. If the channel is not closed, then a panic is caused. The behavior is undefined if the handle for a channel is used after the channel has been freed.
The Go language has a notion of a nil channel. We represent this with NULL.
The first 20 points for this section of the assignment will be awarded by evaluating your implementation of zero-capacity channels.
Your implementation should be performed using both C and Intel 64 assembler. Use C for as much of the code as possible and only use assembler when necessary. Place the C code in a file called goroutines.c and place the assembler code in a file called asm.s.
You should use valgrind to verify that you properly free all allocated memory. However, be aware that the tricks we are using to implement goroutines confuses valgrind with respect to memory reference errors. It apparently issues spurious read and write error messages in this case.
The directory ~cs520/public/prog4 contains test programs (and a Makefile) that you can link with your code. Use these files to start your testing, but you may need to create other files in order to do thorough testing.
Your program will be graded primarily by testing it for correct functionality. However, you may lose points if your program is not properly structured or adequately documented. Remember that with assembly language, documentation is even more important than with C programs.
Please turn-off any debugging code before you submit your program.
Your program will be graded using agate.cs.unh.edu so be sure to test in that environment. Your programs will be compiled using these gcc flags: -g -Wall -std=c99.
Your programs should be submitted for grading from agate.cs.unh.edu.
To turn in this assignment, type:
~cs520/bin/submit prog4 goroutines.c asm.s
Submissions can be checked by typing:
~cs520/bin/scheck prog4
This assignment is due Wednesday April 3. The standard late policy concerning late submissions will be in effect. See the course overview webpage.
Remember: as always you are expected to do your own work on this assignment. Copying code from another student or from sites on the internet is explicitly forbidden!
Comments and questions should be directed to pjh@cs.unh.edu