Linux IPC in Embedded Systems (Short Practical Guide)
IPC (Inter-Process Communication) lets processes exchange data and signals. In embedded Linux, IPC choice affects latency, memory use, complexity, and reliability.
Quick selection
- Stream logs or simple producer->consumer:
pipe/FIFO - Request/response between local services: Unix domain socket
- Structured async messages with priorities: POSIX message queue
- High-throughput low-latency data path: shared memory + semaphore/mutex
- Simple notifications/events: signal or
eventfd
1) Pipe/FIFO
Best for
- Simple one-way byte stream
- Shell-like pipelines or parent-child communication
Pros
- Very simple API
- Low overhead
Cons
- Byte stream only (no message boundaries)
- Multi-writer design becomes messy
// parent writes, child reads
int fd[2];
pipe(fd);
if (fork() == 0) {
close(fd[1]);
char buf[64] = {0};
read(fd[0], buf, sizeof(buf));
printf("child got: %s\n", buf);
_exit(0);
}
close(fd[0]);
write(fd[1], "hello", 5);
2) Unix Domain Socket (AF_UNIX)
Best for
- Local client/server daemons
- Need bidirectional communication and framing
Pros
- Faster than TCP loopback for local IPC
- Supports stream or datagram modes
- Can pass file descriptors (
SCM_RIGHTS)
Cons
- More setup than pipes
- Protocol framing is your responsibility
// server side (minimal)
int s = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr = {.sun_family = AF_UNIX, .sun_path = "/tmp/ipc.sock"};
unlink(addr.sun_path);
bind(s, (struct sockaddr *)&addr, sizeof(addr));
listen(s, 4);
int c = accept(s, NULL, NULL);
write(c, "ok\n", 3);
3) POSIX Message Queue
Best for
- Discrete messages (not raw streams)
- Priority-based handling
Pros
- Message boundaries preserved
- Priority support
Cons
- Queue size/limits must be tuned
- Slightly more kernel/resource management
mqd_t q = mq_open("/sensor_q", O_CREAT | O_RDWR, 0644, NULL);
mq_send(q, "temp:42", 7, 1);
char msg[64];
unsigned prio;
ssize_t n = mq_receive(q, msg, sizeof(msg), &prio);
4) Shared Memory + Synchronization
Best for
- High data rate (audio/video/sensor buffers)
- Lowest copy overhead between processes
Pros
- Very fast for large data
- Good for ring buffers
Cons
- Needs synchronization (mutex/semaphore/futex)
- Harder to debug than queues/sockets
int fd = shm_open("/telemetry", O_CREAT | O_RDWR, 0644);
ftruncate(fd, 4096);
void *p = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// writer/reader must coordinate with a semaphore or mutex
5) Signals / eventfd
Best for
- Lightweight notifications (“new data ready”, “stop now”)
Pros
- Very low overhead event trigger
Cons
- Signals carry tiny payload and are easy to misuse
- Not for bulk data transfer
int efd = eventfd(0, 0);
uint64_t one = 1;
write(efd, &one, sizeof(one)); // notify
read(efd, &one, sizeof(one)); // consume
Embedded Linux practical notes
- For deterministic behavior, avoid overloading one IPC channel for everything.
- Use shared memory for data plane, and message queue/socket for control plane.
- If hard real-time matters, pin threads/IRQs and measure worst-case latency.
- Keep payloads fixed-size when possible; dynamic allocations increase jitter.
A practical default architecture
- Sensor process -> shared memory ring buffer
- Control process -> reads shared memory
- Supervisor/service manager -> Unix socket for commands and health checks
- Event notification ->
eventfdor message queue
This split stays fast and maintainable on most embedded Linux products.
Final rule of thumb
- Start with Unix sockets for clarity.
- Move hot paths to shared memory only when profiling shows a real bottleneck.
- Keep synchronization simple before making it clever.