Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Loading...
Searching...
No Matches
Memory Module - User Guide

What is the Memory Module?

  • The Memory module enforces static-only allocation for the entire framework.
  • No malloc(), no free() — all memory is allocated at compile-time or
  • initialization-time. This guarantees deterministic behavior and eliminates
  • memory fragmentation.
  • Key idea: If you can't statically allocate it, you don't need it.

Why No Dynamic Allocation?

Safety-Critical Systems Need Guarantees

  • Dynamic allocation has unpredictable latency and may fail at runtime.
  • Static allocation has O(1) time, fails at compile-time, no fragmentation.

Real-World Impact

  • Railway signaling system example:
  • * // BAD: Uses malloc
    * train_command_t *cmd = malloc(sizeof(train_command_t));
    * if (cmd == NULL) {
    * // 3ms critical deadline... out of memory?
    * // Safety-critical failure mid-operation!
    * SAPI_SAFESTATE(...);
    * }
    *
    * // GOOD: Static allocation
    * static train_command_t cmd; // Always available
    * // No runtime allocation needed
    *

Quick Start

1. Include Header

  • * #include "safeapi/memory/sapi_memory.h"
    *

2. Allocate at Compile-Time or Initialization

  • * // Option A: Static buffer (entire program lifetime)
    * static uint8_t buffer[256];
    *
    * // Option B: Application state (initialized once)
    * typedef struct {
    * sapi_channel_t vital_channel;
    * sapi_timer_t heartbeat_timer;
    * uint8_t work_buffer[1024];
    * } app_t;
    *
    * int main(void) {
    * app_t app = {0}; // Zero-initialize
    * // Now app.work_buffer is available for lifetime
    * }
    *

3. Calculate Required Sizes Upfront

  • * // At compile-time, calculate requirements
    * #define MAX_CHANNELS 16
    * #define MAX_TIMERS 32
    * #define MAX_TASKS 64
    * #define BUFFER_SIZE (MAX_CHANNELS * 256)
    *
    * typedef struct {
    * sapi_channel_t channels[MAX_CHANNELS]; // 16 channels
    * sapi_timer_t timers[MAX_TIMERS]; // 32 timers
    * task_t task_queue[MAX_TASKS]; // 64 task slots
    * uint8_t buffer[BUFFER_SIZE]; // Shared buffer
    * } system_t;
    *

Allocation Patterns

Pattern 1: Static Global Buffers

  • For singleton objects (one per program):
  • * // In module.c
    * static uint8_t log_buffer[4096]; // Persistent for program lifetime
    * static uint32_t log_write_pos = 0;
    *
    * void log_write(const char *msg, size_t len) {
    * if (log_write_pos + len > sizeof(log_buffer)) {
    * // Buffer full - wrap or flush
    * }
    * memcpy(&log_buffer[log_write_pos], msg, len);
    * log_write_pos += len;
    * }
    *

Pattern 2: Application State Structure

  • For complex applications with multiple subsystems:
  • * typedef struct {
    * // Channels
    * sapi_channel_t vital_channel;
    * sapi_ipc_handle_t diag_channel;
    *
    * // Services
    * sapi_timer_t timers[MAX_TIMERS];
    * task_t tasks[MAX_TASKS];
    *
    * // Buffers
    * uint8_t input_buffer[256];
    * uint8_t output_buffer[256];
    *
    * // State
    * train_state_t state;
    * statistics_t stats;
    * } application_t;
    *
    * // Single instance per program
    * static application_t g_app = {0}; // All zeroed on startup
    *
    * int main(void) {
    * init_application(&g_app);
    * while (1) {
    * execute_application(&g_app);
    * }
    * }
    *

Pattern 3: Thread-Local State (Multi-Threaded)

  • * // Thread-local buffer (each thread gets own copy)
    * __thread uint8_t thread_buffer[256]; // Per-thread
    *
    * void thread_worker(void *arg) {
    * // thread_buffer is unique to this thread
    * // No synchronization needed for this buffer
    * process_data(thread_buffer, sizeof(thread_buffer));
    * }
    *

Resource Sizing

Calculate Max Requirements

  • * #define MAX_CHANNELS 16
    * #define MAX_TIMERS 32
    * #define MAX_PENDING_TASKS 64
    * #define MAX_LOG_ENTRIES 1000
    * #define MAX_NVM_RECORDS 100
    *
    * // Total memory required:
    * // MAX_CHANNELS * 64 bytes = 1 KB
    * // MAX_TIMERS * 40 bytes = 1.2 KB
    * // MAX_PENDING_TASKS * 32 bytes = 2 KB
    * // MAX_LOG_ENTRIES * 32 bytes = 32 KB
    * // MAX_NVM_RECORDS * 256 bytes = 25.6 KB
    * // Total: ~62 KB
    *

Verify at Compile-Time

  • * // application.h
    * #define APP_STATIC_MEMORY_SIZE (65536) // 64 KB
    *
    * // application.c
    * _Static_assert(
    * sizeof(application_t) <= APP_STATIC_MEMORY_SIZE,
    * "Application state exceeds maximum memory"
    * );
    *

