Skip to main content

Real-Time Safety

The timing constraint

A 5G NR base station processes one slot every 500 µs (numerology µ=1) or 1 ms (µ=0). The L1 modem and the time-critical portions of the L2 scheduler must complete all their work within this budget - every slot, without exception. A single missed deadline causes a dropped slot, which translates directly to degraded throughput and user experience for every UE in the cell.

This hard real-time requirement shapes how code on the critical path must be written.

What the critical path must never do

The following operations are forbidden on any code path that executes within the slot processing budget:

Forbidden operationWhy
Dynamic memory allocation (new, malloc, std::vector::push_back that triggers a resize, …)Allocation involves a system call and a lock on the allocator; latency is unbounded
System calls (file I/O, socket operations, clock_gettime with VDSO disabled, ..)Kernel transitions have unpredictable latency under load
Explicit synchronisation (std::mutex::lock, condition variables, semaphores)Blocking on a contended lock causes priority inversion with no bounded wait time
ExceptionsException handling involves heap allocation and dynamic dispatch; timing is not guaranteed
Logging to a blocking sinkWriting to a file or socket on the critical path introduces I/O latency

Pre-allocated memory pools, lock-free queues, and non-blocking ring buffers are the standard alternatives for each of these.

Coding discipline

Real-time safety is enforced through code review and tooling primarily. We primarily use RTSan (real-time sanitizer). RTSan runs daily in CI and reports any allocation, system call, or blocking primitive reached from a real-time-annotated call stack. OCUDU uses the OCUDU_RTSAN_NONBLOCKING macro to annotate any code that must not violate RT requirements.

When contributing code to a real-time path, annotate it with the appropriate marker so the sanitizer can enforce the constraint automatically.

Performance profiling

Meeting the timing budget requires knowing where time is actually spent. OCUDU uses:

  • Execution traces - fine-grained timestamps at slot entry/exit and at key processing stages, written to a lock-free ring buffer and flushed off the critical path.
  • Benchmarks - micro-benchmarks for inner-loop functions (channel estimation, LDPC encode/decode, FFT) run in CI to catch regressions before they land.
  • Profilers - periodic profiling runs (perf, VTune) on representative workloads to identify hotspots and guide optimisation.

Real-time safe alternatives

NeedRT-safe approach
Variable-length bufferPre-allocated memory pool; fixed-capacity ring buffer
Passing data between threadsLock-free queue (single-producer / single-consumer or MPSC)
Signalling an eventAtomic flag or lock-free notification primitive
Logging from the critical pathWrite to a lock-free ring buffer; a background thread drains it
Timing measurementclock_gettime(CLOCK_MONOTONIC_RAW) or a hardware TSC read