Inter-Process Communication (IPC)¶
Overview¶
IPC mechanisms allow processes to exchange data and coordinate actions. Linux provides several IPC methods, each with different characteristics and use cases.
Pipes¶
Pipes provide unidirectional data flow between processes.
Unnamed Pipes¶
Created with pipe() system call, only between related processes (parent-child).
#include <unistd.h>
int pipefd[2];
pipe(pipefd); // pipefd[0] = read end, pipefd[1] = write end
if (fork() == 0) {
// Child writes
close(pipefd[0]);
write(pipefd[1], "Hello", 5);
close(pipefd[1]);
exit(0);
} else {
// Parent reads
close(pipefd[1]);
char buf[10];
read(pipefd[0], buf, 5);
close(pipefd[0]);
}
Characteristics:
- Unidirectional: One-way communication
- Related processes only: Must share file descriptors
- Buffer size: Typically 64KB (check with
ulimit -p) - Blocking: Reads block until data available, writes block when full
- Anonymous: No filesystem entry
Shell Pipes:
Named Pipes (FIFOs)¶
Pipes with filesystem entries, allowing unrelated processes to communicate.
# Create FIFO
mkfifo /tmp/mypipe
# Process 1 (writer)
echo "Hello" > /tmp/mypipe
# Process 2 (reader)
cat /tmp/mypipe
#include <sys/stat.h>
#include <fcntl.h>
// Create FIFO
mkfifo("/tmp/mypipe", 0666);
// Writer
int fd = open("/tmp/mypipe", O_WRONLY);
write(fd, "Hello", 5);
close(fd);
// Reader
int fd = open("/tmp/mypipe", O_RDONLY);
char buf[10];
read(fd, buf, 5);
close(fd);
Characteristics:
- Filesystem entry: Has a pathname
- Unrelated processes: Any process can open
- Persistent: Exists until unlink()
- Blocking opens: Writer blocks until reader, vice versa
How Pipes Work at Kernel Level¶
graph LR
A[Process A] -->|write| B[Kernel Buffer]
B -->|read| C[Process B]
Kernel implementation:
- Ring buffer: Circular buffer in kernel memory
- File descriptors: Both ends are file descriptors
- Synchronization: Kernel handles blocking/waking
- Size limit: Default 64KB (can be adjusted)
# View pipe buffer size
ulimit -p
cat /proc/sys/fs/pipe-max-size
# Adjust pipe size (in code)
fcntl(fd, F_SETPIPE_SZ, 1048576); // 1MB
Signals¶
Signals are asynchronous notifications sent to processes.
Common Signals¶
| Signal | Number | Default Action | Description |
|---|---|---|---|
| SIGHUP | 1 | Terminate | Hangup (terminal closed) |
| SIGINT | 2 | Terminate | Interrupt (Ctrl+C) |
| SIGQUIT | 3 | Core dump | Quit (Ctrl+\) |
| SIGILL | 4 | Core dump | Illegal instruction |
| SIGTRAP | 5 | Core dump | Trace trap (debugger) |
| SIGABRT | 6 | Core dump | Abort (assert failure) |
| SIGBUS | 7 | Core dump | Bus error (alignment) |
| SIGFPE | 8 | Core dump | Floating point exception |
| SIGKILL | 9 | Terminate | Kill (cannot be caught) |
| SIGSEGV | 11 | Core dump | Segmentation fault |
| SIGPIPE | 13 | Terminate | Broken pipe |
| SIGALRM | 14 | Terminate | Timer alarm |
| SIGTERM | 15 | Terminate | Termination request |
| SIGCHLD | 17 | Ignore | Child process status changed |
| SIGCONT | 18 | Continue | Continue if stopped |
| SIGSTOP | 19 | Stop | Stop (cannot be caught) |
| SIGTSTP | 20 | Stop | Terminal stop (Ctrl+Z) |
Signal Handling¶
#include <signal.h>
// Signal handler
void handler(int signum) {
printf("Caught signal %d\n", signum);
}
// Register handler
signal(SIGINT, handler);
// or better:
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, NULL);
Sending signals:
# Send signal to process
kill -TERM <PID>
kill -9 <PID> # SIGKILL
killall program
# From code
kill(pid, SIGTERM);
raise(SIGTERM); // Send to self
Signal Safety¶
Async-Signal-Safe Functions
Signal handlers can only call async-signal-safe functions. Most library functions are NOT safe!
Safe functions include:
write(),read()_exit()signal(),sigaction()
NOT safe:
printf(),malloc(),free()- Most library functions
// BAD: printf is not async-signal-safe
void bad_handler(int sig) {
printf("Signal %d\n", sig);
}
// GOOD: use write()
void good_handler(int sig) {
const char msg[] = "Signal caught\n";
write(STDOUT_FILENO, msg, sizeof(msg)-1);
}
Signal Masking¶
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
// Block SIGINT
sigprocmask(SIG_BLOCK, &set, NULL);
// Critical section
// SIGINT will be pending, not delivered
// Unblock
sigprocmask(SIG_UNBLOCK, &set, NULL);
Sockets¶
Sockets provide bidirectional communication between processes.
Unix Domain Sockets¶
For IPC on same machine, more efficient than TCP.
#include <sys/socket.h>
#include <sys/un.h>
// Server
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mysock");
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
listen(sock, 5);
int client = accept(sock, NULL, NULL);
// Client
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mysock");
connect(sock, (struct sockaddr*)&addr, sizeof(addr));
Advantages over network sockets:
- Faster (no network stack)
- Can pass file descriptors
- Filesystem permissions
- More secure
Network Sockets¶
See Networking Stack section for detailed coverage.
Message Queues¶
Ordered list of messages stored in kernel.
POSIX Message Queues¶
#include <mqueue.h>
// Create/open queue
mqd_t mq = mq_open("/myqueue", O_CREAT | O_RDWR, 0644, NULL);
// Send message
char msg[] = "Hello";
mq_send(mq, msg, sizeof(msg), 0);
// Receive message
char buffer[100];
mq_receive(mq, buffer, sizeof(buffer), NULL);
// Close and unlink
mq_close(mq);
mq_unlink("/myqueue");
Characteristics:
- Ordered: FIFO within same priority
- Priority: Messages have priorities
- Persistent: Survives process termination
- Limits: Max message size, queue size
System V Message Queues¶
Older API, still widely used.
#include <sys/msg.h>
// Create queue
key_t key = ftok("/tmp/myfile", 'A');
int msgid = msgget(key, IPC_CREAT | 0666);
// Send
struct msgbuf {
long mtype;
char mtext[100];
} msg;
msg.mtype = 1;
strcpy(msg.mtext, "Hello");
msgsnd(msgid, &msg, sizeof(msg.mtext), 0);
// Receive
msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0);
// Remove
msgctl(msgid, IPC_RMID, NULL);
Shared Memory¶
Fastest IPC method - processes map same physical memory.
POSIX Shared Memory¶
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
// Create shared memory
int shm_fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, 4096);
// Map into address space
void *ptr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
MAP_SHARED, shm_fd, 0);
// Use memory
strcpy((char*)ptr, "Hello");
// Cleanup
munmap(ptr, 4096);
close(shm_fd);
shm_unlink("/myshm");
System V Shared Memory¶
#include <sys/shm.h>
// Create segment
key_t key = ftok("/tmp/myfile", 'B');
int shmid = shmget(key, 4096, IPC_CREAT | 0666);
// Attach
void *ptr = shmat(shmid, NULL, 0);
// Use
strcpy((char*)ptr, "Hello");
// Detach and remove
shmdt(ptr);
shmctl(shmid, IPC_RMID, NULL);
Important:
Synchronization Required
Shared memory provides no synchronization! You must use mutexes, semaphores, or other mechanisms to coordinate access.
Semaphores¶
Semaphores coordinate access to shared resources (covered in detail in Synchronization).
POSIX Semaphores¶
#include <semaphore.h>
// Named semaphore (inter-process)
sem_t *sem = sem_open("/mysem", O_CREAT, 0644, 1);
sem_wait(sem); // P operation (decrement)
// Critical section
sem_post(sem); // V operation (increment)
sem_close(sem);
sem_unlink("/mysem");
// Unnamed semaphore (thread/process)
sem_t sem;
sem_init(&sem, 1, 1); // pshared=1 for processes
sem_destroy(&sem);
System V Semaphores¶
#include <sys/sem.h>
// Create semaphore set
key_t key = ftok("/tmp/myfile", 'C');
int semid = semget(key, 1, IPC_CREAT | 0666);
// Initialize
semctl(semid, 0, SETVAL, 1);
// Wait (P)
struct sembuf op = {0, -1, 0};
semop(semid, &op, 1);
// Signal (V)
op.sem_op = 1;
semop(semid, &op, 1);
// Remove
semctl(semid, 0, IPC_RMID);
Comparison of IPC Mechanisms¶
| Mechanism | Speed | Use Case | Complexity |
|---|---|---|---|
| Pipes | Fast | Related processes, streaming | Low |
| FIFOs | Fast | Unrelated processes, streaming | Low |
| Signals | Fast | Notifications, simple events | Low |
| Unix Sockets | Fast | Local bidirectional comm | Medium |
| Message Queues | Medium | Structured messages | Medium |
| Shared Memory | Fastest | Large data, high performance | High |
Practice Questions¶
- What is the difference between named and unnamed pipes?
- Why can't you catch SIGKILL?
- What functions are async-signal-safe and why does it matter?
- Why is shared memory the fastest IPC mechanism?
- When would you use message queues instead of pipes?
- How do Unix domain sockets differ from network sockets?
- What happens if you write to a pipe with no readers?
Further Reading¶
man 7 pipe,man 3 mkfifoman 7 signal,man 2 sigactionman 7 unix,man 2 socketman 7 mq_overviewman 7 shm_overview- "The Linux Programming Interface" Chapters 44-54