CS360 Makeup Final Exam - May 4, 2012 - James S. Plank
Answers and Grading
Question 1
- int dup2(int orig, int target): This duplicates the orig file descriptor so that the given descriptor target will be identical to orig. If target was open,
it is closed first. It is typically used to assign an opened file descriptor to standard input
or standard output.
- int pipe(int fd[2]): This sets up a buffer in the operating system that allows multiple
processes to communicate. fd[0] is a file descriptor to the read end, and
fd[1] is a file descriptor to the write end. When you call write() on fd[1],
the bytes go into the buffer in the operating system, and may be extracted by read()
calls on fd[0]. Closing all the write ends of the pipe make read() return 0.
Closing all the read ends of the pipe make write() generate SIGPIPE.
The most typical use of pipe() is from the shell, where standard output of one
command is used as standard input of a second command, through the pipe.
- int wait(int *stat_loc): This has the calling thread block until one of its children
has exited. The return value is the process id of the child that has exited, and stat_loc
is a pointer to an integer, which wait fills in. That holds information like how the
child exited (normal, signal), and further information about the child's termination (exit
value, id of the signal).
Wait is typically called by the shell to wait for all of the processes in a command to complete.
- int select(int nfds, fd_set *readfds, NULL, NULL, NULL); This allows one to determine
when individual members of a set of file descriptors have something to read(). readfds specifies the set of file descriptors, and nfds is one greater than the maximum element
of readfds. When select() returns, the elements of readfds that are set
have the property that read() calls on them will not block.
select() is typically used to read from multiple socket connections, or from standard input
and multiple socket connections. Although it can be avoided by threads, without threads, it's
the only way to block selectively on multiple events.
Grading
Three points per definition.
Question 2
Part A: Your program is generating SIGPIPE. At the time that the
server drops your connection, you are usually in the sleep() call.
When you wake up, you write "GET-QUOTES" to the server, but the connection is
gone. Thus, the fflush() call generates SIGPIPE and the process
dies.
Part B: To fix this, you should simply do what we advocated in class. Write
a signal handler for SIGPIPE and have it do nothing but register itself as
the handler for future SIGPIPE signals. Then, test fflush() for failure,
and if it fails, re-establish the connection. This code is in a while() loop,
because the first thing that you want to do when you re-open the connection is to
write "GET-QUOTES\n" to the server.
I've put the new code in red.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct {
char *host;
int port;
int qtime;
FILE *fin;
FILE *fout;
int socket;
} Ripoff_Info;
void ignore_sigpipe(int x)
{
signal(SIGPIPE, ignore_sigpipe);
return;
}
void establish(Ripoff_Info *r, int close_em)
{
if (close_em) {
fclose(r->fin);
fclose(r->fout);
close(r->socket);
}
r->socket = request_connection(r->host, r->port);
r->fin = fdopen(r->socket, "r");
r->fout = fdopen(r->socket, "w");
}
void read_quotes(Ripoff_Info *r)
{
signal(SIGPIPE, ignore_sigpipe);
establish(r, 0);
while (1) {
sleep(r->qtime);
while (fprintf(r->fout, "GET-QUOTES\n") < 0 || fflush(r->fout) != 0) establish(r, 1);
if (read_and_display_quotes(r->fin) == 0) establish(r, 1);
}
}
|
Grading
- It generates Sigpipe: 3 points
- In the fflush() call, because the connection is dropped during sleep(): 2 points
- Writing a handler and calling signal(): 3 points
- Catching it by testing the return to fflush(): 2 points
If you caught it using a global variable, you lost a point.
Question 3
Part A:
Once you call execl(), the program's address space is overwritten. Threads A
and B will both be gone at this point. You may think, "If the operating system has
allocated threads for A and B, maybe they'll exist after the execl()
call." However, they cannot exist, because their memory is gone -- no code, globals, heap
or stack. The operating system will get rid of the threads -- operating systems are smart
like that.
Part B:
Nuts and bolts fork(), execl(), wait():
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
main()
{
int pid, status;
while (1) {
if (fork() == 0) {
execl("./ripoff", "ripoff", "servier.ripoff.com", "5555", "1", NULL);
exit(1);
} else {
pid = wait(&status);
}
}
}
|
Does this program scare you a little? It does me -- if the execl() call fails,
then we're going to be in an infinite fork()/execl()/wait() loop. It's not as
bad as a fork bomb, though, since there are only two processes that exist at any one
time, and you can kill the parent and stop the loop.
Grading
- Part A: 4 points
- Part B: 8 points
Question 4
Here's my answer. I ignore SIGPIPE and let fflush() catch problems when
the write ends go away. I'll talk more about the exit semantics later. I call
signal() in both threads because I don't know which thread will execute
first.
Note the use of the condition variable. I have ripoff_to_server() hold the
lock while it calls fputs() and fflush(). That might seem bad, but
the only time that server_to_ripoff() tries to lock the mutex is when it
has detected that the connection to the server has been lost and it is reconnecting.
In that case, the fflush() call will fail and release the mutex, waiting for
the connection to be reestablished.
void ignore_sigpipe(int x)
{
signal(SIGPIPE, ignore_sigpipe);
return;
}
void *ripoff_to_server(void *arg)
{
Thread_Info *ti;
char buffer[510];
ti = (Thread_Info *) arg;
signal(SIGPIPE, ignore_sigpipe);
while (1) {
if (fgets(buffer, 510, ti->fin_ripoff) == NULL) exit(0);
pthread_mutex_lock(ti->lock);
while (fputs(buffer, ti->fout_server) == EOF || fflush(ti->fout_server) != 0) {
pthread_cond_wait(ti->cond, ti->lock);
}
pthread_mutex_unlock(ti->lock);
}
}
void *server_to_ripoff(void *arg)
{
Thread_Info *ti;
char buffer[510];
ti = (Thread_Info *) arg;
signal(SIGPIPE, ignore_sigpipe);
while (1) {
if (fgets(buffer, 510, ti->fin_server) == NULL) {
pthread_mutex_lock(ti->lock);
fclose(ti->fin_server);
fclose(ti->fout_server);
close(ti->fd_server);
ti->fd_server = request_connection("server.ripoff.com", 5555);
ti->fin_server = fdopen(ti->fd_server, "r");
ti->fout_server = fdopen(ti->fd_server, "w");
pthread_cond_signal(ti->cond);
pthread_mutex_unlock(ti->lock);
} else {
fputs(buffer, ti->fout_ripoff);
if (fflush(ti->fout_ripoff) != 0) exit(0);
}
}
}
|
Part B: If the main thread exits naturally, the operating system terminates the
process. pthread_exit() has the thread exit without terminating the process.
Part C:
- The read end of fd_ripoff goes away. This is detected by ripoff_to_server,
by having fgets() return NULL. exit() is subsequently called, which terminates
the program.
- The write end of fd_ripoff goes away. This is detected by having the signal
handler ignore SIGPIPE, and having fflush() fail in server_to_ripoff().
When fflush() fails, exit() terminates the program.
- The read end of fd_server goes away. This is detected in server_to_ripoff()
by having fgets() return NULL. When this happens, we close the old connections and
establish new ones. We need to hold the lock during this process, because ripoff_to_server()
may want to use ti->fout_server. When we get the new connection, we need to call
pthread_cond_signal(), because
ripoff_to_server() may have called pthread_cond_wait() when it couldn't write to the server.
- The write end of fd_server goes away. This is detected in ripoff_to_server()
when fflush() or fputs() fails. When this happens, we call pthread_cond_wait()
on the condition variable so that we can wait for server_to_ripoff() to discover that
the connetion is gone, and reconnect.
You did not have to mimic my structure -- there are other valid ways to handle this problem.
Even if you didn't handle everything well, if you explained what you did, that got you credit.
Grading
- Part A: 12 points. Your solution didn't have to mimic mine, but it had
to make sense.
- Part B: 4 points.
- Part C: 2 points per part.