CS520
Spring 2019
Laboratory 7 addendum
Friday March 22
This addendum provides more details on the steps to follow to
complete Lab 7.
The goal for Lab 7 is to complete the createGoroutine, yieldGoroutine
and the cleanupGouroutine functions:
- Define a C struct for the Goroutine Control Block (GCB),
which contains the following members:
- members to store the saved contents of the rsp, rbx, r12, r13,
r14 and r15 registers.
- a pointer to a GCB, to allow GCBs to be linked together.
- members to store values to be used to initialize the rdi and rsi
registers when a goroutine is created and then started.
- a pointer to the base of the goroutine's stack so it can be deallocated.
- Create a "ready list", a linked list of all the GCBs for goroutines
that are ready to run.
The first GCB on the list should be the currently running goroutine.
I recommend implementing the ready list by using two global static pointers
to GCBs, one to point to the head of the list and one to point to the
tail of the list.
Initialize these two pointers to NULL.
- Write an Intel 64 assembly language routine to be used by
yieldGoroutine in order to save the state of the current goroutine and
to restore the state of the goroutine to run next:
- The function should begin with the two instructions that all
"good" Intel 64 functions begin with: push the current frame
pointer (rbp) onto the top of stack, and establish the new frame pointer
to point to the top of the stack.
- Next check if the function's first parameter (which will be in rdi)
is null (zero).
If so, branch over the next several instructions that save the
state of the current goroutine.
- Save the state (rsp, rbx, r12, r13, r14, r15) of the current goroutine
into the GCB pointed to by the first parameter (rdi).
This will be done by a sequence of "movq" instructions, e.g.
movq %rsp, 0(%rdi).
Note that each value will be stored at a particular offset into
the struct.
In the "movq" instruction given, it is assumed that the rsp register
is stored first in the GCB struct, which will be at offset 0.
Be sure that the offsets you use in assembly language align with
how you declared the C struct.
- Restore the state of the goroutine to run next from the GCB pointed
to by the second parameter (rsi).
This will be done by a sequence of "movq" instructions, e.g.
movq 0(%rsi), %rsp.
Also move values from the rdi and rsi GCB slots into rdi and rsi.
Be sure you set the rsi register last.
- The function should end with the two instructions that all "good"
Intel 64 functions end with: set the frame pointer (rbp) by
popping a value from the top of the stack, and then a return
instruction.
- Write the panic C function. The exact C code is:
void panic(char *format, ...)
{
va_list ap;
va_start(ap, format);
vfprintf(stderr, format, ap);
fprintf(stderr, "\n");
exit(-1);
}
To support this code, you need to include stdarg.h at the top of
goroutines.c.
- Write the gStart C function. The exact C code is:
static void gStart(void (*func)(void *), void *arg) {
func(arg);
gCleanup();
}
- Write the yieldGoroutine C function:
- If the ready list is empty, malloc a GCB for the main goroutine
and insert it into the ready list. If malloc fails, call the
panic function with an appropriate error message.
- Rotate GCBs on the ready list: first (current running goroutine)
becomes last and second (next to run) becomes first.
- Call the assembly language routine to save the state of the
current running goroutine into its GCB and to restore the state
of the goroutine to run next from its GCB.
- Write the createGoroutine C function:
- If the first argument is NULL, call the panic function with an
appropriate error message.
- If the ready list is empty, malloc a GCB for the main goroutine
and insert it into the ready list.
- Use malloc to allocate a GCB and put it on the end of the ready list.
- Use malloc to allocate a stack.
Save the base address of the stack in the GCB so it can be freed later.
- If any of these malloc calls fail, call the panic function with an
appropriate error message.
- Initialize the GCB and the stack so that the goroutine, when it is
yielded to, will execute the gStart function:
- put the createGoroutine's arguments into the rdi and rsi
slots of the GCB.
- put the address of the third-to-last quadword in the stack into
the rsp slot of the GCB.
- put the address of the gStart function into the
second-to-last quadword in the stack.
- Write the gCleanup C function:
- Remove the GCB from the front of the ready list.
- Free the stack for the last goroutine that called gCleanup.
- Remember the pointer to the base of the stack for the current
goroutine so that it can be freed at the next call to gCleanup.
- Free the GCB that was removed from the front of the ready list.
- Call the assembly language routine that saves/restores goroutine
state, passing NULL for its first parameter, and passing as the
second parameter the address of the GCB now on the front
of the ready list.
- Write the cleanupGoroutines C function:
- Iterate through the ready list and free the GCB and stack for
each goroutine. (Of course, do not free the stack for the main
goroutine.)
Last modified on January 15, 2019.
Comments and questions should be directed to
pjh@cs.unh.edu