Skip to content

Latest commit

 

History

History
392 lines (290 loc) · 7.98 KB

File metadata and controls

392 lines (290 loc) · 7.98 KB

PromptOS IPC Protocol v1

Version: 1.0
Status: Draft


1. Overview

PromptOS provides multiple IPC mechanisms:

Mechanism Usage Speed
Pipes Unidirectional data stream Medium
Message Queues Structured messages Medium
Shared Memory Large amounts of data Very fast
Futex Synchronization Very fast
Event Handles Signaling Very fast

2. Pipes

2.1 Creation

handle_t handles[2];
int result = sys_pipe(handles, 0);
// handles[0] = Read end
// handles[1] = Write end

2.2 Flags

#define PIPE_NONBLOCK   0x0001  // Non-blocking I/O
#define PIPE_CLOEXEC    0x0002  // Close on exec

2.3 Usage

// Write
ssize_t written = sys_write(handles[1], data, len);

// Read
char buffer[1024];
ssize_t read = sys_read(handles[0], buffer, sizeof(buffer));

2.4 Behavior

  • Writing blocks when buffer is full (except PIPE_NONBLOCK)
  • Reading blocks when buffer is empty (except PIPE_NONBLOCK)
  • Writing to a closed pipe → EPIPE
  • Buffer size: 64 KB (configurable)

3. Message Queues

3.1 Design

Message Queues enable structured messages with:

  • Priorities (0-31)
  • Maximum message size
  • Maximum queue depth

3.2 API

// Create/Open
handle_t mq = sys_msgqueue_create("/myqueue", MQ_CREATE | MQ_RDWR);

// Send (Priority 0-31)
struct message {
    uint32_t type;
    uint32_t length;
    char data[256];
};

struct message msg = { .type = 1, .length = 5, .data = "Hello" };
sys_msgqueue_send(mq, &msg, sizeof(msg), 10);  // Priority 10

// Receive (highest priority first)
int priority;
ssize_t len = sys_msgqueue_recv(mq, &msg, sizeof(msg), &priority);

// Close
sys_msgqueue_close(mq);

3.3 Flags

#define MQ_CREATE       0x0001  // Create if not exists
#define MQ_EXCL         0x0002  // Error if exists
#define MQ_RDONLY       0x0004  // Read only
#define MQ_WRONLY       0x0008  // Write only
#define MQ_RDWR         0x000C  // Read and Write
#define MQ_NONBLOCK     0x0010  // Non-blocking

3.4 Limits

Limit Default Maximum
Max Message Size 8 KB 64 KB
Max Queue Depth 64 1024
Max Priority 31 31

4. Shared Memory

4.1 Design

Shared Memory enables direct memory access between processes. Zero-Copy for maximum performance.

4.2 API

// Create
handle_t shm = sys_shmget(SHM_PRIVATE, 4096, SHM_CREAT | SHM_RDWR);

// Or with name (persistent)
handle_t shm = sys_shmget(SHM_NAME("/myshm"), 4096, SHM_CREAT | SHM_RDWR);

// Map
void *ptr = sys_shmat(shm, NULL, 0);

// Use
memcpy(ptr, data, len);

// Unmap
sys_shmdt(ptr);

// Close handle
sys_close(shm);

4.3 Flags

#define SHM_CREAT       0x0001  // Create
#define SHM_EXCL        0x0002  // Create exclusively
#define SHM_RDONLY      0x0004  // Read only
#define SHM_RDWR        0x0008  // Read and write

// For sys_shmat:
#define SHM_RND         0x0100  // Round down address

4.4 Synchronization

Shared Memory requires explicit synchronization:

// With Futex
int *lock = (int*)ptr;

// Lock
while (__sync_lock_test_and_set(lock, 1)) {
    sys_futex(lock, FUTEX_WAIT, 1, NULL);
}

// Critical section...

// Unlock
*lock = 0;
sys_futex(lock, FUTEX_WAKE, 1, NULL);

5. Futex (Fast Userspace Mutex)

5.1 Design

Futex enables efficient synchronization:

  • Fast Path: Purely in userspace (no syscalls)
  • Slow Path: Kernel support for blocking/waking

5.2 Operations

#define FUTEX_WAIT      0   // Wait if *uaddr == val
#define FUTEX_WAKE      1   // Wake up
#define FUTEX_REQUEUE   2   // Wake up and requeue

5.3 API

// Wait
// Blocks if *uaddr == val (checked atomically)
int result = sys_futex(&lock, FUTEX_WAIT, expected_value, &timeout);

// Wake up
// Wakes up to 'count' waiting threads
int woken = sys_futex(&lock, FUTEX_WAKE, count, NULL);

