Programming the Temporal Dimension: A Complete Guide to Real-Time Systems in C

Real-time systems are the invisible backbone of modern technology—from automotive engine controllers and medical devices to avionics and industrial automation. In these systems, correctness depends not only on logical results but also on the timing of those results. C, with its deterministic execution and low-level control, remains the language of choice for real-time development. This comprehensive guide explores the principles, techniques, and patterns for building robust real-time systems in C.

What Are Real-Time Systems?

A real-time system is one where the correctness of the system depends not only on the logical result of computation but also on the time at which the results are produced.

Types of Real-Time Systems:

TypeDefinitionExampleConsequences of Missed Deadline
Hard Real-TimeMissing a deadline causes system failureAirbag deployment, pacemakerCatastrophic, potential loss of life
Firm Real-TimeOccasional deadline misses are tolerable, but degrade qualityVideo streaming, audio processingDegraded quality, but not catastrophic
Soft Real-TimeTimeliness is desirable but not criticalWeb server, GUI responseReduced user experience

Real-Time System Architecture

┌─────────────────────────────────────────────────────────────┐
│                     Application Layer                        │
├─────────────────────────────────────────────────────────────┤
│                  Real-Time Executive (RTOS)                  │
│  ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐          │
│  │ Task 1  │ │ Task 2  │ │ Task 3  │ │ Task 4  │          │
│  │(Periodic│ │(Aperiodic│ │(Sporadic│ │(Idle)  │          │
│  │ 10ms)   │ │ )       │ │ )       │ │        │          │
│  └─────────┘ └─────────┘ └─────────┘ └─────────┘          │
├─────────────────────────────────────────────────────────────┤
│                    Scheduler & IPC                           │
├─────────────────────────────────────────────────────────────┤
│                  Hardware Abstraction Layer                  │
├─────────────────────────────────────────────────────────────┤
│                     Hardware (CPU, Timers, I/O)              │
└─────────────────────────────────────────────────────────────┘

Fundamental Real-Time Concepts

1. Tasks and Threads

#include <pthread.h>
#include <sched.h>
#include <time.h>
// Real-time task structure
typedef struct {
void (*function)(void* arg);
void* arg;
struct timespec period;
struct timespec next_deadline;
int priority;
int is_periodic;
} RTTask;
// Create a real-time thread with priority
int create_rt_thread(pthread_t* thread, void* (*func)(void*), void* arg, int priority) {
pthread_attr_t attr;
struct sched_param param;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);  // FIFO scheduling
param.sched_priority = priority;
pthread_attr_setschedparam(&attr, &param);
return pthread_create(thread, &attr, func, arg);
}
// Set thread to real-time priority (Linux)
int set_rt_priority(int priority) {
struct sched_param param;
param.sched_priority = priority;
if (sched_setscheduler(0, SCHED_FIFO, &param) == -1) {
perror("sched_setscheduler");
return -1;
}
return 0;
}

2. Timing and Clocks

#include <time.h>
#include <stdio.h>
// High-resolution timer
typedef struct {
struct timespec start;
struct timespec end;
} Timer;
void timer_start(Timer* t) {
clock_gettime(CLOCK_MONOTONIC, &t->start);
}
void timer_stop(Timer* t) {
clock_gettime(CLOCK_MONOTONIC, &t->end);
}
long long timer_elapsed_ns(Timer* t) {
return (t->end.tv_sec - t->start.tv_sec) * 1000000000LL +
(t->end.tv_nsec - t->start.tv_nsec);
}
// Busy wait with precise timing
void precise_delay_ns(long long nanoseconds) {
struct timespec req, rem;
req.tv_sec = nanoseconds / 1000000000;
req.tv_nsec = nanoseconds % 1000000000;
while (nanosleep(&req, &rem) == -1) {
if (errno == EINTR) {
req = rem;
} else {
break;
}
}
}
// Spin-wait (for very short delays - use with caution)
void spin_wait_ns(long long nanoseconds) {
struct timespec start, now;
clock_gettime(CLOCK_MONOTONIC, &start);
do {
clock_gettime(CLOCK_MONOTONIC, &now);
} while ((now.tv_sec - start.tv_sec) * 1000000000LL +
(now.tv_nsec - start.tv_nsec) < nanoseconds);
}

