Fundamentals 32 min read

Master Linux Inter‑Process Communication: Pipes, Signals, Shared Memory & More

This guide explains Linux inter‑process communication (IPC) mechanisms—including anonymous and named pipes, signals, file‑based communication, semaphores, various shared‑memory techniques, message queues, TCP/UDP sockets, and Unix domain sockets—providing concepts, typical use cases, and complete C code examples for each method.

Liangxu Linux
Liangxu Linux
Liangxu Linux
Master Linux Inter‑Process Communication: Pipes, Signals, Shared Memory & More

Introduction

Linux runs many processes simultaneously. Inter‑Process Communication (IPC) lets processes exchange data. The kernel provides several IPC mechanisms, each suited to different relationships, data volumes, latency, and persistence requirements.

1. Pipes

Anonymous pipe

Concept : A one‑way byte stream that exists only in memory and can be used between related processes (e.g., parent and child). Full‑duplex communication requires two pipes.

#include <unistd.h>
int main() {
    int pipefd[2];
    pipe(pipefd);               // create pipe
    if (fork() == 0) {         // child
        close(pipefd[1]);       // close write end
        char buf[5];
        read(pipefd[0], buf, 5);
    } else {                    // parent
        close(pipefd[0]);       // close read end
        write(pipefd[1], "hello", 5);
    }
    return 0;
}

Named pipe (FIFO)

Concept : A special file in the filesystem that unrelated processes can open for reading or writing.

// server.c
int main() {
    const char *fifoPath = "/tmp/my_fifo";
    mkfifo(fifoPath, 0666);
    char buf[1024];
    int fd;
    while (1) {
        fd = open(fifoPath, O_RDONLY);
        read(fd, buf, sizeof(buf));
        printf("Received: %s
", buf);
        close(fd);
    }
    return 0;
}
// client.c
int main() {
    const char *fifoPath = "/tmp/my_fifo";
    char buf[1024];
    printf("Enter message: ");
    fgets(buf, sizeof(buf), stdin);
    int fd = open(fifoPath, O_WRONLY);
    write(fd, buf, strlen(buf) + 1);
    close(fd);
    return 0;
}

2. Signals

Concept : Asynchronous notifications sent by the kernel or another process (e.g., SIGINT, SIGTERM). They are useful for handling exceptions, external interrupts, timers, and child‑process status changes.

List signals with kill -l:

1) SIGHUP   2) SIGINT   3) SIGQUIT  4) SIGILL   5) SIGTRAP
6) SIGABRT  7) SIGBUS   8) SIGFPE   9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
...