5.4 Mutex Implementation

typedef struct {
    int state;  // 0 = unlocked, 1 = locked (no waiters), 2 = locked (waiters)
} Mutex;

void mutex_lock(Mutex *m) {
    int c;
    if ((c = __sync_val_compare_and_swap(&m->state, 0, 1)) != 0) {
        // Contention
        if (c != 2) {
            c = __sync_lock_test_and_set(&m->state, 2);
        }
        while (c != 0) {
            sys_futex(&m->state, FUTEX_WAIT, 2, NULL);
            c = __sync_lock_test_and_set(&m->state, 2);
        }
    }
}

void mutex_unlock(Mutex *m) {
    if (__sync_fetch_and_sub(&m->state, 1) != 1) {
        m->state = 0;
        sys_futex(&m->state, FUTEX_WAKE, 1, NULL);
    }
}

6. Event Handles

6.1 Design

Event Handles replace traditional Unix signals:

  • Explicit polling or blocking
  • No race conditions
  • Combination with poll()

6.2 API

// Create
handle_t evt = sys_eventfd(0, EFD_SEMAPHORE);

// Signal (increments counter)
uint64_t value = 1;
sys_write(evt, &value, sizeof(value));

// Wait (decrements counter)
uint64_t count;
sys_read(evt, &count, sizeof(count));  // Blocks if counter == 0

6.3 Flags

#define EFD_SEMAPHORE   0x0001  // Semaphore mode (decrement by 1)
#define EFD_NONBLOCK    0x0002  // Non-blocking
#define EFD_CLOEXEC     0x0004  // Close on exec

6.4 Use with poll()

struct pollfd fds[2] = {
    { .fd = socket, .events = POLLIN },
    { .fd = event,  .events = POLLIN }
};

int ready = sys_poll(fds, 2, -1);

if (fds[0].revents & POLLIN) {
    // Socket has data
}
if (fds[1].revents & POLLIN) {
    // Event signaled
}

7. Process Events (Signal Replacement)

7.1 Event Types

typedef enum {
    EVENT_NONE          = 0,
    EVENT_TERMINATE     = 1,    // Terminate process (like SIGTERM)
    EVENT_INTERRUPT     = 2,    // Interrupt (like SIGINT)
    EVENT_CHILD_EXIT    = 3,    // Child exited (like SIGCHLD)
    EVENT_ALARM         = 4,    // Timer expired (like SIGALRM)
    EVENT_USER1         = 5,    // User-defined
    EVENT_USER2         = 6,    // User-defined
    EVENT_WINDOW        = 7,    // Window size changed
    EVENT_IO            = 8,    // I/O possible
} EventType;

7.2 Event Handling

// Send event
sys_kill(pid, EVENT_TERMINATE);

// Receive event (via Event Handle)
handle_t proc_events = sys_eventfd(0, 0);
sys_ioctl(proc_events, IOCTL_BIND_PROCESS_EVENTS, EVENT_TERMINATE | EVENT_CHILD_EXIT);

// Wait for events
struct pollfd pfd = { .fd = proc_events, .events = POLLIN };
sys_poll(&pfd, 1, -1);

// Read event
ProcessEvent event;
sys_read(proc_events, &event, sizeof(event));

switch (event.type) {
    case EVENT_TERMINATE:
        // Cleanup and exit
        break;
    case EVENT_CHILD_EXIT:
        // Handle child process
        sys_waitpid(event.pid, &status, WNOHANG);
        break;
}

8. Communication Patterns

8.1 Client-Server (Message Queue)

Client                          Server
  |                               |
  |-- msgqueue_send(request) --->|
  |                               |-- process request
  |<-- msgqueue_send(response) --|
  |                               |

8.2 Producer-Consumer (Pipe)

Producer                        Consumer
  |                               |
  |-- write(pipe, data) -------->|
  |-- write(pipe, data) -------->|
  |                               |-- read(pipe) + process
  |                               |

8.3 Shared Buffer (SHM + Futex)

Writer                          Reader
  |                               |
  |-- lock(futex)                 |
  |-- write to shm                |
  |-- unlock(futex) ------------>|-- lock(futex)
  |                               |-- read from shm
  |                               |-- unlock(futex)

9. Best Practices

  1. Small messages: Message Queues
  2. Large amounts of data: Shared Memory + Futex
  3. Streaming: Pipes
  4. Signaling: Event Handles
  5. Low-Latency: Shared Memory with Spinlocks

Avoid

  • Busy-waiting without yield()
  • Large messages over Message Queues
  • Polling without timeout
  • Shared Memory without synchronization

IPC Protocol v1 - PromptOS