Real-Time Scheduling

1. Rate Monotonic Scheduling (RMS)

Rate Monotonic Scheduling assigns priorities based on period: shorter period = higher priority.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
typedef struct {
int id;
int period_ms;
int execution_time_ms;
int deadline_ms;
int (*task_func)(void*);
void* arg;
int missed_deadlines;
} RTTaskSet;
// Rate monotonic priority assignment
void assign_rms_priorities(RTTaskSet* tasks, int num_tasks) {
// Sort by period (shorter period = higher priority)
for (int i = 0; i < num_tasks - 1; i++) {
for (int j = i + 1; j < num_tasks; j++) {
if (tasks[i].period_ms > tasks[j].period_ms) {
RTTaskSet temp = tasks[i];
tasks[i] = tasks[j];
tasks[j] = temp;
}
}
}
// Assign priorities (1 = highest)
for (int i = 0; i < num_tasks; i++) {
tasks[i].deadline_ms = tasks[i].period_ms;
printf("Task %d: period=%dms, priority=%d\n",
tasks[i].id, tasks[i].period_ms, i + 1);
}
}
// Utilization bound test for RMS
int rms_schedulable(RTTaskSet* tasks, int num_tasks) {
double utilization = 0.0;
double bound = num_tasks * (pow(2.0, 1.0 / num_tasks) - 1.0);
for (int i = 0; i < num_tasks; i++) {
utilization += (double)tasks[i].execution_time_ms / tasks[i].period_ms;
}
printf("Utilization: %.3f (bound: %.3f)\n", utilization, bound);
return utilization <= bound;
}

2. Earliest Deadline First (EDF) Scheduler

#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
struct timespec deadline;
int execution_time_us;
int (*func)(void*);
void* arg;
int active;
} EDFTask;
typedef struct {
EDFTask* tasks;
int capacity;
int count;
int (*comparator)(const EDFTask*, const EDFTask*);
} EDFScheduler;
// Compare deadlines for sorting
int deadline_comparator(const EDFTask* a, const EDFTask* b) {
if (a->deadline.tv_sec == b->deadline.tv_sec) {
return a->deadline.tv_nsec - b->deadline.tv_nsec;
}
return a->deadline.tv_sec - b->deadline.tv_sec;
}
// Initialize EDF scheduler
EDFScheduler* edf_init(int max_tasks) {
EDFScheduler* sched = malloc(sizeof(EDFScheduler));
sched->tasks = calloc(max_tasks, sizeof(EDFTask));
sched->capacity = max_tasks;
sched->count = 0;
sched->comparator = deadline_comparator;
return sched;
}
// Add task to scheduler
void edf_add_task(EDFScheduler* sched, EDFTask task) {
if (sched->count < sched->capacity) {
sched->tasks[sched->count++] = task;
}
}
// Find next task to execute (earliest deadline)
EDFTask* edf_get_next(EDFScheduler* sched) {
if (sched->count == 0) return NULL;
EDFTask* earliest = &sched->tasks[0];
for (int i = 1; i < sched->count; i++) {
if (sched->comparator(&sched->tasks[i], earliest) < 0) {
earliest = &sched->tasks[i];
}
}
return earliest;
}

3. Fixed-Priority Preemptive Scheduler