Simple handler example :

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
void signal_handler(int sig) {
    printf("Received signal: %d
", sig);
}
int main() {
    signal(SIGINT, signal_handler);
    while (1) sleep(1);
    return 0;
}

3. Files as IPC

Processes can read/write a common file to exchange data. When multiple writers are involved, file locks (fcntl or flock) prevent race conditions.

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
// writer
int main() {
    const char *file = "/tmp/ipc_file";
    int fd = open(file, O_RDWR | O_CREAT, 0666);
    write(fd, "Hello from Process A", 20);
    close(fd);
    return 0;
}
// reader
int main() {
    const char *file = "/tmp/ipc_file";
    int fd = open(file, O_RDWR);
    char buf[50];
    read(fd, buf, 20);
    close(fd);
    return 0;
}
Note : Use fcntl(fd, F_SETLKW, &amp;fl) or flock(fd, LOCK_EX) to serialize concurrent writes.

4. Semaphores

Concept : A counter used to synchronize access to shared resources. POSIX semaphores are preferred; they exist either anonymously (memory‑only) or with a name in the filesystem.

Named‑semaphore example (mutual exclusion for a log file) :

#include <semaphore.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
    FILE *log = fopen("logfile.txt", "a");
    sem_t *sem = sem_open("/log_semaphore", O_CREAT, 0644, 1);
    sem_wait(sem);
    fprintf(log, "Log from process %d
", getpid());
    fflush(log);
    sem_post(sem);
    sem_close(sem);
    fclose(log);
    return 0;
}

5. Shared Memory

Shared memory offers the fastest IPC by mapping the same physical memory into multiple processes.

5.1 Anonymous shared memory (mmap)

#include <sys/mman.h>
size_t size = 4096;
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
/* use *addr */
munmap(addr, size);

5.2 File‑backed shared memory

#include <sys/mman.h>
#include <fcntl.h>
size_t size = 4096;
int fd = open("shared_file", O_RDWR|O_CREAT, 0666);
ftruncate(fd, size);
void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
/* use *addr */
munmap(addr, size);
close(fd);

5.3 POSIX shared memory

#include <sys/mman.h>
int fd = shm_open("/example_shm", O_CREAT|O_RDWR, 0666);
ftruncate(fd, 4096);
void *addr = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
/* ... */
munmap(addr, 4096);
shm_unlink("/example_shm");

5.4 System V shared memory

#include <sys/shm.h>
key_t key = ftok("sharedfile", 'A');
int shmid = shmget(key, 1024, IPC_CREAT|0666);
char *addr = shmat(shmid, NULL, 0);
/* ... */
shmdt(addr);
shmctl(shmid, IPC_RMID, NULL);

Example: two unrelated processes synchronizing access to a file‑backed segment with a named semaphore :

// writer.c
int main() {
    const char *file = "shared_file";
    size_t size = 4096;
    int fd = open(file, O_RDWR|O_CREAT, 0666);
    void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    sem_t *sem = sem_open("/mysemaphore", O_CREAT, 0666, 1);
    sem_wait(sem);
    strcpy((char*)addr, "Hello from writer");
    sem_post(sem);
    munmap(addr, size);
    close(fd);
    sem_close(sem);
    return 0;
}
// reader.c
int main() {
    const char *file = "shared_file";
    size_t size = 4096;
    int fd = open(file, O_RDONLY);
    void *addr = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
    sem_t *sem = sem_open("/mysemaphore", 0);
    sem_wait(sem);
    printf("Read: %s
", (char*)addr);
    sem_post(sem);
    munmap(addr, size);
    close(fd);
    sem_close(sem);
    return 0;
}

6. Message Queues (System V)

Processes send messages identified by a type; receivers can fetch messages of a specific type.

#include <sys/ipc.h>
#include <sys/msg.h>
struct msgbuf { long mtype; char mtext[100]; };
int main() {
    key_t key = ftok("queuefile", 65);
    int msqid = msgget(key, IPC_CREAT|0666);
    struct msgbuf msg = {1, "Hello World"};
    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
    // receiver
    msgrcv(msqid, &msg, sizeof(msg.mtext), 1, 0);
    printf("Received: %s
", msg.mtext);
    msgctl(msqid, IPC_RMID, NULL);
    return 0;
}

7. Sockets (TCP/UDP)

Sockets enable communication on the same host or across a network. TCP provides reliable, connection‑oriented streams; UDP offers connectionless datagrams.

// TCP server (server.c)
int main() {
    int srv = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = { .sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY, .sin_port = htons(8080) };
    bind(srv, (struct sockaddr*)&addr, sizeof(addr));
    listen(srv, 3);
    while (1) {
        int client = accept(srv, NULL, NULL);
        char buf[1024];
        read(client, buf, sizeof(buf));
        printf("Client: %s
", buf);
        close(client);
    }
    close(srv);
    return 0;
}
// TCP client (client.c)
int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in srv = { .sin_family = AF_INET, .sin_port = htons(8080) };
    inet_pton(AF_INET, "127.0.0.1", &srv.sin_addr);
    connect(sock, (struct sockaddr*)&srv, sizeof(srv));
    const char *msg = "Hello from client";
    send(sock, msg, strlen(msg), 0);
    close(sock);
    return 0;
}

8. Unix Domain Sockets

Local‑only sockets that use a filesystem pathname instead of an IP address, offering higher performance for same‑host IPC.

// server (server.c)
int main() {
    int srv = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/unix_socket");
    bind(srv, (struct sockaddr*)&addr, sizeof(addr));
    listen(srv, 5);
    while (1) {
        int client = accept(srv, NULL, NULL);
        char buf[100];
        read(client, buf, sizeof(buf));
        printf("Received: %s
", buf);
        close(client);
    }
    close(srv);
    unlink("/tmp/unix_socket");
    return 0;
}
// client (client.c)
int main() {
    int sock = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "/tmp/unix_socket");
    connect(sock, (struct sockaddr*)&addr, sizeof(addr));
    const char *msg = "Hello from client";
    write(sock, msg, strlen(msg));
    close(sock);
    return 0;
}
Note : Remove the socket file with unlink() after the server terminates.

Summary

Linux provides a rich set of IPC mechanisms—from simple pipes and files to powerful sockets and shared‑memory techniques. Selecting the appropriate method depends on process relationships, data size, latency, and persistence needs. Understanding each mechanism’s characteristics enables developers to build robust, efficient inter‑process communication for both small utilities and complex multi‑process applications.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

message queuesLinuxIPCsemaphores
Liangxu Linux
Written by

Liangxu Linux

Liangxu, a self‑taught IT professional now working as a Linux development engineer at a Fortune 500 multinational, shares extensive Linux knowledge—fundamentals, applications, tools, plus Git, databases, Raspberry Pi, etc. (Reply “Linux” to receive essential resources.)

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.