For this problem, you will write a program that takes
stdin and sends the input to two other programs (provided
on the command line) as their input. For example, here is a run using
sort and wc:
$ ./split-output /usr/bin/sort /usr/bin/wc this is not sorted <--user typed ctrl-D after this line is 4 4 19 not sorted this
a) Write a function, writeBuf that uses the
write function to print an entire buffer to a file
descriptor (you are not allowed to use the dprintf
function for this question). [2 points]
/* Function: writeBuf * ------------------ * Writes the contents of buf with size len to the file descriptor fdOut * @param fdOut - the file descriptor where the data will be written * @param buf - the buffer to write * @param len - the number of bytes in the buffer */ void writeBuf(int fdOut, char *buf, ssize_t len);
b) Write a function, dualEcho that reads from
fdIn and writes to both fdOut1 and
fdOut2 until fdIn closes. If either
fdOut1 or fdOut2 has a value of -1, you should
not write any data to that file descriptor. You can assume that your
writeBuf function from part (a) works perfectly. You are
not allowed to use any dynamic memory for this function, and you cannot
presume to know how much data will be read from fdIn. [3
points]
/* Function: dualEcho * ------------------ * Writes the all input from fdIn to both fdOut1 and fdOut2 * @param fdIn - the file descriptor to read from (until EOF) * @param fdOut1 - the first output file descriptor (ignore if -1) * @param fdOut2 - the second output file descriptor (ignore if -1) */ void dualEcho(int fdIn, int fdOut1, int fdOut2);
c) Complete the main function, which starts two
programs and sends stdin to both, using your (assumably
perfect) dualEcho function. You should not change
stdout of either program. [5 points]
int main(int argc, char **argv) { if (argc < 3) {
printf("Usage:\n\t %s prog1 prog2\n",argv[0]); return 0;
}
char *progA[] = {argv[1], NULL}; char *progB[] = {argv[2], NULL};
// your code here:
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
//
return 0;
}
d) As noted in the original question, your program does not
handle de-interleaving the two outputs (i.e., one program does not print
all of its output before the other program). Let's assume we actually
want program A to print all of its data before program B prints its
data.
One potential solution (let's call it "Solution A") would be to
create two additional pipes and map stdout of each program
to the write end of the pipes. Then, your program could read from the
read end of each pipe, first reading program A and writing all of its
output to the terminal stdout, and then repeating for
program B.
Another potential solution ("Solution B") might have the
second child (the one that will execvp program B)
raise a SIGSTOP on itself before the
execvp, and then have the parent process
waitpid for program A to finish before sending a
SIGCONT signal to program B.
It turns out that pipes have a maximum internal buffer (64KB on
current Linux versions), such that write calls will
block when a write pipe buffer is at its capacity, and would not
unblock until some data has been read from the read buffer.
In 100 words or less, answer the following question: Would
either Solution A or Solution B presented above work by modifying
our program as described? If both would work, explain which option
would be better. If only one would work, explain why it succeeds
and the other doesn't. If neither would work, explain the
underlying problem that affects both. Again, assume you can not
predict how much data will be presented to stdin for
your program. [2 points]
Problem 2: Signal Puzzle
6 points
This
question has 2 parts. Consider the following C program and its
execution. Assume all processes run to completion, all system and
printf calls succeed, and that all calls to
printf are atomic. Assume nothing about scheduling or time
slice durations.
static pid_t pid; // global void signalHandler(int signal) { if (pid
== 0) {
printf("child!\n");
} else {
printf("parent!\n");
exit(0);
} }
int main(int
argc, char *argv[])
{ signal(SIGUSR1, signalHandler);
for (int i =
0; i < 3;
i++) { pid = fork();
if (pid == 0)
{ //
child if
(i % 2 ==
1) { raise(SIGUSR1); }
else { kill(getppid(),
SIGUSR1); } return 0;
} } printf("I made it here!\n"); for (int i
= 0; i < 3; i++) { waitpid(-1,
NULL, 0);
} printf("And I made
it here!\n"); return 0;
}
a) Write down all possible outputs from the program. [4 points]
b) If the exit 0 line was removed from the signal
handler, what are two significant differences that you would
always see in the output that you did not see in part (a)? [2
points]
Problem 3: Sleep Sort
5 points
This problem
has 3 parts. Believe it or not, sorting is still an active
research field, and computer scientists develop new sorting algorithms
frequently. You saw merge sort in lab last week. Some
sorting algorithms are better than others. For this problem, you will
write sleep sort, a sorting algorithm literally first
posted on the 4chan Internet forum, known for its anonymous,
meme-creating, troll-happy users who seem to be there only for the lulz.
Here is how the sleep sort algorithm works: the
function loops through an unsorted vector of integers,
forks a process, and sleeps, in seconds, equal
to the integer itself. After the sleep is complete, it
prints the number to stdout. Convince yourself that this
will, indeed, print out a sorted list of integers, and then convince
yourself that it is an incredibly stupid way to sort a list of integers
(though arguably admirable, in a 4chan sense).
a) Write the sleepsort function below. Ensure that
the function properly waits for its children to complete before
returning. [3 points] void
sleepsort(vector numbers) {
b) sleep sort is sometimes referred to as one
giant race condition that happens to do something useful. In 100
words or less, explain what is meant by this phrase. [1 point]
c) A clever 4chaner commented that you could use
usleep, which sleeps for microseconds instead of seconds,
to speed up the sort by a factor of a million, instantly. It would be
the world's best performance increase resulting from a single-character
change in a program. In practice, though, this doesn't work. In 100
words or less: what would cause this amazing speedup to break the sort?
[1 point]
Problem 4: File Systems
7 points
This
problem has 4 parts. Recall that the Unix v6 file system has 512
byte sector sizes, with a 32-byte inode per file, 2-byte
block id numbers, and 16-byte struct direntv6 entries. Use
this information to answer the following questions. All answers must
be 100 words or less for credit.
a) Newer hard drives and file systems sometimes define the sector
size to be 4096 bytes. Describe the pros and cons of a larger sector
size. [2 points]
b) Assume a newer file system changed the size of an
inode to 64 bytes. Explain how you would use the extra 32
bytes for the greatest benefit of the file system. List one negative of
having a larger inode size. [2 points]
c) What is the main limitation to a 2-byte block id number? Name
a tradeoff to having a 4-byte block number. [2 points]
d) Explain how a directory is "just a regular file" in terms of
the Unix v6 file system. [1 point]
Problem 5: Safer SIGINT
3 points
When you type
ctrl-C while running a program, a SIGINT ("Interrupt")
signal is sent to the program, which usually terminates the program.
This is not particularly nice, and the program might be in the middle of
something critical. Using a signal handler, we can capture
SIGINT, and then set a flag that lets the program know that
the user wants to terminate it. If the program checks the flag often, it
can handle the termination, but do it gracefully, after it has closed
everything down. Here is an example:
static bool okay_to_quit = false;
void
sigint_handler(int sig) {
okay_to_quit = true; }
int main(int
argc, char *argv[])
{ signal(SIGINT, sigint_handler);
int fds = open("importantFile.txt",
O_WRONLY | O_CREAT | O_EXCL, 0644);
while (true) { printf("I'm doing something important!\n"); dprintf(fds,"Important
data.\n"); sleep(1); if (okay_to_quit) break; }
printf("Safely Quitting!\n"); close(fds);
return 0; }
We've now got a relatively safe way to handle
SIGINT. But, what if we want to make things even safer?
Sometimes a user can accidentally type ctrl-C. But users will
very rarely type two ctrl-Cs in a row, one after the
other. Modify the signal handler presented above to only set
okay_to_quit to true if the user types
ctrl-C twice in a 500ms time frame. Assume you have the following
function:
/* Function: get_ms_time * This function returns the current
time in milliseconds. * A
duration of time in milliseconds can be calculated by * calling this function twice, and
subtracting the second *
value from the first. */ int get_ms_time();
We have provided you with two static function
variables, which persist between calls to the function. Recall from
CS107 that a static function variable is declared once and then
retains its value for the duration of the program. If the function
is called again, the value is the same as it was the last time the
function was run.
/* Function: sigint_handler(int sig)
* Sets the global okay_to_quit variable to true if * if a user types
ctrl-C twice within kTimeThreshold * number of milliseconds */ void
sigint_handler(int sig) { const int kTimeThreshold = 500; // ms
// first_sigint: variable to indicate that we have seen the first of
// two SIGINTs
static bool first_sigint = false;
// start: variable to hold the start time between calls to this
// function
static int start;