#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
typedef struct {
pthread_t thread;
int priority;
int period_us;
int execution_us;
void (*func)(void*);
void* arg;
volatile int running;
volatile int deadline_missed;
} RTTask;
typedef struct {
RTTask* tasks;
int num_tasks;
volatile int active;
pthread_t scheduler_thread;
sem_t tick_sem;
} RTScheduler;
// Task execution loop
void* task_loop(void* arg) {
RTTask* task = (RTTask*)arg;
struct timespec start, end;
long long elapsed;
while (task->running) {
// Record start time
clock_gettime(CLOCK_MONOTONIC, &start);
// Execute task
task->func(task->arg);
// Check deadline
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed = (end.tv_sec - start.tv_sec) * 1000000LL +
(end.tv_nsec - start.tv_nsec) / 1000;
if (elapsed > task->execution_us) {
task->deadline_missed = 1;
printf("Task %d missed deadline! (executed %lldus, budget %dus)\n",
task->priority, elapsed, task->execution_us);
}
// Sleep for remaining period
usleep(task->period_us - elapsed);
}
return NULL;
}
// Initialize real-time scheduler
int rt_scheduler_init(RTScheduler* sched, RTTask* tasks, int num_tasks) {
sched->tasks = tasks;
sched->num_tasks = num_tasks;
sched->active = 1;
sem_init(&sched->tick_sem, 0, 0);
// Sort tasks by priority (highest first)
for (int i = 0; i < num_tasks - 1; i++) {
for (int j = i + 1; j < num_tasks; j++) {
if (tasks[i].priority < tasks[j].priority) {
RTTask temp = tasks[i];
tasks[i] = tasks[j];
tasks[j] = temp;
}
}
}
// Create tasks with appropriate priorities
for (int i = 0; i < num_tasks; i++) {
tasks[i].running = 1;
create_rt_thread(&tasks[i].thread, task_loop, &tasks[i], tasks[i].priority);
}
return 0;
}

Inter-Task Communication

1. Message Queues

#include <mqueue.h>
#include <fcntl.h>
#include <sys/stat.h>
// Message structure
typedef struct {
int type;
int priority;
void* data;
size_t size;
} RTMessage;
// Create a real-time message queue
mqd_t create_rt_queue(const char* name, int max_msgs, size_t msg_size) {
struct mq_attr attr;
attr.mq_flags = 0;
attr.mq_maxmsg = max_msgs;
attr.mq_msgsize = msg_size;
attr.mq_curmsgs = 0;
mqd_t mqd = mq_open(name, O_CREAT | O_RDWR, 0666, &attr);
if (mqd == (mqd_t)-1) {
perror("mq_open");
return -1;
}
return mqd;
}
// Send message with timeout
int send_rt_message(mqd_t mqd, const RTMessage* msg, int timeout_ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
return mq_timedsend(mqd, (char*)msg, sizeof(RTMessage), msg->priority, &ts);
}
// Receive message with timeout
int receive_rt_message(mqd_t mqd, RTMessage* msg, int timeout_ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
unsigned int prio;
ssize_t bytes = mq_timedreceive(mqd, (char*)msg, sizeof(RTMessage), &prio, &ts);
if (bytes > 0) {
msg->priority = prio;
return 0;
}
return -1;
}

2. Shared Memory with Mutexes

#include <pthread.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct {
pthread_mutex_t mutex;
pthread_cond_t cond;
volatile int data_ready;
void* shared_data;
size_t data_size;
int writer_active;
} RTSharedMemory;
// Create shared memory region
RTSharedMemory* create_rt_shared_memory(const char* name, size_t size) {
int fd = shm_open(name, O_CREAT | O_RDWR, 0666);
if (fd == -1) {
perror("shm_open");
return NULL;
}
ftruncate(fd, sizeof(RTSharedMemory) + size);
RTSharedMemory* shm = mmap(NULL, sizeof(RTSharedMemory) + size,
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
// Initialize synchronization primitives (only once)
if (shm->writer_active == 0) {
pthread_mutexattr_t mutex_attr;
pthread_mutexattr_init(&mutex_attr);
pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&shm->mutex, &mutex_attr);
pthread_condattr_t cond_attr;
pthread_condattr_init(&cond_attr);
pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
pthread_cond_init(&shm->cond, &cond_attr);
shm->data_ready = 0;
shm->data_size = size;
shm->writer_active = 1;
}
return shm;
}
// Writer function (producer)
int rt_shared_write(RTSharedMemory* shm, const void* data, int timeout_ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
pthread_mutex_lock(&shm->mutex);
// Wait for reader to consume previous data
while (shm->data_ready == 1) {
if (pthread_cond_timedwait(&shm->cond, &shm->mutex, &ts) != 0) {
pthread_mutex_unlock(&shm->mutex);
return -1;  // Timeout
}
}
memcpy(shm->shared_data, data, shm->data_size);
shm->data_ready = 1;
pthread_cond_signal(&shm->cond);
pthread_mutex_unlock(&shm->mutex);
return 0;
}
// Reader function (consumer)
int rt_shared_read(RTSharedMemory* shm, void* data, int timeout_ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
pthread_mutex_lock(&shm->mutex);
while (shm->data_ready == 0) {
if (pthread_cond_timedwait(&shm->cond, &shm->mutex, &ts) != 0) {
pthread_mutex_unlock(&shm->mutex);
return -1;  // Timeout
}
}
memcpy(data, shm->shared_data, shm->data_size);
shm->data_ready = 0;
pthread_cond_signal(&shm->cond);
pthread_mutex_unlock(&shm->mutex);
return 0;
}

