Process I/O
Systems Programming
COP-3402
Table of Contents
Process review
What's a process?
How do we create new processes in UNIX?
Interprocess Communication
- Processes are isolated
- After exec, different programs
- Fork/exec
- No longer same address space
- No longer same running program
- Process isolation protects from accessing each other's memory
- How do we pass information between them?
Example
Pass ls to wc
What do ls and wc do?
Using bash
ls | wc
Send output of ls to input of wc
- ls and wc and separate processes
- How does using
|send information between them?
What is standard I/O in UNIX?
Pipe overview
- Pipe for inter-process communication
- Create pipe
- Two new files
- Write to one, data can be read from the other
Diagram
- Pipe
- ls sends info to pipe
- wc reads info from pipe
- Network sockets
- Similar file-like interface (read/write vs. send/recv)
- Different implementation
- Swap out pipe with socket
Creating pipes
| Symbol | Reference | Reading |
|---|---|---|
| pipe() | man 2 pipe |
LPI 44.2 |
man 2 pipe
pipe.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv) {
int pipefd[2];
char buf;
pid_t cpid;
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (-1 == cpid) {
perror("fork");
exit(EXIT_FAILURE);
}
if (0 == cpid) {
// child
close(pipefd[1]); // close write end (parent does writing)
while (read(pipefd[0], &buf, 1) > 0) {
write(STDOUT_FILENO, &buf, 1);
}
write(STDOUT_FILENO, "\n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else {
// parent
close(pipefd[0]); // close read end (child does reading)
write(pipefd[1], argv[1], strlen(argv[1]));
close(pipefd[1]);
wait(NULL);
exit(EXIT_SUCCESS);
}
}
Piping between different programs
Example: "ls | wc"
Setup
- Three processes
- Master process
- ls
- wc
- Master process
- Creates the pipe
- Fork/exec the two programs
- Replace stdio with the pipe
Algorithm for master process
- Create pipe
- Fork process for ls
- Replace ls's stdout with the pipe
- Run ls with the new stdout
- Fork process for wc
- Replace wc's stdin with the pipe
- Run wc with the new stdin
- Wait for processes the finish
Questions:
- Why does forking itself not run ls or wc? What does forking do?
- Why is ls's stdout replaced, not its stdin?
- What happens to ls's stdin?
Diagram
- Master process: filter.c
- Create pipe
- Fork new process
- Redirect process's output to the pipe
- Run ls
- Fork another process
- Redirect process's input to the pipe
- Run wc
- Let everything run as usual
- Note the benefit of having stdio
- Programs don't need to written specifically for piping. Availability of stdio makes this possible.
filter.c
- A program that runs "ls | wc"
- Create a pipe
- Fork-exec two processes
- Make ls write to the pipe
- Make wc read from the pipe
Implementat in the style of stepwise refinement.
Pseudo-code
pipefd = new pipe // create a new pipe
leftpid = fork // fork process for ls
if in child // code for the child
close(pipefd[read_end]) // close read end, since left child writes
dup2(pipefd(write_end), stdout) // replace stdou with the pipe
exec("ls")
// ls is running, control not returned here
// back in the paren
rightpid = fork
if in child
close(pipefd[write_end]) // close the write end, since right child reads
dup2(pipefd(read_end), stdin) // replace stdin with the pipe's output
exec("wc")
// wc is running, control not returned here
// back in the parent
close(pipefd[read_end])
close(pipefd[write_end])
wait() // wait for one child
wait() // wait for the other
Documentation
| Symbol | Reference | Reading |
|---|---|---|
| pipe() | man 2 pipe |
LPI 44.2 |
| dup2() | man 2 close |
LPI 44.4 |
pipe_exec.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h> // pipe(), dup2()
// man 2 pipe
// man 2 dup2
int main(int argc, char **argv) {
int pipefd[2];
char buf;
pid_t leftpid, rightpid;
if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
// pipefd[1] is the write end
// pipefd[0] is the read end
// ls | wc
// create a process for the first program, ls
leftpid = fork();
if (-1 == leftpid) {
perror("fork");
exit(EXIT_FAILURE);
}
if (0 == leftpid) {
// child
if (close(pipefd[0]) == -1) { // close read end, since that will be used by the second program
perror("close");
exit(EXIT_FAILURE);
}
// redirect the program's standard out with the input to the pipe (write end, which is pipefd[1])
printf("rightpipe dup2(%d, %d)\n", pipefd[1], STDOUT_FILENO);
if (dup2(pipefd[1], STDOUT_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
if (close(pipefd[1]) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
char *prog = "/usr/bin/ls";
char *newargv[] = { NULL };
char *newenv[] = { NULL };
execve(prog, newargv, newenv);
perror("execve");
_exit(EXIT_FAILURE);
} else {
// in parent, continue to fork second program
}
rightpid = fork();
if (-1 == rightpid) {
perror("fork");
exit(EXIT_FAILURE);
}
if (0 == rightpid) {
// child
if (close(pipefd[1]) == -1) { // close write, since that is being used by the first program
perror("close");
exit(EXIT_FAILURE);
}
// redirect the program's standard in with the output of the pipe (read end, which is pipefd[0])
printf("leftpipe dup2(%d, %d)\n", pipefd[0], STDIN_FILENO);
if (dup2(pipefd[0], STDIN_FILENO) == -1) {
perror("dup2");
exit(EXIT_FAILURE);
}
if (close(pipefd[0]) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
char *prog = "/usr/bin/wc";
char *newargv[] = { NULL };
char *newenv[] = { NULL };
execve(prog, newargv, newenv);
perror("execve");
_exit(EXIT_FAILURE);
} else {
// in parent, continue to wrap up
}
// back in parent
// close pipefds
if (close(pipefd[0]) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
if (close(pipefd[1]) == -1) {
perror("close");
exit(EXIT_FAILURE);
}
// wait for both children
if (wait(NULL) == -1) {
perror("wait");
exit(EXIT_FAILURE);
}
if (wait(NULL) == -1) {
perror("wait");
exit(EXIT_FAILURE);
}
}
wait.c
#include <stdio.h>
#include <unistd.h> // fork()
#include <stdlib.h> // exit()
#include <inttypes.h> // intmax_t
#include <sys/wait.h> // wait()
// man 2 fork
// man 2 execve
// man 2 wait
int main(int argc, char **argv) {
pid_t pid;
switch (pid = fork()) {
case -1:
perror("fork");
exit(EXIT_FAILURE);
break;
case 0:
// child
puts("inside child process\n");
char *prog = "/usr/bin/ls";
char *newargv[] = { NULL, "./", NULL };
char *newenv[] = { NULL };
execve(prog, newargv, newenv);
perror("execve");
_exit(EXIT_FAILURE);
break;
default:
// parent
int wstatus;
printf("child pid: %jd\n", (intmax_t) pid);
do {
pid_t wpid = wait(&wstatus);
if (-1 == wpid) {
perror("waid");
exit(EXIT_FAILURE);
}
if (WIFEXITED(wstatus)) {
printf("exited, status=%d\n", WEXITSTATUS(wstatus));
} else if (WIFSIGNALED(wstatus)) {
printf("killed by signal %d\n", WTERMSIG(wstatus));
} else if (WIFSTOPPED(wstatus)) {
printf("stopped by signal %d\n", WSTOPSIG(wstatus));
} else if (WIFCONTINUED(wstatus)) {
printf("continued\n");
}
} while (!WIFEXITED(wstatus) && !WIFSIGNALED(wstatus));
exit(EXIT_SUCCESS);
break;
}
}