, , , ,

Master Inter-Process Communication 2025

What is IPC (Inter-Process Communication)?

IPC (Inter-Process Communication) refers to mechanisms that allow multiple processes to communicate and synchronize with each other. Since processes in most operating systems run in isolated memory spaces, they require specific techniques to exchange data and coordinate actions. IPC methods include:

  • Message Passing
  • Shared Memory
  • Pipes
  • Signals
  • Sockets
  • Semaphores and Mutexes

QNX Message-Passing Mechanism

QNX uses a synchronous message-passing IPC mechanism, which is reliable, secure, and real-time friendly. It is the core method for communication between processes in QNX Neutrino RTOS.

The key functions are:

MsgSend()

  • Used by a client process to send a message to a server process.
  • It blocks the client until the server replies.
  • Syntax: int MsgSend(int coid, const void *smsg, int sbytes, void *rmsg, int rbytes);
    • coid: Connection ID to the server
    • smsg: Pointer to the send buffer
    • sbytes: Size of the send buffer
    • rmsg: Pointer to the receive buffer (for the reply)
    • rbytes: Size of the receive buffer

MsgReceive()

  • Used by the server to receive a message from any client.
  • It blocks until a message arrives.
  • Syntax: int MsgReceive(int chid, void *msg, int bytes, struct _msg_info *info);
    • chid: Channel ID created by ChannelCreate()
    • msg: Pointer to buffer where message will be received
    • bytes: Size of the buffer
    • info: (Optional) Message info like sender’s PID

MsgReply()

  • Used by the server to reply to the client after processing the request.
  • Unblocks the client’s MsgSend().
  • Syntax: int MsgReply(int rcvid, int status, const void *msg, int bytes);
    • rcvid: Receive ID obtained from MsgReceive()
    • status: Status code (usually 0 for success)
    • msg: Reply buffer
    • bytes: Size of reply

Message Passing Flow in QNX:

Client Process            Kernel             Server Process
  |                        |                     |
  |---- MsgSend() -------->|                     |
  |                        |---- MsgReceive() -->|
  |                        |<--- MsgReply() -----|
  |<-----------------------|                     |

Summary

  • MsgSend() → sends a message and waits for a reply.
  • MsgReceive() → blocks until a message is received.
  • MsgReply() → sends the reply and unblocks the client.

This model ensures synchronization, security, and determinism, ideal for real-time embedded systems.

What is a channel and connection in QNX?

In QNX’s message-passing IPC model, channels and connections are fundamental components that facilitate communication between processes.

Channel (Server-Side)

  • A channel is created by a server process using ChannelCreate().
  • It acts as a message queue where incoming messages from clients are placed.
  • The server listens for messages on the channel using MsgReceive().

Key Points:

  • Each channel has a channel ID (chid).
  • One process can create multiple channels.
  • Think of it as a doorway where clients knock (send messages) to get service.

Example:

int chid = ChannelCreate(0);  // Create a channel

Connection (Client-Side)

  • A connection is created by a client using ConnectAttach().
  • It connects the client process to the server’s channel.
  • The function returns a connection ID (coid) used in MsgSend().

Key Points:

  • A client must know the server’s PID and channel ID to connect.
  • Connections are lightweight and kernel-managed.
  • Think of it as a phone line that connects the client to the server’s message queue.

Example:

int coid = ConnectAttach(0, server_pid, server_chid, _NTO_SIDE_CHANNEL, 0);

Channel–Connection Analogy:

ConceptAnalogy
ChannelCustomer Service Counter (server)
ConnectionPhone line or customer calling (client)
MsgSendMaking the call and stating the request
MsgReceiveServer picking up the call
MsgReplyServer giving a response

Summary:

TermCreated ByUsed InDescription
ChannelServerChannelCreate, MsgReceiveEntry point for incoming messages
ConnectionClientConnectAttach, MsgSendLink between client and server’s channel
Here’s a simple example demonstrating how to use ChannelCreate() on the server side and ConnectAttach() on the client side in QNX using message passing (MsgSend, MsgReceive, MsgReply).