3. Lock-Free Ring Buffer

#include <stdatomic.h>
#include <stdint.h>
typedef struct {
atomic_size_t head;
atomic_size_t tail;
void** buffer;
size_t capacity;
size_t elem_size;
} LockFreeRingBuffer;
// Initialize lock-free ring buffer
LockFreeRingBuffer* lf_ring_init(size_t capacity, size_t elem_size) {
LockFreeRingBuffer* rb = malloc(sizeof(LockFreeRingBuffer));
rb->buffer = calloc(capacity, sizeof(void*));
rb->capacity = capacity;
rb->elem_size = elem_size;
atomic_init(&rb->head, 0);
atomic_init(&rb->tail, 0);
return rb;
}
// Producer - returns 1 if successful, 0 if full
int lf_ring_push(LockFreeRingBuffer* rb, const void* elem) {
size_t head = atomic_load(&rb->head);
size_t next_head = (head + 1) % rb->capacity;
if (next_head == atomic_load(&rb->tail)) {
return 0;  // Buffer full
}
// Copy data into buffer
if (rb->buffer[head] == NULL) {
rb->buffer[head] = malloc(rb->elem_size);
}
memcpy(rb->buffer[head], elem, rb->elem_size);
atomic_store(&rb->head, next_head);
return 1;
}
// Consumer - returns 1 if successful, 0 if empty
int lf_ring_pop(LockFreeRingBuffer* rb, void* elem) {
size_t tail = atomic_load(&rb->tail);
if (tail == atomic_load(&rb->head)) {
return 0;  // Buffer empty
}
memcpy(elem, rb->buffer[tail], rb->elem_size);
atomic_store(&rb->tail, (tail + 1) % rb->capacity);
return 1;
}

Memory Management for Real-Time Systems

1. Fixed-Size Memory Pool

#include <stdint.h>
typedef struct {
void* start;
void* free_list;
size_t block_size;
size_t num_blocks;
uint8_t* bitmap;
} MemoryPool;
// Initialize memory pool with fixed-size blocks
MemoryPool* mempool_init(size_t block_size, size_t num_blocks) {
MemoryPool* pool = malloc(sizeof(MemoryPool));
pool->block_size = block_size;
pool->num_blocks = num_blocks;
pool->start = malloc(block_size * num_blocks);
pool->bitmap = calloc(num_blocks, sizeof(uint8_t));
// Initialize free list
pool->free_list = pool->start;
void** current = pool->free_list;
for (size_t i = 0; i < num_blocks - 1; i++) {
*current = (char*)current + block_size;
current = *current;
}
*current = NULL;
return pool;
}
// O(1) allocation
void* mempool_alloc(MemoryPool* pool) {
if (pool->free_list == NULL) {
return NULL;  // Out of memory
}
void* block = pool->free_list;
pool->free_list = *(void**)block;
// Find and mark bitmap (for debugging)
size_t index = ((uintptr_t)block - (uintptr_t)pool->start) / pool->block_size;
pool->bitmap[index] = 1;
return block;
}
// O(1) deallocation
void mempool_free(MemoryPool* pool, void* block) {
size_t index = ((uintptr_t)block - (uintptr_t)pool->start) / pool->block_size;
if (index < pool->num_blocks && pool->bitmap[index] == 1) {
*(void**)block = pool->free_list;
pool->free_list = block;
pool->bitmap[index] = 0;
}
}

