8 Interprocess Communication
Published:
Pipes
Unnamed pipe
No file name, just an fd
#include <unistd.h>
int pipe(int fd[2]);
Kernel prepares a pipe structure, connecting fd[0]
(read) with fd[1]
(write) After fork()
, parent and child will share the same pipe.
- Pipe is one-way traffic (half duplex)
- Need to
close()
unused ends of pipe
int fd[2];
pipe(fd);
if (fork() == 0) {
close(fd[1]); // close unused write end
read(fd[0], ...);
} else {
close(fd[0]); // close unused read end
write(fd[1], ...)
}
connect2
How a shell would stitch together two processes for a pipeline (E. p1 | p2
becomes ./connect2 p1 -- p2
)
int dup2(int oldfd, int newfd);
If newfd
is open, dup2()
will close it first, and duplicate oldfd
to newfd
- Atomic operation
- Creates two child processes
- Stitch the IO together
pipe(fd);
if ((pid1 = fork()) == 0) {
close(fd[0]); // Close read end of pipe
dup2(fd[1], 1); // Redirect stdout to write end of pipe
close(fd[1]); // stdout already writes to pipe, close spare fd
execvp(*argv1, argv1); // fds preserved
}
if ((pid2 = fork()) == 0) {
close(fd[1]); // Close write end of pipe
dup2(fd[0], 0); // Redirect stdin from read end of pipe
close(fd[0]); // stdin already reads from pipe, close spare fd
execvp(*argv2, argv2);
}
// Parent does not need either end of the pipe
close(fd[0]);
close(fd[1]);
Named pipes
If something’s unnamed, you can only use it between related processes
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
Blocked in open()
until someone else opens the other end
- Fine for multiple processes to R/W
- But at least one reader and one writer.
- Analogous to unnamed pipe (
read()
will block for empty unnamed pipe, same for write)
Memory mapped IO
Memory Mapping `mmap()
- Shared, file-backed: can link unrelated processes
- Avoid physical file with
shm_open()
(shared mem open) - Will create a “fake file” under
/dev/shm
(an in-memory filesystem)
- Avoid physical file with
- Shared, anonymous: Must be related, no actual file involved
- Private, file-backed: private copy of the file in memory, not written back to file
- Private, anonymous: Just allocate memory (E. malloctopus)
POSIX Semaphore
Also available in unnamed and Named
- For multiple processes (instead of thread), semaphore object needs to reside in a file for every process to see it
Create a multi-process semaphore embedded in some other structure (counter
), and then putting it into a shared anonymous memory region
struct counter { // semaphore
sem_t sem;
int cnt;
};
static struct counter *counter = NULL;
// multiple processes will run this loop
static void inc_loop() {
for (int i = 0; i < 1000; i++) {
sem_wait(&counter->sem);
counter->cnt++;
sem_post(&counter->sem);
}
}
int main(int argc, char **argv) {
// Create a shared anonymous memory mapping, set global pointer to it
counter = mmap(/*addr=*/NULL, /*let mmap decide*/
sizeof(struct counter),
// Region is readable and writable
PROT_READ | PROT_WRITE,
// Want to share anonymous mapping with forked child
MAP_SHARED | MAP_ANONYMOUS,
/*fd=*/-1, // No associated file (anony)
/*offset=*/0);
assert(counter != MAP_FAILED);
assert(counter->cnt == 0); // Mapping is already zero-initialized.
sem_init(&counter->sem, /*pshared=*/1, /*value=(binary)*/1);
pid_t pid;
if ((pid = fork()) == 0) {
inc_loop();
return 0;
}
inc_loop();
waitpid(pid, NULL, 0);
printf("Total count: %d, Expected: %d\n", counter->cnt, 1000 * 2);
sem_destroy(&counter->sem);
munmap(counter, sizeof(struct counter)); // aka free
}
Unnamed semaphore are just sem_t
objects passed to sem_init()
and sem_destroy()
How about named semaphore? Passed to sem_open()
, and it gives back a pointer to a newly created named sem_t
object file.
- Necessary for unrelated processes