Server Code (server.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/neutrino.h>

int main() {
    int chid = ChannelCreate(0);  // Create a channel
    if (chid == -1) {
        perror("ChannelCreate");
        exit(EXIT_FAILURE);
    }

    printf("Server PID: %d, Channel ID: %d\n", getpid(), chid);

    char msg[100];
    int rcvid;

    while (1) {
        rcvid = MsgReceive(chid, msg, sizeof(msg), NULL);
        if (rcvid == -1) {
            perror("MsgReceive");
            continue;
        }

        printf("Server received: %s\n", msg);
        MsgReply(rcvid, 0, "ACK from server", 16);
    }

    return 0;
}

Client Code (client.c)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/neutrino.h>

int main() {
    int server_pid;
    printf("Enter Server PID: ");
    scanf("%d", &server_pid);

    int coid = ConnectAttach(0, server_pid, 1, _NTO_SIDE_CHANNEL, 0);  // Connect to server's channel 1
    if (coid == -1) {
        perror("ConnectAttach");
        exit(EXIT_FAILURE);
    }

    const char *message = "Hello from client";
    char reply[100];

    if (MsgSend(coid, message, strlen(message) + 1, reply, sizeof(reply)) == -1) {
        perror("MsgSend");
    } else {
        printf("Client received reply: %s\n", reply);
    }

    ConnectDetach(coid);
    return 0;
}

How to Run:

  1. Compile: gcc server.c -o server gcc client.c -o client
  2. Open two terminals.
  3. Run the server in one terminal: ./server
  4. Note the PID and use it in the client when prompted: ./client

What is a pulse in QNX and how is it used?

A pulse in QNX is a lightweight, asynchronous notification that can be sent between threads or processes through a channel, like a minimal message without a payload.

Key Characteristics of Pulses:

  • Lightweight: Smaller and faster than full messages.
  • Asynchronous: Sent without waiting for a reply.
  • No data payload: Only delivers a small integer code and value.
  • Delivered via MsgReceive() just like regular messages.
  • Used for:
    • Timer expirations
    • Signal notifications
    • Interrupt service routines
    • User-defined events

Pulse Structure:

When a pulse is received, it appears in the MsgReceive() as a struct _pulse, which looks like:

struct _pulse {
    uint16_t type;      // Always _PULSE_TYPE
    uint16_t subtype;   // Custom or system-defined subtype
    int8_t   code;      // Short code (user-defined or system)
    int8_t   priority;
    int16_t  scoid;
    pid_t    pid;
    int32_t  value;     // Custom user-defined value
};

How to Use a Pulse

Step 1: Server creates a channel

int chid = ChannelCreate(0);

Step 2: Client connects to the server

int coid = ConnectAttach(0, pid, chid, _NTO_SIDE_CHANNEL, 0);

Step 3: Send a pulse using MsgSendPulse()

MsgSendPulse(coid, getprio(0), PULSE_CODE, PULSE_VALUE);
  • coid: Connection ID
  • getprio(0): Current thread priority
  • PULSE_CODE: A small user-defined code (e.g., 1)
  • PULSE_VALUE: A small user-defined value (e.g., 100)

Step 4: Server handles the pulse

struct _pulse pulse;
int rcvid = MsgReceive(chid, &pulse, sizeof(pulse), NULL);

if (rcvid == 0) {
    // It's a pulse
    if (pulse.code == PULSE_CODE) {
        printf("Received pulse with value: %d\n", pulse.value);
    }
}

Pulses always return rcvid == 0 in MsgReceive().

Use Case Examples:

  • Notify a server thread from a timer (e.g., TimerCreate() + SIGEV_PULSE)
  • Notify a process that an event has occurred (e.g., file ready, button pressed)
  • Efficient inter-thread notifications without full messages

Pulse vs Message:

FeatureMessagePulse
SizeLargerSmall (struct _pulse)
Reply NeededYes (MsgReply())No
BlockingYes (MsgSend() blocks)No (MsgSendPulse() is non-blocking)
Use CaseFull request/responseSimple event notification

How is Shared Memory Implemented in QNX?

In QNX Neutrino RTOS, shared memory allows multiple processes to access the same region of memory, enabling fast data exchange without copying. It is suitable for high-throughput communication, unlike message passing, which is better for synchronization and control.

Key Functions for Shared Memory in QNX

QNX follows POSIX-compliant shared memory APIs. The steps are:

Step-by-Step Implementation

1. Create/Open a Shared Memory Object

Use shm_open() to create or open a shared memory region.

int shm_fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
  • /my_shm: Name of the shared memory object (must begin with /)
  • O_CREAT: Create if it doesn’t exist
  • O_RDWR: Open for read/write
  • 0666: File permission

2. Set the Size of Shared Memory

Use ftruncate() to set the size of the memory.

ftruncate(shm_fd, sizeof(struct shared_data));

3. Map the Shared Memory into Address Space

Use mmap() to map the object into the process’s memory space.

struct shared_data* ptr = mmap(0, sizeof(struct shared_data),
                                PROT_READ | PROT_WRITE,
                                MAP_SHARED, shm_fd, 0);
  • PROT_READ | PROT_WRITE: Permissions
  • MAP_SHARED: Changes are visible to other processes

4. Access the Memory

Read/write directly using the pointer:

ptr->counter = 10;

5. Unmap and Unlink (When Done)

To clean up:

munmap(ptr, sizeof(struct shared_data));
close(shm_fd);
shm_unlink("/my_shm");  // Only once, when you're done permanently

Synchronization Tip

Shared memory is fast but not synchronized. You need to use:

  • Mutexes or semaphores (e.g., pthread_mutex_t)
  • Named semaphores using sem_open() for inter-process locking

Example Shared Data Structure

struct shared_data {
    int counter;
    pthread_mutex_t lock;
};

To use pthread_mutex_t across processes, initialize it with:

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&ptr->lock, &attr);

Summary

StepAction
1shm_open() – create or open shared memory
2ftruncate() – set size
3mmap() – map to virtual address space
4Access memory as needed
5Use mutex/semaphore for safe access
6munmap() and shm_unlink() to clean up

Leave a Reply

Your email address will not be published. Required fields are marked *