2. Stack Allocator (Arena)

typedef struct {
void* start;
void* current;
size_t size;
size_t used;
} StackAllocator;
// Initialize stack allocator
StackAllocator* stack_allocator_init(size_t size) {
StackAllocator* alloc = malloc(sizeof(StackAllocator));
alloc->start = malloc(size);
alloc->current = alloc->start;
alloc->size = size;
alloc->used = 0;
return alloc;
}
// Allocate from stack
void* stack_alloc(StackAllocator* alloc, size_t bytes) {
if (alloc->used + bytes > alloc->size) {
return NULL;  // Out of memory
}
void* ptr = alloc->current;
alloc->current = (char*)alloc->current + bytes;
alloc->used += bytes;
return ptr;
}
// Reset allocator (free all)
void stack_allocator_reset(StackAllocator* alloc) {
alloc->current = alloc->start;
alloc->used = 0;
}

Handling Interrupts

#include <signal.h>
#include <time.h>
#include <stdio.h>
// Timer interrupt handler
volatile sig_atomic_t timer_ticks = 0;
void timer_handler(int signum) {
timer_ticks++;  // Only atomic operations allowed in interrupt context
}
// Set up periodic timer interrupt
void setup_timer_interrupt(int interval_us) {
struct sigaction sa;
struct itimerval timer;
// Install timer handler
sa.sa_handler = timer_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
sigaction(SIGALRM, &sa, NULL);
// Configure timer
timer.it_value.tv_sec = 0;
timer.it_value.tv_usec = interval_us;
timer.it_interval.tv_sec = 0;
timer.it_interval.tv_usec = interval_us;
setitimer(ITIMER_REAL, &timer, NULL);
}
// Critical section with interrupts disabled
void critical_section(void (*critical_func)(void*), void* arg) {
sigset_t mask, old_mask;
// Block all signals
sigfillset(&mask);
sigprocmask(SIG_BLOCK, &mask, &old_mask);
// Execute critical code
critical_func(arg);
// Restore signals
sigprocmask(SIG_SETMASK, &old_mask, NULL);
}

Real-Time Linux (PREEMPT_RT)

#include <sys/mman.h>
#include <stdlib.h>
// Lock memory to prevent paging
void lock_memory() {
if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
perror("mlockall");
exit(1);
}
}
// Set CPU affinity
void set_cpu_affinity(int cpu) {
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) == -1) {
perror("sched_setaffinity");
}
}
// Complete real-time initialization
void rt_init(int priority, int cpu) {
// Lock memory
lock_memory();
// Set CPU affinity
set_cpu_affinity(cpu);
// Set real-time priority
set_rt_priority(priority);
// Disable console output (optional)
// setvbuf(stdout, NULL, _IONBF, 0);
}

Real-Time Example: PID Controller

