Sitemap
Mohamed Uvais

Mohamed Uvais

I am a Solution Architect in the automotive industry with a passion for sharing ideas and knowledge to help build a knowledgeable and thriving community

Linux IPC Tutorial: Shared Memory, Pipes, FIFO, Sockets, Semaphores and Signals Explained

12 min readOct 10, 2026

--

What is Linux IPC (Inter Process Communication) ?

In the previous article, we learned the detailed concepts of Linux Process and threads. As a next step, this article covers how threads and processes communicate with each other. Inter-process communication (IPC) is a method used to exchange data among multiple threads and processes. These threads may reside in one or more processes, and those processes may even run on different computers or targets. The Linux kernel coordinates such communication activities.

Conceptually, Linux inter-process communication is divided or selected based on the following categories:

  • Message passing
  • Event Notification
  • Memory Sharing
  • Synchronization

Refer to the diagram below, which illustrates Linux IPC categories and the associated IPC mechanisms.

Press enter or click to view image in full size
Linux IPC categories and associated IPC mechanisms

Types of IPC Mechanisms in Linux

  • Linux Shared Memory
  • Linux Memory mapped Files
  • Process Semaphores
  • Linux Pipes
  • Linux FIFO
  • Sockets
  • Linux Signals

Linux Shared Memory IPC

The simplest method to communicate, exchange, or share data between one or more processes is by using shared memory. When one process modifies the shared memory, another process can immediately see the modification. It is also one of the fastest communication methods since memory is simply shared between processes and no data copying is required.

Shared memory does not require a system call or kernel intervention for every memory access. This reduces user-space to kernel-space switching overhead when accessing data. However, since the kernel does not synchronize access to shared memory, applications must implement their own synchronization mechanisms. For example, memory read and write operations may occur simultaneously, which can lead to race conditions if not properly protected. Process semaphores can be used to avoid such race conditions.

Press enter or click to view image in full size
Shared Memory Mapping Between Multiple Processes

When shared memory is created using a specific key (for example, an ID shared between processes), the kernel allocates the memory segment and provides a shared memory ID. Any number of processes can use that key to acquire the same shared memory ID.

There are five main steps involved in accessing shared memory:

Allocate the segment

  • When shared memory is created using a specific key (for example, an ID shared between processes), the kernel allocates the segment and provides the shared memory ID. Any number of processes can use that key to acquire the ID. The System V IPC API used to allocate shared memory is shmget().

Attach the segment to the process

  • Since Linux maintains a separate virtual memory space for each process, the allocated memory must be mapped into the respective process address space. The System V IPC API used for this mapping is shmat().

Access the memory segment

  • As with a normal array or memory block, the shared memory can be read from or written to by any process that has mapped the segment into its address space. Once the shared memory is attached to a process, it can be accessed using regular memory operations without requiring special APIs for each read or write operation.
  • Since multiple processes may access the same memory segment simultaneously, proper synchronization mechanisms should be used to prevent race conditions and ensure data consistency.

Detach the segment from the process

  • This is typically done when the process has finished using the shared memory segment. The System V IPC API used to detach the memory segment is shmdt().

De-allocate segment

  • After all processes have detached the memory segment, it should be de-allocated so that the memory can be freed for other processes. The System V IPC API used to de-allocate the segment is shmctl().

Useful tools to monitor and control shared memory are ipcs and ipcrm.

Please note that shared memory has a security risk in that any process with sufficient permissions and knowledge of the shared memory key can potentially access the shared memory segment. Therefore, appropriate access permissions and security considerations should be applied when using shared memory.

Memory Mapped Files

This is another efficient way of sharing data between processes. Memory-mapped files allow different processes to communicate through a shared file. It creates an association between a file and a process’s memory space. This means that you do not need to create shared memory explicitly. Instead, a process can access the file contents using ordinary memory access operations.

This concept is relatively easy to understand because it works much like creating a file and using it across different processes. However, instead of reading and writing the file through file I/O operations, the mmap() API is used to map the file contents into each process’s address space. Once mapped, the file contents can be accessed just like a normal array or memory buffer.

The following steps are required to create and access memory-mapped files:

Create or open the file

  • You can use the standard POSIX open() API to create a new file or open an existing file.

Map the file into memory

  • mmap() is the POSIX API used to map the file contents into the local process memory space. This API returns a memory address that can be assigned to a pointer and used to read from or write to the mapped file contents.

Access the shared memory

  • Once the file is mapped, the data can be accessed using normal memory operations. For example, APIs such as memcpy() can be used to read from or write to the mapped memory region.

Unmap the memory

  • munmap() is used to remove the memory mapping when it is no longer required

Close the file

  • The POSIX close() API is used to close the opened file descriptor when it is no longer required.

This approach combines the benefits of file-based sharing and direct memory access, making it an efficient mechanism for Linux inter-process communication.

Semaphore

