Unlock Linux IPC: Deep Dive into Pipes, Signals, Shared Memory and More
This comprehensive guide explores Linux inter‑process communication (IPC), explaining why processes need to communicate, the underlying kernel mechanisms, and detailed coverage of pipes, FIFOs, signals, files, shared memory, message queues, and sockets with practical code examples and real‑world use cases.
1. Linux Inter‑Process Communication Basics
In everyday life we constantly exchange information, just as processes in a Linux system need to communicate and cooperate. Without IPC, each process would be an isolated island, leading to system chaos.
Selecting the appropriate IPC method is crucial for performance, stability, and scalability. Future IPC technologies will aim for higher performance, better security, and smarter operation, especially in distributed systems, AI, and IoT.
1.1 Why Communicate?
Processes, like people, have communication needs and are isolated in separate address spaces. To exchange information and coordinate tasks, IPC is essential.
The choice of IPC method depends on requirements, data volume, and implementation constraints, similar to how humans choose fire‑signals, carrier pigeons, letters, telegrams, phone calls, or instant messages.
1.2 Why Is Communication Possible?
The kernel space is shared; although each process has its own user space, they can access the kernel via system calls, which act as a bridge for communication.
Linux processes are like independent rooms; the kernel is a central control room that mediates communication.
2. Linux IPC Framework
2.1 Structure of IPC Mechanisms
The IPC mechanism consists of a communication core in kernel space and user‑space interfaces, analogous to a post office (core) and letters or phones (interfaces).
2.2 Types of IPC Mechanisms
(1) Shared‑Memory‑Based : After the kernel creates a channel, both sides can communicate directly without further kernel assistance, similar to opening a door between two rooms. Synchronisation (e.g., semaphores) is required to avoid data races.
(2) Message‑Passing : Every communication still passes through the kernel, like a messenger delivering letters between rooms.
2.3 Interface Design
Message‑passing is typically used for asymmetric communication (client‑server).
Shared memory suits symmetric communication but can also support asymmetric patterns.
Three typical interfaces are needed: creating the channel, locating/joining the channel, and using the channel for read/write operations.
3. In‑Depth Analysis of Common IPC Methods
3.1 Pipes
Pipes are a fundamental IPC tool that act like a data conduit between processes. They come in two forms:
Anonymous Pipes
Anonymous pipes exist only in memory and are unidirectional, mainly used between related processes (e.g., parent‑child). The pipe() call returns two file descriptors: one for reading, one for writing.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1024
int main() {
int pipe_fd[2];
pid_t pid;
char buffer[BUFFER_SIZE];
if (pipe(pipe_fd) == -1) { perror("pipe creation failed"); exit(1); }
pid = fork();
if (pid == -1) { perror("fork failed"); exit(1); }
else if (pid == 0) { // child
close(pipe_fd[1]);
ssize_t bytes = read(pipe_fd[0], buffer, BUFFER_SIZE-1);
if (bytes == -1) { perror("read failed"); exit(1); }
buffer[bytes] = '\0';
printf("Child received: %s
", buffer);
close(pipe_fd[0]);
exit(0);
} else { // parent
close(pipe_fd[0]);
const char *msg = "Hello, child!";
write(pipe_fd[1], msg, strlen(msg));
close(pipe_fd[1]);
wait(NULL);
printf("Parent sent: %s
", msg);
}
return 0;
}Advantages: simple, fast, no disk I/O. Disadvantages: half‑duplex, limited to related processes, limited buffer size.
Named Pipes (FIFO)
Named pipes have a pathname in the file system, allowing unrelated processes to communicate.
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#define FIFO_NAME "myfifo"
#define BUFFER_SIZE 1024
int main() {
int fd;
if (mkfifo(FIFO_NAME, 0666) == -1 && errno != EEXIST) { perror("mkfifo"); return 1; }
fd = open(FIFO_NAME, O_WRONLY);
const char *msg = "Hello, client!";
write(fd, msg, strlen(msg));
close(fd);
return 0;
}Named pipes are suitable for long‑lived communication, such as logging systems.
3.2 Signals
Signals are asynchronous notifications from the kernel to a process (e.g., Ctrl‑C generates SIGINT). Processes can ignore, use default handling, or install custom handlers.
SIGINT (2): interrupt from keyboard.
SIGTERM (15): graceful termination.
SIGKILL (9): forced termination.
SIGCHLD (17): child status change.
import signal, os, sys, time
def handle_sigint(signum, frame):
print(f"
Received SIGINT({signum}) – user interrupt")
print("Cleaning up…")
time.sleep(1)
print("Cleanup done, exiting")
sys.exit(0)
def handle_sigterm(signum, frame):
print(f"
Received SIGTERM({signum}) – termination request")
sys.exit(0)
def handle_sighup(signum, frame):
print(f"
Received SIGHUP({signum}) – terminal hangup")
print("Reloading config…")
time.sleep(1)
print("Config reloaded")
def handle_sigchld(signum, frame):
print(f"
Received SIGCHLD({signum}) – child state change")
try:
while True:
pid, status = os.waitpid(-1, os.WNOHANG)
if pid == 0:
break
print(f"Child {pid} exited, status {status}")
except OSError:
pass
def main():
signal.signal(signal.SIGINT, handle_sigint)
signal.signal(signal.SIGTERM, handle_sigterm)
signal.signal(signal.SIGHUP, handle_sighup)
signal.signal(signal.SIGCHLD, handle_sigchld)
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
print(f"PID: {os.getpid()}")
print("Running – press Ctrl+C or send signals…")
while True:
time.sleep(1)
if __name__ == "__main__":
main()Signals are lightweight but can only carry a small amount of information.
3.3 Files
Processes can exchange data by reading and writing the same file, similar to using a shared notebook. File locks (shared or exclusive) are required to avoid race conditions.
3.4 Shared Memory
Shared memory provides the fastest IPC because data is not copied between processes. Synchronisation (e.g., semaphores) must be added for safe concurrent access.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define TEXT_SZ 2048
struct shared_use_st { char text[TEXT_SZ]; };
int main() {
int shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
if (shmid == -1) { perror("shmget"); exit(1); }
void *shm = shmat(shmid, 0, 0);
if (shm == (void *)-1) { perror("shmat"); exit(1); }
struct shared_use_st *shared = shm;
strcpy(shared->text, "Data to be shared");
printf("Read from shared memory: %s
", shared->text);
shmdt(shm);
shmctl(shmid, IPC_RMID, 0);
return 0;
}3.5 Message Queues
Message queues act like a mailbox where processes can post and retrieve messages, supporting asynchronous communication and message categorisation.
#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>
#define MSG_SIZE 128
typedef struct { long mtype; char mtext[MSG_SIZE]; } message_buf;
int main() {
key_t key = ftok(".", 'a');
int msgid = msgget(key, IPC_CREAT | 0666);
message_buf msg = {1, "Hello, message queue!"};
msgsnd(msgid, &msg, strlen(msg.mtext)+1, 0);
printf("Message sent: %s
", msg.mtext);
msgrcv(msgid, &msg, MSG_SIZE, 1, 0);
printf("Message received: %s
", msg.mtext);
msgctl(msgid, IPC_RMID, NULL);
return 0;
}3.6 Sockets
Sockets provide network‑level communication and can also be used for local IPC (UNIX domain sockets).
import socket, os, signal, sys
SOCKET_PATH = "/tmp/local_ipc_socket"
def handle_sigint(signum, frame):
print("
Interrupt received, cleaning up…")
if os.path.exists(SOCKET_PATH): os.unlink(SOCKET_PATH)
sys.exit(0)
def run_server():
signal.signal(signal.SIGINT, handle_sigint)
if os.path.exists(SOCKET_PATH): os.unlink(SOCKET_PATH)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(SOCKET_PATH)
server.listen(1)
client, _ = server.accept()
while True:
data = client.recv(1024)
if not data: break
print(f"Received: {data.decode()}")
client.sendall(f"Server got: {data.decode()}".encode())
if data.decode().lower() == 'exit': break
client.close(); server.close(); os.unlink(SOCKET_PATH)
if __name__ == "__main__":
run_server()4. Linux IPC Case Studies
4.1 Pipe Example
Parent creates a pipe, writes a message, child reads and prints it.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>
#define BUFFER_SIZE 1024
int main() {
int pipefd[2];
pid_t pid;
char buffer[BUFFER_SIZE];
if (pipe(pipefd) == -1) { perror("pipe"); exit(1); }
pid = fork();
if (pid == 0) { // child
close(pipefd[1]);
ssize_t n = read(pipefd[0], buffer, BUFFER_SIZE-1);
buffer[n] = '\0';
printf("Child read: %s
", buffer);
close(pipefd[0]);
exit(0);
} else { // parent
close(pipefd[0]);
const char *msg = "Hello from parent!";
write(pipefd[1], msg, strlen(msg));
close(pipefd[1]);
wait(NULL);
printf("Parent done
");
}
return 0;
}4.2 Message Queue Example
Sender process posts a message; receiver reads until an "exit" message is received.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
struct msg_buffer { long msg_type; char msg_text[1024]; };
int main() {
key_t key = ftok("msg_queue_demo", 65);
int msgid = msgget(key, 0666 | IPC_CREAT);
struct msg_buffer msg = {1, "Hello from sender!"};
msgsnd(msgid, &msg, sizeof(msg.msg_text), 0);
strcpy(msg.msg_text, "exit");
msgsnd(msgid, &msg, sizeof(msg.msg_text), 0);
sleep(1);
msgctl(msgid, IPC_RMID, NULL);
return 0;
} #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <sys/ipc.h>
struct msg_buffer { long msg_type; char msg_text[1024]; };
int main() {
key_t key = ftok("msg_queue_demo", 65);
int msgid = msgget(key, 0666);
struct msg_buffer msg;
while (1) {
msgrcv(msgid, &msg, sizeof(msg.msg_text), 1, 0);
printf("Received: %s
", msg.msg_text);
if (strcmp(msg.msg_text, "exit") == 0) break;
}
return 0;
}4.3 Shared Memory Example
Writer creates shared memory, writes a string, and pauses for the reader.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
#define SHM_KEY 0x123456
int main() {
int shmid = shmget(SHM_KEY, SHM_SIZE, 0666 | IPC_CREAT);
char *addr = shmat(shmid, NULL, 0);
const char *msg = "Hello from shared memory writer!";
strncpy(addr, msg, SHM_SIZE-1);
printf("Wrote: %s
", msg);
sleep(5);
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
} #include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
#define SHM_KEY 0x123456
int main() {
int shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
char *addr = shmat(shmid, NULL, 0);
printf("Read: %s
", addr);
shmdt(addr);
return 0;
}4.4 Signal Example
Receiver registers handlers for SIGUSR1 and SIGUSR2; sender sends these signals to the receiver’s PID.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void handle_usr1(int s){ printf("Got SIGUSR1, PID %d
", getpid()); }
void handle_usr2(int s){ printf("Got SIGUSR2, exiting
"); exit(0); }
int main(){
signal(SIGUSR1, handle_usr1);
signal(SIGUSR2, handle_usr2);
printf("Receiver PID: %d
", getpid());
while(1) pause();
return 0;
} #include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
int main(int argc, char *argv[]){
if(argc!=2){ fprintf(stderr,"Usage: %s <PID>
",argv[0]); exit(1); }
pid_t pid = (pid_t)atoi(argv[1]);
kill(pid, SIGUSR1);
sleep(1);
kill(pid, SIGUSR2);
return 0;
}4.5 Socket Example
Server listens on TCP port 8080, client connects, sends a message, and receives a reply.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(){
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr = { .sin_family=AF_INET, .sin_addr.s_addr=INADDR_ANY, .sin_port=htons(PORT) };
bind(server_fd, (struct sockaddr*)&addr, sizeof(addr));
listen(server_fd, 5);
int client = accept(server_fd, NULL, NULL);
char buf[BUFFER_SIZE];
read(client, buf, BUFFER_SIZE);
printf("Received: %s
", buf);
const char *resp = "Server got your message";
send(client, resp, strlen(resp), 0);
close(client); close(server_fd);
return 0;
} #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main(){
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv = { .sin_family=AF_INET, .sin_port=htons(PORT) };
inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr);
connect(sock, (struct sockaddr*)&serv, sizeof(serv));
const char *msg = "Hello, server!";
send(sock, msg, strlen(msg), 0);
char buf[BUFFER_SIZE];
read(sock, buf, BUFFER_SIZE);
printf("Server replied: %s
", buf);
close(sock);
return 0;
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Deepin Linux
Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