#include <stdio.h>
#include <math.h>
typedef struct {
double kp;           // Proportional gain
double ki;           // Integral gain
double kd;           // Derivative gain
double setpoint;     // Desired value
double integral;     // Integral accumulator
double prev_error;   // Previous error
double output_min;   // Minimum output
double output_max;   // Maximum output
struct timespec last_time;
} PIDController;
void pid_init(PIDController* pid, double kp, double ki, double kd,
double setpoint, double output_min, double output_max) {
pid->kp = kp;
pid->ki = ki;
pid->kd = kd;
pid->setpoint = setpoint;
pid->integral = 0.0;
pid->prev_error = 0.0;
pid->output_min = output_min;
pid->output_max = output_max;
clock_gettime(CLOCK_MONOTONIC, &pid->last_time);
}
double pid_compute(PIDController* pid, double measurement) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
// Calculate delta time in seconds
double dt = (now.tv_sec - pid->last_time.tv_sec) +
(now.tv_nsec - pid->last_time.tv_nsec) / 1e9;
pid->last_time = now;
// Limit dt to prevent instability
if (dt > 0.1) dt = 0.1;
if (dt <= 0.0) dt = 0.001;
// Calculate error
double error = pid->setpoint - measurement;
// Proportional term
double proportional = pid->kp * error;
// Integral term with anti-windup
pid->integral += error * dt;
double integral = pid->ki * pid->integral;
// Derivative term (using derivative of measurement to avoid derivative kick)
double derivative = -pid->kd * (measurement - pid->prev_error) / dt;
pid->prev_error = measurement;
// Calculate output
double output = proportional + integral + derivative;
// Clamp output
if (output > pid->output_max) {
output = pid->output_max;
} else if (output < pid->output_min) {
output = pid->output_min;
}
return output;
}
// PID control loop (runs at fixed frequency)
void* pid_control_loop(void* arg) {
PIDController* pid = (PIDController*)arg;
double process_value = 0.0;
double control_output;
struct timespec period = {0, 10000000};  // 10ms period
while (1) {
// Read sensor (simulated)
process_value = read_sensor();
// Compute PID output
control_output = pid_compute(pid, process_value);
// Apply output to actuator
set_actuator(control_output);
// Wait for next cycle
nanosleep(&period, NULL);
}
return NULL;
}

Performance Measurement and Analysis

#include <stdio.h>
#include <stdint.h>
// Measure execution time
typedef struct {
uint64_t min_ns;
uint64_t max_ns;
uint64_t total_ns;
uint64_t count;
uint64_t sum_squares;
} ExecutionStats;
void stats_init(ExecutionStats* stats) {
stats->min_ns = UINT64_MAX;
stats->max_ns = 0;
stats->total_ns = 0;
stats->count = 0;
stats->sum_squares = 0;
}
void stats_add(ExecutionStats* stats, uint64_t elapsed_ns) {
if (elapsed_ns < stats->min_ns) stats->min_ns = elapsed_ns;
if (elapsed_ns > stats->max_ns) stats->max_ns = elapsed_ns;
stats->total_ns += elapsed_ns;
stats->count++;
stats->sum_squares += elapsed_ns * elapsed_ns;
}
double stats_mean(ExecutionStats* stats) {
return (double)stats->total_ns / stats->count;
}
double stats_stddev(ExecutionStats* stats) {
double mean = stats_mean(stats);
double variance = (double)stats->sum_squares / stats->count - mean * mean;
return sqrt(variance);
}
// Measure worst-case execution time (WCET)
uint64_t measure_wcet(void (*func)(void*), void* arg, int iterations) {
Timer timer;
uint64_t max_time = 0;
for (int i = 0; i < iterations; i++) {
timer_start(&timer);
func(arg);
timer_stop(&timer);
uint64_t elapsed = timer_elapsed_ns(&timer);
if (elapsed > max_time) {
max_time = elapsed;
}
}
return max_time;
}

Real-Time System Design Patterns

1. Producer-Consumer with Bounded Buffer