A semaphore is a synchronization mechanism used to synchronize one or more processes or threads and avoid race conditions. A key difference between a semaphore and a mutex is that a semaphore maintains a count of available resources, whereas a mutex enforces exclusive ownership of a critical section.

Semaphores are broadly classified into two types:

Binary Semaphore

  • Used to protect a single resource across processes or threads.

Counting Semaphore

  • Used when multiple instances of a resource need to be protected. For example, if there are three printers and up to three users are allowed to print simultaneously, a counting semaphore can be used to synchronize access by maintaining the count of available resources.

The diagram below illustrates how a semaphore works when three resources need to be protected.

Press enter or click to view image in full size
Semaphore-Based Synchronization of Multiple Resources

There are four main steps involved when using a semaphore:

Note: POSIX and C++ APIs are shown as alternative implementations for the same synchronization concept. Typically, only one approach is used in a given application.

Initialize or allocate the semaphore:

  • This is the first step performed by a process or thread. It creates the semaphore and returns a semaphore handle that will be used in subsequent operations.

POSIX approach

  • sem_init() for synchronization within a process.
  • Alternatively, use sem_open() for synchronization across processes.
  • The initial resource count determines whether the semaphore behaves as a binary semaphore or a counting semaphore.

C++ approach:

  • std::binary_semaphore() and std::counting_semaphore() are used to create binary and counting semaphores respectively.

Acquire the semaphore before using the resource

  • Before entering a critical section or accessing a protected resource, the semaphore must be acquired.
  • POSIX approach: sem_wait() to acquire the semaphore
  • C++ approach: semaphore_object.acquire() to acquire the semaphore

Release the acquired semaphore:

  • When leaving the critical section or after finishing the use of the protected resource, the semaphore should be released.
  • POSIX approach: sem_post() releases the semaphore.
  • C++ approach: semaphore_object.release()

Destroy the semaphore:

  • Once the semaphore is no longer needed, it should be destroyed to free the associated resources.

POSIX approach:

  • sem_destroy() destroys a semaphore created using sem_init().
  • sem_close() closes a semaphore created using sem_open().

C++ approach:

Get Mohamed Uvais’s stories in your inbox

Join Medium for free to get updates from this writer.

No explicit destroy API is required. The semaphore object is automatically destroyed when it goes out of scope

Note: The order in which resources are locked is important to avoid deadlock situations. The same deadlock prevention principles that apply to mutexes also apply to semaphores.

Linux Pipes (Anonymous Pipes)

A Linux pipe, also known as an anonymous pipe, is a message-passing mechanism primarily used for unidirectional communication. Data is written to one end of the pipe and read from the other end. The data is read in the same order in which it was written (FIFO order).

A Linux pipe can be used to communicate between two threads within the same process, or between related processes created using fork(). If the writer writes data faster than the reader can consume it and the pipe buffer becomes full, the writer blocks until space becomes available. Similarly, if the reader attempts to read when no data is available, it blocks until data becomes available. In this way, a pipe provides built-in synchronization between the communicating entities.

A Linux pipe can typically be used in the following scenarios:

  • Communication between two threads within the same process.
  • Communication between related processes, such as a parent process and its child process created using fork().

For communication between independent, unrelated processes, refer to the FIFO (Named Pipe) mechanism described in the next section.

The following steps are typically involved when using a pipe:

Creating a Linux Pipe

  • The POSIX API pipe() is used to create a pipe that can be shared between threads within the same process or between related processes created using fork().

Write data to the Linux pipe

  • The POSIX API write() is used to write data to the pipe so that it can be read by another thread or process.

Read data from the Linux pipe

  • The POSIX API read() is used to read data from the pipe that was written by another thread or process.

Close the Linux pipe

  • The POSIX API close() is used to close the file descriptors associated with the pipe when they are no longer required.

Note: Since pipes are unidirectional, two pipes are typically required if bidirectional communication is needed between the communicating entities.

Linux FIFO (Named Pipes)

Linux FIFO (First In, First Out), also known as a named pipe, works in a similar way to an anonymous pipe. Data written to the FIFO is read in the same order in which it was written. The key difference is that a FIFO has a name in the file system. Due to this, it can be used for communication between independent processes.

A Linux FIFO can be created using the POSIX API mkfifo(). For example: mkfifo /tmp/MyFifo creates a FIFO at the specified path. It will create a FIFO in the specified path. Once the FIFO is created, any process or thread can use POSIX APIs open(), read(), and write() to access the FIFO and exchange data.

It is very similar to a pipe, except that it has a name in the file system, allowing independent processes to identify and access it.

I have illustrated both Linux pipes and Linux FIFOs with a simple diagram for easier understanding.

Press enter or click to view image in full size
Linux Pipe vs FIFO (Named Pipe)

Socket

A socket is a bi-directional inter-process communication mechanism. The main advantage of sockets is that they can be used to communicate between two processes running on the same machine or on different machines. Many standard applications such as SSH, FTP, and HTTP use socket communication. Similar to files, Linux provides socket descriptors that processes or threads can use to read and write data.

