Tom Thumb found the Lab 1 "shell" project just too hard, so he decided
to write a "mini-shell" instead. His mini-shell can only run one
sub-program, with mandatory input redirection. For example, the command
"minish wc -l foo.txt
" should run the command "wc -l
< foo.txt
". When the sub-command exits, the mini-shell will
print out "Done!
". Here's his code:
int main(int argc, char **argv) { int infd; pid_t p; // Open the last argv[] argument for redirection infd = open(argv[argc - 1], O_RDONLY); dup2(infd, 0); if ((p = fork()) == 0) { argv[argc - 1] = NULL; // clear out redirection execvp(argv[1], &argv[2]); // run subcommand! } printf("Done!\n"); }
Needless to say, Tom Thumb isn't a very good programmer! Help the poor fellow out.
Question. List at least 3 error conditions that Tom has forgotten to check for.
Chastened, Tom returns to the drawing board. Here's his second try.
void sigchld_handler(int signal_value) { int status; (void) wait(&status); printf("Done!\n"); exit(0); } int main(int argc, char **argv) { int infd; pid_t p; // Open the last argv[] argument for redirection infd = open(argv[argc - 1], O_RDONLY); dup2(infd, 0); if ((p = fork()) == 0) { argv[argc - 1] = NULL; // clear out redirection execvp(argv[1], &argv[2]); // run subcommand! } signal(SIGCHLD, &sigchld_handler); while (1) /* Do nothing */; }
This is better, but not much.
Question. Does Tom's code contain any race conditions, or "Heisenbugs"? If yes, describe a sequence of events that will trigger a race condition. If no, explain why not.
Question. Describe the performance consequences
of Tom's waiting strategy (the while
loop in
main
). Could Tom do better? Why and how? Refer to specific
operating system structures discussed in class.
Question. Describe at least two fundamentally different circumstances where Tom's minishell will never return.
All your CS 111 TAs love mutexes. In fact, they love mutexes to an insane degree. Last weekend on a late-night skim-milk bender, they wrote three programs that attempt to synchronize on three different mutexes. Here they are:
mutex_t l1, l2, l3; process_1() { acquire(l3); acquire(l1); release(l1); release(l3); } process_2() { acquire(l2); acquire(l3); acquire(l2); release(l2); release(l2); release(l3); } process_3() { acquire(l3); acquire(l1); acquire(l2); release(l1); release(l3); release(l2); }
The TAs were supposed to figure out whether these processes contained a deadlock, but cleverly, they've decided to pass off this work to you.
mutex_t
is a simple mutual-exclusion lock.mutex_t
is a recursive mutual-exclusion lock.mutex_t
is a semaphore allowing at most two
processes to hold the lock at a time.Question. Define a lock ordering for the mutexes, and change one or more of the processes to follow that lock ordering, so that no deadlock is possible with recursive mutexes.
Extra Credit. Put a box around the lame joke in the preceding question.