Skip to main content

Message Passing

Aurora OS implements blocking message queues for process-to-process communication.

SYS_MSGSND

Send a message to another process. Syscall Number: 19

Parameters

target_pid
int
required
PID of the receiving process
data
const void*
required
Pointer to message payload
len
size_t
required
Length of message in bytes (max 256)

Returns

return
int
  • On success: 0
  • On error: -1 (invalid PID, process not found, or message too large)

Behavior

If the target process’s message queue is full (32 messages), the sender blocks until space becomes available.
  1. Validates target PID and message length (≤ 256 bytes)
  2. Creates an ipc_message structure:
    • Sets sender_pid to current process
    • Sets receiver_pid to target
    • Copies message data
  3. Attempts to enqueue the message
  4. If queue full, blocks sender and reschedules
  5. Wakes receiver if it was blocked in msgrcv

Message Structure

struct ipc_message {
    int sender_pid;
    int receiver_pid;
    uint64_t cap_token;      // Reserved for capability-based access control
    size_t len;
    uint8_t data[MSG_MAX_SIZE];  // 256 bytes
};

Example

const char *msg = "Hello from sender";
int ret = msgsnd_syscall(target_pid, msg, strlen(msg) + 1);
if (ret == 0) {
    // Message sent successfully
}

SYS_MSGRCV

Receive a message from the process’s message queue. Syscall Number: 20

Parameters

buf
void*
required
Buffer to store received message data
max_len
size_t
required
Maximum number of bytes to copy (buffer size)

Returns

return
int
  • On success: number of bytes copied to buffer
  • On error: -1

Behavior

If the message queue is empty, the caller blocks until a message arrives.
  1. Checks the process’s message queue
  2. If empty, blocks the process and reschedules
  3. Dequeues the oldest message (FIFO order)
  4. Copies min(msg.len, max_len) bytes to user buffer
  5. Returns the number of bytes copied

Example

char buffer[256];
int len = msgrcv_syscall(buffer, sizeof(buffer));
if (len > 0) {
    printf("Received %d bytes: %s\n", len, buffer);
}

Queue Limits

Each process has a fixed-size queue of 32 messages. If a sender tries to send when the queue is full, it will block.
#define MSG_MAX_SIZE 256      // Max message payload
#define MSG_QUEUE_SIZE 32     // Messages per process

Shared Memory

Shared memory allows multiple processes to access the same physical memory region.

SYS_SHMGET

Create a shared memory segment. Syscall Number: 21

Parameters

size
size_t
required
Size of the shared memory segment in bytes (rounded up to pages)

Returns

return
int
  • On success: shared memory ID (positive integer)
  • On error: -1 (no free slots or allocation failure)

Behavior

  1. Rounds size up to page boundaries (4096-byte multiples)
  2. Allocates physical pages (simplified: may not be contiguous)
  3. Creates a shm_region structure:
    • Assigns a unique ID
    • Records physical base address and size
    • Initializes refcount to 0
  4. Returns the shared memory ID

Limits

Aurora OS supports up to 16 concurrent shared memory segments system-wide.
#define SHM_MAX 16

Example

int shm_id = shmget_syscall(8192);  // Create 8 KB segment
if (shm_id >= 0) {
    // Use shm_id with shmat
}

SYS_SHMAT

Attach a shared memory segment to the process’s address space. Syscall Number: 22

Parameters

shm_id
int
required
Shared memory ID returned by shmget
hint_addr
uint64_t
Suggested virtual address. If 0, kernel chooses the address automatically.

Returns

return
uint64_t
  • On success: virtual address where segment is mapped
  • On error: -1 (invalid ID)

Behavior

  1. Finds the shared memory region by ID
  2. Chooses virtual address:
    • If hint_addr == 0: Uses 0x60000000 + shm_id * 0x100000
    • Otherwise: Uses hint_addr
  3. Maps all pages of the segment with PAGE_WRITABLE | PAGE_USER
  4. Increments the segment’s reference count
  5. Returns the virtual address

Example

int shm_id = shmget_syscall(4096);
void *ptr = (void *)shmat_syscall(shm_id, 0);
if (ptr != (void *)-1) {
    strcpy(ptr, "Shared data");
    shmdt_syscall((uint64_t)ptr, shm_id);
}

SYS_SHMDT