The following are the two main socket domains:

  • Network Socket (AF_INET) is typically used for communication using IP, which allows communication across different machines or processors.
  • Unix Domain Socket (AF_UNIX) is typically used for communication using file-system paths, providing faster communication between processes running on the same machine or processor.

Sockets can be created using either the stream type(SOCK_STREAM) or the datagram type (SOCK_DGRAM):

  • SOCK_STREAM is used for reliable TCP stream communication.
  • SOCK_DGRAM is used for UDP datagram communication.

The following illustration shows how socket communication works. It includes the POSIX APIs (for example, socket(), bind(), connect(), and accept()) required to establish client and server communication. The socket type (SOCK_STREAM or SOCK_DGRAM) is specified when creating the socket using socket().

Linux Socket Communication: Client-Server Connection Workflow

Several application-level IPC mechanisms are built on top of sockets. Some popular examples are:

  • D-Bus is built using AF_UNIX sockets.
  • gRPC is typically built using TCP/IP sockets (AF_INET/AF_INET6)
  • REST APIs are typically implemented using AF_INET/AF_INET6 sockets.
  • Wayland display server protocol uses AF_UNIX sockets.

Several widely used protocols such as HTTP, SSH, and SFTP use sockets with TCP (SOCK_STREAM), while protocols such as DNS and DHCP use sockets with UDP (SOCK_DGRAM).

Since sockets can use IP-based communication, they provide high performance, low latency, and bi-directional communication capabilities.

Note: Sockets do not provide built-in transport security. Security mechanisms such as TLS must be added on top of socket communication when secure data transmission is required.

Signals

A signal is not a data communication mechanism between two processes in the traditional sense. Instead, it provides a way to asynchronously notify a process that an event has occurred. A process must register a signal handler if it wants to receive and handle a signal. Any process can send a signal to another process by specifying its process ID (PID). Signals can be registered using the POSIX API signal() or sigaction(). Modern Linux applications typically prefer sigaction() because it provides more predictable behavior and additional configuration options.

There are two classifications of signals in Linux:

Standard Signals

  • Standard Linux signals range from 1 to 31. Common examples include SIGINT (Signal 2), SIGKILL (Signal 9), and SIGTERM (Signal 15). Standard signals cannot carry a payload.
  • SIGUSR1 and SIGUSR2 can be used by applications for custom signaling purposes.

Real-Time Signals

  • Linux provides a range of real-time signals between SIGRTMIN and SIGRTMAX. A key advantage of real-time signals is that they are queued by the kernel, providing a more reliable signaling mechanism. They can also carry a small payload (for example, an integer value).

A diagrammatic illustration is provided below

Press enter or click to view image in full size
Linux Signals: Event Notification Mechanism

Note: Signals are typically used for event notification, process control, and asynchronous handling rather than transferring large amounts of data between processes.

Comparison Table

Linux IPC Comparison

Press enter or click to view image in full size

Sample Code

For easier understanding, I have provided sample code for each IPC mechanism. For more details and complete source code examples, refer to the GitHub repository below:

GitHub Repository:

https://github.com/mohduvais-prog/linux-process-thread-examples/tree/main/linux_ipc_comm

Summary

Linux IPC is a fundamental concept for every Linux developer, embedded engineer, and systems programmer. Understanding when to use shared memory, memory-mapped files, semaphores, pipes, FIFO, sockets, and signals helps build efficient, scalable, and maintainable software systems.

Several Linux IPC mechanisms are available, each designed for different communication and synchronization requirements. Choosing the appropriate IPC mechanism based on performance, synchronization, communication scope, and system requirements is an important design decision in Linux application development.

Which IPC Mechanism Should I Choose?

Press enter or click to view image in full size

This provides a quick reference for selecting the most suitable IPC mechanism based on the application requirements.

Linux IPC FAQ

What is the fastest IPC mechanism in Linux?

Shared memory is generally considered the fastest IPC mechanism because no data copying is required between processes.

What is the difference between Pipe and FIFO?

A pipe is typically used between related processes, whereas a FIFO can be used between unrelated processes.

What is the difference between AF_INET and AF_UNIX sockets?

AF_INET uses IP networking, while AF_UNIX is optimized for local inter-process communication.

Can signals transfer data?

Standard signals cannot carry data. Real-time signals can carry a small payload.

When should I use Shared Memory instead of a Socket?

Use shared memory when communicating between processes on the same Linux system and maximum throughput is required. Use sockets when communication must occur across different systems or across network boundaries.

What is the most commonly used IPC mechanism in Linux?

Sockets are among the most widely use

--

Mohamed Uvais
Mohamed Uvais

Written by Mohamed Uvais

I am a Solution Architect in the automotive industry with a passion for sharing ideas and knowledge to help build a knowledgeable and thriving community