Inter-Process Communication

This document describes the way Inter-Process Communication (IPC) works.

Pipes

Inter-process Uni-directional Channels (IUC), also called pipes, allow two distinct processes to communicate.

Pipes are made of two uni-directional parts:

  • A sender channel (SC) which can only be written to (write-only)
  • A receiver channel (RC), which can only be read from (read-only)

The two processes sharing a pipes are:

  • The sender process, which uses the SC to send data to the other process
  • The receiver process, which uses the RC to retrieve data sent by the other process

The process that creates the pipe gets both the SC and the RC, and is expected to provide one of them to another process.

Each SC and RC has a unique identifier, which is binded to the process that created it.
The process receiving an SC or RC receives another identifier for it, unique to that process, which prevents IDs collisions.

Opening pipes

A process can open a pipe with another process using the OPEN_PIPE syscall.

The other process will then receive the RECV_PIPE signal. If no handler is set when the signal is sent, the opening syscall fails.

Pipes' pending data

When a pipe is written to, the data is written to a memory zone. This zone is called the pipe's buffer and it's content is called the pending data.
When a pipe is read from, the pending data is progressively retrieved, erased as the read progresses.

The default size of the pipe's buffer is 64 KB, but this can be extended to up to 16 MB during its creation. When it is reached, no data can be written to the pipe anymore, meaning the other process must read data from it in order to free space to write it.

Pipes locking

When a pipe is being written to or read from, it is locked, which means no other writing or reading can happen during this time. This prevents data races which are a common source of bugs which are complex to debug, while not compromising performances.

Closing pipes

Any of the two processes (be it the receiver or the sender) can close a pipe using the CLOSE_PIPE syscall, providing its SC or RC identifier. The pipe is immediatly closed on both sides, and the other process receives the PIPE_CLOSED signal.

Message pipes

Pipes are designed to transmit streams of data, but sometimes we need to use them to transmit messages. This is why there are specific syscalls and signals to deal with this problem.

A message pipe is a pipe that only sends and receives dynamic-sized messages instead of a stream of bytes.
They have a maximum length of 64 KB, which is the pipes' buffer's minimal capacity. Messages must always be sent at once and cannot be sent partially.
Their length is determined when the message is sent which, coupled to pipes locking, allows to retrieve complete messages directly.

It's not possible to send "non-message" data through a message pipe, as the action of writing to a pipe will automatically check if it's a message pipe and ensure the size and "send at once" requirement are met.

Interactive usage

You can find more informations on interactive usage in the shell specifications.

Service sockets

The downside of message pipes is that they are not designed to handle responses from the receiver process, and they also don't have built-in errors handling.

To solve this, it's possible to use service sockets, which as their name indicate are mainly used to communicate with services. A socket is caracterized by the process opening it, called the service, and the process receiving it, the client.

Opening

Sockets are opened with the OPEN_SERV_SOCK syscall. The other process receives the RECV_SERV_SOCK signal on its side.

Exchanges and messages

Service sockets are based on exchanges, which are sets of messages. An exchange is described by a structure made of the following:

  • Identifier (8 bytes)
  • Method or notification code (2 bytes)
  • Status (1 byte): 0x01 if the exchange must be closed, 0x00 otherwise
  • Error code (on 2 bytes) - only valid if the exchange is closed by this message
  • Message counter for this exchange (2 bytes)
  • Message's length (2 bytes)

This structure is handled by the kernel itself and can be managed using a set of system calls and signals.

Messages can then be sent using the SEND_SOCK_MSG syscall and received through the RECV_SOCK_MSG signal. To be read, the receiver process must use the READ_SOCK_MSG syscall.

They must be sent at once, like in message pipes. The maximum size is 4 KB by default, but can be extended through the opening syscall up to 256 MB.

A message can either initiate an exchange or answer to an existing one: in the first case, an identifier will be generated by the kernel and returned by the sending syscall, and in the latter the message will receive an incremented identifier that allows to track where in the exchange the message is. This also prevents a process to send two messages before the other one answers.

Concluding exchanges

Exchanges can be either be concluded either by sending an error message or by sending a message with a close indicator to indicate this message will be the last one in the socket and no answer will be accepted.

Sending a message in a concluded exchange will result in a syscall error.

Methods and notifications

An exchange can be opened by a client to request something to the service, it is then called a method. In that case, the message's body is called the arguments.

When the server opens itself an exchange with the client, it's called a notification. A notification message's body is called its data.

Closing service sockets

Service sockets can be closed with the CLOSE_SERV_SOCK syscall, which triggers the SERV_SOCK_CLOSED signal on the other process' side.

Although exchanges are based on signals which are asynchronous by design, the answer mechanism and messages tracking which ensures the receiver process answers before sending another message allows for simplier communications and synchronization between the two processes.

Shared Memory

Shared memory allows a process to share a part of its memory with another process. It has multiple advantages over pipes:

  • Data is not copied twice, as the sender process directly shares a part of its own memory
  • There is no pipe management, which results in saving operations and less memory accesses
  • There is no pipe buffer to manage which means all the data can be sent at once

Its main disadvantage being that all data is shared at once, so there is no synchronization or "asynchronous sending" mechanism, which is the purpose of pipes.

It works by asking the kernel to share the memory through the SHARE_AMS syscall, the target process receiving the RECV_SHARED_AMS signal.

There are two types of sharing:

  • Mutual sharing allows both the sharer and the receiver processes to access the shared memory ;
  • Exclusive sharing unmaps the shared memory from the sharer and only allows the receiver process to access it

Exclusive mode has several advantages: the sender process to not have to care about managing this memory and avoid overwriting it by accident, but it also ensures the receiving process that the sender will not perform malicious modifications on the shared buffer while the data is processed on its side.

Mutual memory sharing can be stopped using the UNSHARE_AMS syscall, while exclusive sharing are left to the receiver process.