typedef struct {
void** buffer;
size_t capacity;
size_t head;
size_t tail;
size_t count;
pthread_mutex_t mutex;
pthread_cond_t not_full;
pthread_cond_t not_empty;
} BoundedBuffer;
int bounded_buffer_init(BoundedBuffer* bb, size_t capacity) {
bb->buffer = calloc(capacity, sizeof(void*));
bb->capacity = capacity;
bb->head = 0;
bb->tail = 0;
bb->count = 0;
pthread_mutex_init(&bb->mutex, NULL);
pthread_cond_init(&bb->not_full, NULL);
pthread_cond_init(&bb->not_empty, NULL);
return 0;
}
int bounded_buffer_put(BoundedBuffer* bb, void* item, int timeout_ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
pthread_mutex_lock(&bb->mutex);
while (bb->count == bb->capacity) {
if (pthread_cond_timedwait(&bb->not_full, &bb->mutex, &ts) != 0) {
pthread_mutex_unlock(&bb->mutex);
return -1;
}
}
bb->buffer[bb->head] = item;
bb->head = (bb->head + 1) % bb->capacity;
bb->count++;
pthread_cond_signal(&bb->not_empty);
pthread_mutex_unlock(&bb->mutex);
return 0;
}
void* bounded_buffer_get(BoundedBuffer* bb, int timeout_ms) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000;
pthread_mutex_lock(&bb->mutex);
while (bb->count == 0) {
if (pthread_cond_timedwait(&bb->not_empty, &bb->mutex, &ts) != 0) {
pthread_mutex_unlock(&bb->mutex);
return NULL;
}
}
void* item = bb->buffer[bb->tail];
bb->tail = (bb->tail + 1) % bb->capacity;
bb->count--;
pthread_cond_signal(&bb->not_full);
pthread_mutex_unlock(&bb->mutex);
return item;
}

2. Watchdog Timer

#include <signal.h>
#include <setjmp.h>
typedef struct {
int timeout_seconds;
volatile int fed;
jmp_buf jump_buffer;
pthread_t watchdog_thread;
} Watchdog;
void watchdog_timeout_handler(int signum) {
// Longjmp to recovery point
// (Use with caution - only for catastrophic failures)
// longjmp(watchdog.jump_buffer, 1);
}
void* watchdog_thread(void* arg) {
Watchdog* wd = (Watchdog*)arg;
while (1) {
sleep(wd->timeout_seconds);
if (!wd->fed) {
// Watchdog triggered - system is hung
fprintf(stderr, "Watchdog timeout! System hung.\n");
// Trigger recovery
raise(SIGALRM);
}
wd->fed = 0;
}
}
void watchdog_init(Watchdog* wd, int timeout_seconds) {
wd->timeout_seconds = timeout_seconds;
wd->fed = 1;
struct sigaction sa;
sa.sa_handler = watchdog_timeout_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
pthread_create(&wd->watchdog_thread, NULL, watchdog_thread, wd);
}
void watchdog_feed(Watchdog* wd) {
wd->fed = 1;
}

Best Practices for Real-Time C Programming

  1. Avoid dynamic memory allocation in time-critical paths
  2. Use lock-free data structures where possible
  3. Keep interrupt handlers short - defer work to tasks
  4. Use priority inheritance to prevent priority inversion
  5. Profile execution times to verify deadlines
  6. Test worst-case scenarios not just average cases
  7. Use static analysis tools to catch timing issues
  8. Document timing requirements clearly
  9. Use deterministic algorithms (avoid random, unbounded loops)
  10. Minimize context switching overhead

Common Pitfalls and Solutions

PitfallSolution
Priority inversionUse priority inheritance mutexes
DeadlockEstablish consistent lock ordering
JitterUse high-resolution timers, CPU affinity
Memory fragmentationUse fixed-size memory pools
Cache missesUse data locality, lock-free structures
Stack overflowUse stack monitoring, increase stack size
Timing driftUse hardware timers, avoid software loops

Conclusion

Real-time systems programming in C requires a unique mindset where time is a first-class citizen. Success requires understanding not just the logical correctness of code, but its temporal behavior under all conditions. The techniques covered in this guide—from scheduling algorithms and inter-task communication to memory management and interrupt handling—form the foundation of reliable real-time systems.

Key takeaways:

  • Understand your timing requirements: Hard, firm, or soft real-time
  • Choose the right scheduling algorithm: RMS, EDF, or fixed-priority
  • Use deterministic constructs: Avoid unbounded loops, recursion, dynamic allocation
  • Profile and measure: Verify deadlines with worst-case execution time analysis
  • Design for testability: Simulate timing scenarios

Whether you're developing an automotive control unit, a medical device, or an industrial controller, mastering real-time techniques in C enables you to build systems that are not only logically correct but temporally correct—responding to the world with the precision it demands.

Leave a Reply

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


Macro Nepal Helper