CS360 Final Exam - May 6, 2013 - James S. Plank
Answers and Explanations
Question 1
You need two threads -- one to read from standard input and write to the socket, and
one to read from the socket and write to standard output. The simplest thing to do
is to write a procedure that reads from one FILE * and writes to another.
Then you simply fork a thread with that procedure, having set up the arguments to
have the correct FILE *'s. Here's my answer, in
q1.c:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include "sockettome.h"
typedef struct {
FILE *fin;
FILE *fout;
} Arg;
void *reader(void *arg)
{
Arg *a;
char buf[1000];
a = (Arg *) arg;
while (fgets(buf, 1000, a->fin) != NULL) {
fputs(buf, a->fout);
fflush(a->fout);
}
return NULL;
}
main(int argc, char **argv)
{
/* Not error checking the command line */
int fd;
pthread_t tid1, tid2;
Arg a1, a2;
fd = request_connection(argv[1], atoi(argv[2]));
a1.fin = stdin;
a1.fout = fdopen(fd, "w");
a2.fin = fdopen(fd, "r");
a2.fout = stdout;
pthread_create(&tid1, NULL, reader, (void *) &a1);
pthread_create(&tid2, NULL, reader, (void *) &a2);
pthread_exit(NULL);
}
|
Obviously, there are a few ways to do this -- you could have simply had the main thread
call the procedure. If so, you needed to be careful. For example, when standard input
is closed, if you were reading it in the main thread, and had the main thread exit, you may
actually miss some output from the socket.
Grading:
This problem was worth 20 points, and my grading works as follows.
Your solutions tended to fall into one of four categories:
- Correct: Either you used threads, or you did a good job with processes. This
was worth 20.
- Alternate: Your program alternated reading from standard input and from the
socket. While that's not correct, it was a cogent, albeit incorrect, solution. This
started at 14 points.
- One-side: This answer read from one fd and wrote to another, and ignored the
other two fd's. I also threw in the answers which read one line, then exited.
This started at 10 points.
- None-of-the-above: This is answer that missed the boat completely from my perspective.
Start with zero points.
Once you started in a category, I adjusted your score up or down for various things,
like forgetting fflush(), mis-managing types, serving a socket, or general confusion.
Question 2
A correct answer is in this PDF file. Here are the things that
you should have shown me:
- A free block corresponding to a in the code. It starts at 0x10468, and is 24 bytes.
- A block for b. It starts at 0x104e8 and is 40 bytes.
- A block for c. It starts at 0x10480 and is 72 bytes. Why 72? Because the
old head of the free list is at 0x104c8, and if c were 64 bytes, that would leave an
8-byte chunk, which is too small to put on the free list. Therefore, c's size is 72, so that we don't lose those 8 bytes.
- A block for d. It starts at 0x10528 and is 64 bytes.
- Now I said that there were five blocks on the free list, and that's convenient because
aside from that free block for a above, there are four other free regions of memory:
- 0x10440 to 0x10468
- 0x104c8 to 0x104e8
- 0x10510 to 0x10528
- 0x10568 to 0x10578
- So, we need to put those chunks on the free list. The first chunk will be 0x10468 (the
chunk for a, because when we free a chunk, we put it on the head of the free list.
- The second chunk should be the old head of the free list: 0x104c8.
- The remaining three chunks should be on the free list in any order.
Grading:
20 points:
- A chunk for A with correct size in correct location: 1.5 points
- A chunk for B with correct size in correct location: 1.5 points
- A chunk for C with any size in correct location: 1 points
- Chunk C's size is actually correct: 1 points
- A chunk for D with correct size in correct location: 1.5 points
- A free block starting at 0x104c8 with correct size: 1.5 points
- A free block starting at 0x10440 with correct size: 1.5 points
- A free block starting where B ends with correct size: 1.5 points
- A free block starting where D ends with correct size: 1.5 points
- A's chunk is the head of the free list: 2 points
- 0x104c8 is the next node on the free list: 1.5 points
- Other free list nodes have good flinks: 2 points
- Other free list nodes have good blinks: 2 points
Question 3
Paragraph: The variable b_blocked is a state variable that says
whether thread B is blocked. It starts of by saying that thread B isn't blocked.
In safe_modifiy_state(), thread A will lock the mutex and then test to
see if B is blocked. If not, then it will block it self on condition variable a_cond.
When it wakes up, it should test to see if B is blocked, but I think the code will
work fine even if not, because B should be blocked when A awakens. It then calls
modify_data(). When that's done, it sets the state variable to indicate that
B is unblocked, wakes up B and returns.
In BM(), B needs to lock the mutex, set its state to blocked (by setting b_blocked
to 1, signal A and block on b_cond. When it wakes up, A is done and
b_blocked should be set to 0, so it returns. I actually think it's incorrect
to have B set b_blocked to zero -- think about that. I didn't take off for it.
Why do you need b_blocked anyway? It depends on which thread calls its procedure
first. For example, suppose you simply have A block without testing b_block.
Then you're going to have deadlock if B calls BM() before A calls safe_modify_data().
Code:
q3.c
void safe_modify_data(struct Data *D, Info *I)
{
pthread_mutex_lock(&(I->lock));
while (!I->b_blocked) pthread_cond_wait(&(I->a_cond), &(I->lock));
modify_data(D);
I->b_blocked = 0;
pthread_cond_signal(&(I->b_cond));
pthread_mutex_unlock(&(I->lock));
}
void BM(Info *I)
{
pthread_mutex_lock(&(I->lock));
I->b_blocked = 1;
pthread_cond_signal(&(I->a_cond));
pthread_cond_wait(&(I->b_cond), &(I->lock));
pthread_mutex_unlock(&(I->lock));
}
|
Grading:
Again 20 points. The paragraph was 8 points, and you needed to mention the purposes of
each of the pieces of data (2 points each). The procedures were 6 points each.
Question 4
This is a nuts-and-bolts fork/dup/exec question.
You need to open one pipe, and have that go from kim's standard out to khloe's standard in.
However, you don't close it in your parent process. When khloe exits, you then have the
pipe also go to kourtney's standard in. You also do this with the socket's output -- you
keep it open so that both khloe and kourtney may use it. Here's the code, in
q4.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "sockettome.h"
main()
{
int s, fd, dummy;
int p[2];
s = serve_socket(9021);
fd = accept_connection(s);
close(s);
pipe(p);
if (fork() == 0) {
dup2(fd, 0);
dup2(p[1], 1);
close(p[0]);
close(p[1]);
close(fd);
execlp("kim", "kim", NULL);
exit(1);
}
close(p[1]); /* No reason to keep p[1] around any more */
if (fork() == 0) {
dup2(p[0], 0);
dup2(fd, 1);
close(p[0]);
close(fd);
execlp("khloe", "khloe", NULL);
exit(1);
}
(void) wait(&dummy); /* Wait for khloe. If it's Kim, Khloe's dying pretty soon so
don't worry about it. That wouldn't be true had you not
closed p[1]. */
if (fork() == 0) {
dup2(p[0], 0);
dup2(fd, 1);
close(p[0]);
close(fd);
execlp("kourtney", "kourtney", NULL);
exit(1);
}
close(fd);
close(p[0]);
(void) wait(&dummy);
(void) wait(&dummy);
exit(0);
}
|
We can test it -- here are kim, khloe and kourtney:
kim.c
#include <stdio.h>
#include <stdlib.h>
main()
{
int l;
char c;
while (read(0, &c, 1) == 1) {
write(1, &c, 1);
if (c == '\n') write(1, "Gucci\n", 6);
}
}
|
|
khloe.c
#include <stdio.h>
#include <stdlib.h>
main()
{
int l;
char c;
l = 0;
while (read(0, &c, 1) == 1) {
if (c >= 'a' && c <= 'z') c += ('A' - 'a');
write(1, &c, 1);
if (c == '\n') l++;
if (l == 10) exit(0);
}
}
|
|
kourtney.c
#include <stdio.h>
#include <stdlib.h>
main()
{
int l;
char c;
while (read(0, &c, 1) == 1) {
if (c >= 'a' && c <= 'z') c += ('A' - 'a');
write(1, &c, 1);
}
}
|
|
When we run it, we can throw 10 random lines at it. Kim will write "Gucci\n" after
every line. khloe will convert lowercase to uppercase, but exits after the first
ten lines. kourtney takes over and finishes the job:
UNIX> telnet localhost 9021
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Julia Doghouse
Elizabeth Tyrannic
Ava Effloresce MD
Hunter Bruise
Matthew Phonic
Jordan
Tyler Ontario
Evan Synonym
Isaac Proponent
Lucas Siesta
JULIA DOGHOUSE
GUCCI
ELIZABETH TYRANNIC
GUCCI
AVA EFFLORESCE MD
GUCCI
HUNTER BRUISE
GUCCI
MATTHEW PHONIC
GUCCI
JORDAN
GUCCI
TYLER ONTARIO
GUCCI
EVAN SYNONYM
GUCCI
ISAAC PROPONENT
GUCCI
LUCAS SIESTA
GUCCI
^]
telnet> quit
Connection closed.
UNIX>
Grading
- Setting up the socket: 2 points
- Doing all of the dup2's involving the socket: 4 points
- Calling pipe() and doing all of the dup2's involving it: 4 points
- Doing all of the fork()s and exec()'s: 4 points
- Calling all of those close()'s properly: 4 points
- Calling wait() at the proper time: 2 points