Safe API Framework
Layered API framework for safety-related applications (ERTMS RBC reference targeting CENELEC EN 50128 SIL 4)
Toggle main menu visibility
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
-
Memory Module - Architecture
for internal design
-
Buffer Module - User Guide
for buffer management
-
Non-Volatile Memory User Guide
for non-volatile storage
*/
Generated by
1.18.0