fork
When using fork
, a typical skeleton will look like the below (if
done in the main function). We will take this skeleton and add to it in
each syscall section.
int main(int argc, char *argv[]) {
pid_t pid = fork();
if (pid == -1) {
perror(“Could not fork!\n”);
} else if (pid == 0) { // child
// do stuff in child process
} else { // parent
// do stuff in parent process
}
}
exec
argv[1]` would be the command program and the rest of `argv
would be the arguments for that command (e.g. in ls -a
, ls
is
the program and -a
is the argument). In this case, we are not taking
arguments for the command being passed in, so the child's argv
will
not have additional arguments. Remember that argv
still contains the
program name, not only the arguments passed to it!
int main(int argc, char *argv[]) {
if (argc != 2) {
printf(“Usage: %s program-name”, argv[0]);
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror(“Could not fork!\n”);
} else if (pid == 0) { // child
char *child_argv[] = {argv[1], NULL};
execvp(argv[1], child_argv); // does not return on success
perror(“Couldn’t execute program!\n”);
} else { // parent
// do stuff in parent process
}
}
waitpid
We put waitpid
in the parent process because it is the one waiting.
We are also given the pid of the child process because it is returned by
fork
.
int main(int argc, char *argv[]) {
if (argc != 2) {
printf(“Usage: %s program-name”, argv[0]);
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror(“Could not fork!\n”);
} else if (pid == 0) { // child
char *child_argv[] = {argv[1], NULL};
execvp(argv[1], child_argv); // does not return on success
perror(“Couldn’t execute program!\n”);
} else { // parent
waitpid(pid, NULL, 0);
printf(“done\n”);
}
}
dup2
STDIN_FILENO
and STDOUT_FILENO
are built-in constants that
contain the file descriptors for stdin and stdout, respectively. After
opening infile
to fdin
, dup2(fdin, STDIN_FILENO)
will tell
the system that whenever STDIN_FILENO
is used, it is actually
referencing infile
, not stdin
. This works similarly for
outfile`, except with `STDOUT_FILENO`. After copying the `fdin
and fdout
file descriptors, they are no longer used anywhere else
because their respective files are now handled by STDIN_FILENO
and
STDOUT_FILENO` everywhere that we need them, so we `close(fdin)
and repeat with fdout
.
int main(int argc, char *argv[]) {
if (argc != 4) {
printf(“Usage: %s program-name infile outfile”, argv[0]);
exit(1);
}
pid_t pid = fork();
if (pid == -1) {
perror(“Could not fork!\n”);
} else if (pid == 0) { // child
int fdin = open(argv[2], O_RDONLY);
int fdout = open(argv[3], O_WRONLY|O_CREAT);
dup2(fdin, STDIN_FILENO); // copy fdin fd to stdin fd
dup2(fdout, STDOUT_FILENO); // copy fdout to stdout fd
// close now unused fds
close(fdin);
close(fdout);
char *child_argv[] = {argv[1], NULL};
execvp(argv[1], child_argv); // does not return on success
perror(“Couldn’t execute program!\n”);
} else { // parent
waitpid(pid, NULL, 0);
printf(“done\n”);
}
}
pipe
After creating the pipe using the file descriptor array pipefd
, we
want the write end of the pipe to be in the parent (so that the parent's
stdin can be redirected or /written/ to the child) and the read of the
pipe to be in the child (so that the child can /read/ the parent's
output and treat it as its stdin). We close the respective unused end of
the pipe in the parent and the child. In the child, we want whatever it
is reading through the pipe to replace the stdin, so we use dup2
to
copy the pipe read-end file descriptor to its stdin. In the parent, we
want to take the text and send it through the pipe; we can use write
because each end of the pipe is given by a file descriptor, so writing
to a pipe is equivalent to writing to a file.
int main(int argc, char *argv[]) {
if (argc != 3) {
printf(“Usage: %s program-name text-to-pipe”, argv[0]);
exit(1);
}
int pipefd[2];
pipe(pipefd); // creates the pipe
pid_t pid = fork();
if (pid == -1) {
perror(“Could not fork!\n”);
} else if (pid == 0) { // child
close(pipefd[1]); // close unused write end (for child)
// make child’s stdin the pipe’s read end
dup2(pipefd[0], STDIN_FILENO);
close(pipefd[0]);
char *child_argv[] = {argv[1], NULL};
execvp(argv[1], child_argv); // does not return on success
perror(“Couldn’t execute program!\n”);
} else { // parent
close(pipefd[0]); // close unused read end (for parent)
// write the given text to the pipe’s write end
write(pipefd[1], argv[2], strlen(argv[2]));
close(pipefd[1]);
waitpid(pid, NULL, 0);
printf(“done\n”);
}
}
Exercise: Process Management
-
A
B (child1)
C
D (child2)
E
7
6, 1, 2
or 6, 2, 1
or 1, 6, 23, 1, 2
or 3, 2, 1
or 2, 3, 11, 2, 8, 9
or 2, 1, 9, 8, etc.OR
7
6, 1, 2
or 6, 2, 1
or 1, 6, 22
3, 1
1, 8, 9
or 1, 9, 8 -
Anything that doesn’t close the pipe’s write end will do it. Below we leave both ends of the pipe open in the shell. We could also enter just “3” in slot D.
A
B (child1)
C
D (child2)
E
7 6, 1, 2 3, 1, 2 8, 9 -
Anything that doesn’t redirect the left-hand side’s standard output will do it. It is important that the read end of the pipe be properly redirected, or
wc
would block reading from the shell’s standard input.A
B (child1)
C
D (child2)
E
7
1, 2
(anything without 6)3, 1, 2
1, 2, 8, 9
-
This happens when we execute the two sides of the pipe in series: first the left-hand side, then the right-hand side. Since the pipe contains 64KiB of buffering, this will often appear to work.
A
B (child1)
C
D (child2)
E
7 6, 1, 2 8 3, 1, 2 1, 2, 9 -
Given these system calls, the only way to make this happen is to redirect the wrong ends of the pipe into stdin/stdout.
A
B (child1)
C
D (child2)
E
7 4, 1, 2 5, 1, 2 1, 2, 8, 9