CS360 Final Exam: December 8, 2003. Answers
Question 1
- Free looks 8 bytes in front of the pointer, and assumes that
the integer there is the size of the block to be freed.
- A bad argument is any address or pointer that was not returned as a
result of a malloc() call, or has already been free()'d.
- There are three possible results. First, you can get a bus error,
if the address is not a multiple of 4. This will occur when free()
tries to figure out the size, and dereferences 8 bytes in front of
the pointer. Second, you can get a segmentation violation, as free()
tries to hook the piece of memory onto the free list. If either the
pointer itself, or any pointers that are assumed to exist once the
freelist structure is enforced on the memory, is in "the void" or in
read-only memory, then you will get the segmentation violation. Finally,
you may simply corrupt memory, as the values that are there are legal
pointers, but you won't see the results of this corruption until after
the free() call return.
- I have no problems with seg faults and bus errors -- those are
proper identifications of bad arguments. Its the last problemmatic
addresses -- those that result in memory corruption, that you seek to
avoid. In order to do this,
you can have malloc() put checksums before and after the memory
that it allocates. Then free() can check these for corruption
(or existence) before attempting to free.
Grading: 9 points
- 2 points: Part 1: Size is 8 bytes before the pointer
- 2 points: Part 2: Address not returned from malloc (or already freed)
- 3 points: Part 3: Seg fault, Bus error, Memory corruption
- 2 points: Part 4: Add a checksum
You had to be precise in your wording. For example "the size is at the
front of the memory block," while technically correct, is not precise enough.
Question 2
- pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t, *l);. This
blocks the current thread, and atomically releases the mutex. It places the
thread on a queue associated with cv. When
pthread_cond_signal() awakens the thread, it must acquire the mutex
before unblocking and returning from the pthread_cond_wait() call.
- pthread_cond_signal(pthread_cond_t *cv);
This selects one thread that is waiting on cv, and awakens it. As
stated above, that thread must then acquire the mutex before returning
from its pthread_cond_wait() call. If there are no threads
waiting on cv, then this call does nothing.
Grading: 6 points
One point each:
- Blocking thread needs to hold the mutex.
- Blocking thread releases the mutex.
- Blocking thread blocks.
- Signalling thread picks one thread blocked on the CV.
- Signalling thread wakes it up.
- Blocking thread needs to wait to reacquire the mutex.
Question 3
You don't need multiple processes for this one, but it was ok if you used
them. All you need to do is write to a pipe whose read end is closed:
main()
{
int p[2];
char c;
pipe(p);
close(p[0]);
write(p[1], &c, 1);
}
Grading: 5 points
You pretty much got this one or you didn't. Those who effectively
did "cat | ls" only got 4 points, because that doesn't generate
SIGPIPE until the user types into cat.
Also, if you had two processes, and the first writes fewer than
PIPE_BUF bytes to the second before the second
process gets a chance to run, then the first process can exit
before SIGPIPE is generated. Think about it.
Question 4
main(int argc, char **argv)
{
int ifd, ofd, p[2], i, status;
if (argc < 3) {
fprintf(stderr, "Need >= 2 arguments\n");
exit(1);
}
for (i = 1; i < argc; i++) {
if (i > 1) ifd = p[0];
if (i < argc-1) {
if (pipe(p) < 0) { perror("pipe"); exit(1); }
ofd = p[1];
}
if (fork() == 0) {
if (i > 1) {
dup2(ifd, 0);
close(ifd);
}
if (i < argc-1) {
dup2(ofd, 1);
close(ofd);
}
execlp(argv[i], argv[i], NULL);
perror(argv[i]);
exit(1);
}
close(ofd);
if (i < argc-1) close(ofd);
if (i > 1) close(ifd);
}
for (i = 1; i < argc; i++) {
wait(&status);
}
}
Grading: 13 points
- 1 point: Error check number of arguments
- 4 points: For loop that does the correct basic things (like calling
pipe() in the right place and the right number of times.
- 1 point: Making correct dup2() calls.
- 2 points: Closing correct fd's in the child.
- 2 points: Closing correct fd's in the parent.
- 2 points: Parent calls wait correctly.
- 1 point: No cardinal sin of exec (0 if you don't call exec).
Question 5
So, what you need to do is save your state by calling setjmp() (or, more
properly, sigsetjmp(). Then set up a handler for SIGALRM. then
call alarm(10) and code_cracker(). If code_cracker()
returns before 10 seconds, unset the alarm by calling alarm(0) and
return. Otherwise, your handler will be executed. Call longjmp()
(or, more properly, siglongjmp()) to have crack_10() gain
control again, and it returns NULL.
jmp_buf env;
void handler(int dummy)
{
siglongjmp(env, 1);
}
char *crack_10(char *s)
{
char *output;
output = (char *) malloc(1001);
if (sigsetjmp(env, 0) == 0) {
signal(SIGALRM, handler);
alarm(10);
code_cracker(s, output);
alarm(0);
return output;
} else {
free(output);
return NULL;
}
}
Grading: 5 points
Yes, I didn't realize that Brad didn't go over setjmp()/longjmp().
That said, you still should have known to allocate the output buffer,
call alarm() and set up a signal handler. That got you 60%
credit, and in my accounting for grades, I treated the other two
points as extra credit.
- Allocated room for output: 1 point (0.2 if you did it as a local
variable -- that won't work too well when you return it to your
calling function.....)
- Called alarm(): 1 point
- Set up a handler for SIGALRM: 1 point
- Did the correct thing with setjmp()/longjmp().
Question 6
Part A:
This code takes three file descriptors that are opened for reading.
It waits for something to read on one of them, and when that one
has something to read, it reads exactly ten bytes from it (stalling
if necessary if ten bytes have not arrived yet), prints
those bytes on standard output, and then waits for something to
read on the remaining two file descriptors. It repeats this task
on the remaining two file descriptors, and then returns.
Part B: Yes, you do need a mutex. Why? Well, you are going
to fork off three threads, and have each thread read from a different
file descriptor. If each file descriptor has ten bytes to read, then
there are no problems, and no mutex is necessary. But, suppose
file descriptor a has three bytes to read instantly, and
then a big pause occurs before the remaining seven bytes are available.
Unless you have a mutex keeping the other threads from reading, then
those other threads can read from their file descriptors and print to
standard output before the remaining seven bytes from file descriptor
a are read. Thus, you do need a mutex.
Grading: 6 points
The first 4 points were for the explanation. Again, I wanted some
level of precision. Often your description was ambiguous, and if
so, I took off points (e.g. does your description allow for 30
bytes to be read from one file descriptor)?
The remaining points were for saying "yes", and giving either the
reason that I mentioned above, or that you needed to protect the
shared buffer of printf(). Saying that you need to protect
buf is wrong, because in a threaded implementation, each
thread would have its own buffer (there's no good reason to have
them share a buffer).