Detach a shared memory segment from the process’s address space. Syscall Number: 23

Parameters

addr
uint64_t
required
Virtual address where the segment is mapped
shm_id
int
required
Shared memory ID

Returns

return
int
  • On success: 0
  • On error: -1 (invalid ID)

Behavior

  1. Finds the shared memory region by ID
  2. Unmaps all pages from the virtual address
  3. Decrements the reference count
  4. If refcount reaches 0: Frees all physical pages and marks the segment as unused
When the last process detaches from a shared memory segment, the kernel automatically frees the physical memory.

Example

void *ptr = (void *)shmat_syscall(shm_id, 0);
// Use shared memory...
shmdt_syscall((uint64_t)ptr, shm_id);

Pipes

Pipes provide unidirectional byte streams between processes, typically used for parent-child communication.

SYS_PIPE

Create a pipe and return two file descriptors. Syscall Number: 15

Parameters

pipefd
int[2]
required
Array to store the two file descriptors:
  • pipefd[0]: read end
  • pipefd[1]: write end

Returns

return
int
  • On success: 0 (pipefd array is filled)
  • On error: -1 (no free pipe slots or fd slots)

Behavior

  1. Allocates a struct pipe with a 4096-byte circular buffer
  2. Finds two free file descriptors in the current process
  3. Sets up the descriptors:
    • Read end: flags = 0, fs_priv = pipe pointer
    • Write end: flags = 1, fs_priv = pipe pointer
  4. Returns 0 with pipefd[0] and pipefd[1] set

Pipe Structure

#define PIPE_BUF_SIZE 4096

struct pipe {
    uint8_t buf[PIPE_BUF_SIZE];
    int read_pos, write_pos, count;
    int in_use;
    int waiting_pid;    // PID blocked on read
    int writer_pid;     // PID blocked on write, -1 if none
};

Example

int pipefd[2];
if (pipe_syscall(pipefd) == 0) {
    int pid = fork_syscall();
    if (pid == 0) {
        // Child: close write end, read from pipe
        close_syscall(pipefd[1]);
        char buf[256];
        int n = read_syscall(pipefd[0], buf, sizeof(buf));
        // Process data...
        exit_syscall(0);
    } else {
        // Parent: close read end, write to pipe
        close_syscall(pipefd[0]);
        write_syscall(pipefd[1], "Hello", 5);
        close_syscall(pipefd[1]);
        waitpid_syscall(pid, NULL);
    }
}

SYS_DUP2

Duplicate a file descriptor to a specific fd number. Syscall Number: 16

Parameters

oldfd
int
required
Existing file descriptor to duplicate
newfd
int
required
Target file descriptor number

Returns

return
int
  • On success: newfd
  • On error: -1 (invalid fd)

Behavior

  1. Validates that oldfd is in use
  2. If newfd is already in use and differs from oldfd, closes it
  3. Copies the entire fd_entry from oldfd to newfd
  4. Returns newfd
This is commonly used for I/O redirection, such as redirecting stdout to a pipe or file.

Example - Redirect stdout to Pipe

int pipefd[2];
pipe_syscall(pipefd);

int pid = fork_syscall();
if (pid == 0) {
    // Child: redirect stdout to pipe write end
    close_syscall(pipefd[0]);  // Close read end
    dup2_syscall(pipefd[1], 1);  // stdout = pipe write
    close_syscall(pipefd[1]);  // Close original fd
    
    // Now all writes to fd 1 go to the pipe
    write_syscall(1, "Piped output\n", 14);
    exit_syscall(0);
} else {
    // Parent: read from pipe
    close_syscall(pipefd[1]);
    char buf[256];
    read_syscall(pipefd[0], buf, sizeof(buf));
    close_syscall(pipefd[0]);
}

Comparison of IPC Mechanisms

MechanismUse CaseCapacityBlockingPersistence
MessagesCommand/control messages32 msgs × 256 bytesYesQueue per process
Shared MemoryLarge data transferUp to page multiplesNoRefcounted
PipesStreaming data4096 bytesYesLifetime of pipe

Recommendations

  • Use messages for simple RPC-style communication and event notifications
  • Use shared memory for high-bandwidth data sharing (e.g., video frames, large buffers)
  • Use pipes for traditional Unix-style process pipelines and I/O redirection

Build docs developers (and LLMs) love