CS360 Final Exam - May 4, 2017 - James S. Plank
Answers and Explanations
Question 1
Straightforward assembler:
g:
push #4 / Allocate i on the stack
ld [fp+12] -> %r0 / i = j
st %r0 -> [fp]
b l1
l0:
ld [fp+12] -> %r0 / i = g(i, j)
st %r0 -> [sp]--
ld [fp] -> %r0
st %r0 -> [sp]--
jsr g
pop #8
st %r0 -> [fp]
l1:
ld [fp] -> %r0 / (i > k) - branch on negation
ld [fp+16] -> %r1
cmp
ble l2
ld [fp+12] -> %r0 / j += 5
mov #5 -> %r1
add %r0, %r1 -> %r0
st %r0 -> [fp+12]
b l0 / Back to the top of the loop
l2:
ld [fp+12] -> %r0 / return j
ret
|
Grading: 20 points:
- Push 4: 1 point
- Setting i to j correctly: 3 points
- Branch to the comparison: 1 point
- i = g(i,j) put in the correct place: 1 point
- The comparison: 3 points
- Branch on the negation: 1 point
- Push j then i on the stack: 2 points
- jsr g: 1 point
- Pop 8: 1 point
- Set i to r0 after calling g: 1 point
- j += 5: 3 points
- Returning j at the end: 2 points.
Question 2
Here are the errors that I would check:
- Unaligned pointer: Check to see if x is a multiple of 8.
Check every value from fp[0] to fp[i-1] to see if it is a multiple
of eight.
- Bad size for x: Check the four bytes starting eight bytes
behind x. The easiest way to do that is to check x[-2].
This should be equal to 24 (13 padded to 16, then add 8 for bookkeeping).
- Number of blocks on the free list: There should be two blocks on the free list, because
my_malloc() does not coalesce chunks when it calls my_free().
- The size field of the smaller of the two free blocks:
The value of *fp[0] or *fp[1] should be equal to 88.
- The size field of the larger of the two free blocks:
The value of *fp[0] or *fp[1] should be equal to
8K-88-24. That value is 8080, but you could just give me the expression.
- The three blocks should make up a contiguous regions of 8K bytes:
You need to account for every byte of the three chunks (x, fp[0] and fp[1])
and make sure that they compose a contiguous region of exactly 8K bytes. If they don't then
some of those chunks overlap, or they don't define a contiguous retion.
Grading
I was pretty lenient here. Each one of the above checks would get you five points.
If you didn't tell me how you checked to see that x's size is 24, then
you lost points. I gave some credit to other answers that made sense, even if they weren't
the answers above. The max score here was 20.
Question 3
This is a nuts and bolts fork/exec/dup/pipe/wait problem:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int p[2];
int fd;
int dummy;
pipe(p);
if (fork() == 0) {
fd = open("input.txt", O_RDONLY);
if (fd < 0) { perror("input.txt"); exit(1); }
dup2(fd, 0);
dup2(p[1], 1);
close(fd);
close(p[0]);
close(p[1]);
execlp("a.out", "a.out", "500", NULL);
perror("Execlp failed on a.out");
exit(1);
}
if (fork() == 0) {
fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
dup2(fd, 1);
dup2(p[0], 0);
close(fd);
close(p[0]);
close(p[1]);
execlp("sed", "sed", "-e", "s/X/Y/", NULL);
perror("Execlp failed on sed");
exit(1);
}
close(p[0]);
close(p[1]);
wait(&dummy);
wait(&dummy);
}
|
Grading
Each of these was worth a point:
- Open input.txt
- Error check open of input.txt
- Open output.txt
- Error check open of input.txt
- Fork #1
- Fork #2
- Pipe called before fork calls
- Dup standard input of child 1 (from input.txt)
- Dup standard output of child 2 (to output.txt)
- Dup standard input of child 2 from the pipe
- Dup standard output of child 1 to the pipe
- Close all extraneous file descriptors in child 1
- Close all extraneous file descriptors in child 2
- Close all extraneous file descriptors in the parent
- Execlp of a.out in child 1
- Execlp of sed in child 2
- Arguments to sed done correctly
- Cardinal sin in child 1
- Cardinal sin in child 2
- Parent waits
Question 4
There were two parts to this question. The first was straightforward -- write
a main() that creates the proper shared and private information, and
fork off n threads:
int main(int argc, char **argv)
{
Shared *s;
pthread_t tid;
int i;
Info *ti;
s = (Shared *) malloc(sizeof(Shared));
s->n = atoi(argv[1]);
s->m = new_mutex();
s->c = (PCT **) malloc(sizeof(PCT *) * s->n);
for (i = 0; i < s->n; i++) s->c[i] = new_cond();
s->turn = 0;
for (i = 0; i < s->n; i++) {
ti = (Info *) malloc(sizeof(Info));
ti->id = i;
ti->s = s;
pthread_create(&tid, NULL, thread, (void *) ti);
}
pthread_exit(NULL);
}
|
The second part was the part where you had to figure out synchronization.
You needed to call perform_calculation() outside of any mutex. Then
you need to lock the mutex and potentially block until it's your turn.
When it is your turn, then you call update_state(), still holding
the mutex. Then you set s->turn to be the next thread and signal
its condition variable. Finally, you release the mutex:
void *thread(void *arg)
{
Info *ti;
Shared *s;
ti = (Info *) arg;
s = ti->s;
while (1) {
perform_calculation(ti->id);
pthread_mutex_lock(s->m);
if (s->turn != ti->id) pthread_cond_wait(s->c[ti->id], s->m);
update_state(ti->id);
s->turn++;
if (s->turn == s->n) s->turn = 0;
pthread_cond_signal(s->c[s->turn]);
pthread_mutex_unlock(s->m);
}
return NULL; /* Shut the compiler up */
}
|
There are a few subtleties. First, you need to create all of the condition
variables before creating threads. Some of you called new_cond() in
the loop that created threads. That will be a problem if a thread tries to
signal the next thread's condition variable before that variable has been
created.
Second, the main thread needs to call pthread_exit(NULL) and not
return. If it calls return, then the process dies.
Grading
Extract n from the command line: 1 point
Malloc one copy of shared: 1 point
Malloc one copy of info per thread: 1 point
Initialize shared's condition variables: 1 point
Initialize the rest of shared properly: 1 point
Initialize info properly for each thread: 1 point
Call pthread_create to create each thread.: 1 point
Call pthread_exit on the main thread.: 1 point
Cast the thread argument to an Info: 1 point
While(1) loop: 1 point
Call perform_calculation outside of a mutex: 1 point
Lock the mutex before update_state: 1 point
Pthread_cond_wait until it's your turn: 2 points
Call update_state() when it's your turn: 1 point
Set turn to be the next thread: 2 points
Call pthread_cond_signal to signal the next thread.: 2 points
Unlock the mutex: 1 point
If your synchronization was off (e.g. you do something like "if it's my turn,
then call update_state(), otherwise call wait"), you lost three points.
Question 5
SIGPIPE is a signal, which the operating system sends to your program when you
try to write to a file descriptor for which there is no read end. This happens, for example,
when you try to write to the write-end of a pipe, and all of the read ends have been closed.
A second example is when you try to write to a socket when all of the other ends of the
socket have been closed.
You need to write a signal handler, which is a procedure that you register with the operating
system. When the operating system sends SIGPIPE to your program, it interrupts what the program
is doing, and calls your signal handler. When you have "handled" the signal in your signal
handler, you return, and the operating system has your program return to what it was doing
when it was interrupted by the signal.
The cleanest way to deal with SIGPIPE is to register a handler that does nothing. It simply
returns. Then, you do output with the standard IO library (via fprintf(),
fwrite() or fputs() for example, and with fflush()),
and you test the return values of these calls. When they flag an error, you know that
SIGPIPE has occurred. This lets you handle these output errors very cleanly.
Grading
- 2 points for saying that SIGPIPE is a signal.
- 4 points for saying that it's generated when you write to a pipe whose read ends are all closed.
- 4 points for handling it with a signal handler that you register with the OS.
- 2 points for saying that the OS calls your handler.
- 2 points for saying to ignore SIGPIPE when you want the stdio library to help.
- 4 points for saying that you test the return values of stdio write calls.
Question 6
0x27c54000.