Practical Examples

Example 1: Train Control System

  • * typedef struct {
    * // Communication
    * sapi_channel_t vital_from_online;
    * sapi_channel_t vital_to_online;
    * sapi_ipc_handle_t diagnostic_channel;
    *
    * // Timing
    * sapi_timer_t main_loop_timer;
    * sapi_timer_t watchdog_timer;
    *
    * // State
    * struct {
    * uint32_t speed_kmh;
    * int32_t acceleration;
    * bool brake_engaged;
    * uint8_t signal_aspect;
    * } state;
    *
    * // Buffers
    * struct {
    * uint8_t input[256];
    * uint8_t output[256];
    * uint8_t temp[512];
    * } buffers;
    *
    * // Statistics
    * struct {
    * uint32_t cycles_executed;
    * uint32_t errors_count;
    * uint64_t total_uptime_ms;
    * } stats;
    * } standby_unit_t;
    *
    * // Single instance, allocated in BSS (zero-initialized)
    * static standby_unit_t g_standby = {0};
    *
    * // Total size: ~1 KB (predictable, on-stack or in data section)
    *

Example 2: Ring Buffer Without malloc

  • * typedef struct {
    * uint8_t buffer[256];
    * uint16_t write_pos;
    * uint16_t read_pos;
    * uint16_t count;
    * } ring_buffer_t;
    *
    * void ring_buffer_init(ring_buffer_t *rb) {
    * rb->write_pos = 0;
    * rb->read_pos = 0;
    * rb->count = 0;
    * }
    *
    * sapi_status_t ring_buffer_write(ring_buffer_t *rb, uint8_t byte) {
    * if (rb->count >= 256) {
    * return SAPI_STATUS_RESOURCE_EXHAUSTED;
    * }
    * rb->buffer[rb->write_pos] = byte;
    * rb->write_pos = (rb->write_pos + 1) % 256;
    * rb->count++;
    * return SAPI_STATUS_OK;
    * }
    *

Common Patterns

Pattern 1: Pool-Based Allocation

  • Pre-allocate a pool, hand out slots:
  • * typedef struct {
    * uint8_t data[32];
    * bool in_use;
    * } message_t;
    *
    * static message_t message_pool[16]; // 16 max messages
    *
    * sapi_status_t allocate_message(message_t **msg_out) {
    * for (int i = 0; i < 16; i++) {
    * if (!message_pool[i].in_use) {
    * message_pool[i].in_use = true;
    * *msg_out = &message_pool[i];
    * return SAPI_STATUS_OK;
    * }
    * }
    * return SAPI_STATUS_RESOURCE_EXHAUSTED;
    * }
    *
    * void free_message(message_t *msg) {
    * msg->in_use = false; // Just mark as available
    * }
    *

Pattern 2: Fixed-Size Queues

  • * #define QUEUE_SIZE 32
    *
    * typedef struct {
    * item_t items[QUEUE_SIZE];
    * uint16_t head;
    * uint16_t tail;
    * uint16_t count;
    * } queue_t;
    *
    * sapi_status_t queue_push(queue_t *q, const item_t *item) {
    * if (q->count >= QUEUE_SIZE) {
    * return SAPI_STATUS_RESOURCE_EXHAUSTED;
    * }
    * q->items[q->tail] = *item;
    * q->tail = (q->tail + 1) % QUEUE_SIZE;
    * q->count++;
    * return SAPI_STATUS_OK;
    * }
    *

Best Practices

  • 1. Allocate Everything at Initialization
  • - No malloc/free in real-time paths
  • - All memory allocation happens at startup
  • - Deterministic memory layout
  • 2. Know Your Maximum Sizes
  • - Max channels: fixed at design time
  • - Max concurrent operations: fixed at design time
  • - Max queue depths: fixed at design time
  • 3. Use Compile-Time Assertions
  • - _Static_assert catches oversized allocations
  • - Fail fast at build-time, not runtime
  • - Prevents deployment of broken systems
  • 4. Plan for Worst Case
  • - Don't assume light load
  • - Allocate for peak burst capacity
  • - Better to over-allocate than under-allocate
  • 5. Document Memory Usage
  • - Comment with actual sizes
  • - List worst-case memory need
  • - Track memory budget through integration

Out-of-Memory

  • When allocation fails (pool exhausted):
  • * sapi_status_t rc = allocate_message(&msg);
    * if (rc == SAPI_STATUS_RESOURCE_EXHAUSTED) {
    * // Cannot proceed - trigger safe-state
    * SAPI_SAFESTATE(SAPI_SAFESTATE_LEVEL_SAFE,
    * SAPI_SAFESTATE_REASON_UNSPECIFIED);
    * return rc;
    * }
    *
  • Never ignore allocation failure:
  • * // WRONG: Ignoring allocation failure
    * message_t *msg = get_from_pool(); // Could be NULL
    * msg->data = 42; // Crash if allocation failed!
    *

See Also