- RTOS audit: ChibiOS, RT-Thread, Contiki, RODOS comparison - PikeOS x86 audit report - Bibliography for safety-critical hypervisor research
31 KiB
Deep Audit: ChibiOS, RT-Thread, Contiki, RODOS
Source locations
- ChibiOS:
/home/fabiorafaelcoutada/portugalfuturista/rtos_ref/ChibiOS/ - RT-Thread:
/home/fabiorafaelcoutada/portugalfuturista/rtos_ref/rt-thread/ - Contiki:
/home/fabiorafaelcoutada/portugalfuturista/rtos_ref/contiki/ - RODOS:
/home/fabiorafaelcoutada/portugalfuturista/rtos_ref/rodos/
1. ChibiOS/RT v8.0.0
1.1 Architecture Support
Port layers in os/common/ports/:
- ARM, ARM-common, ARMv6-M, ARMv7-M, ARMv7-M-ALT, ARMv7-R, ARMv8-M-ML, ARMv8-M-ML-ALT, ARMv8-M-ML-TZ, ARMvx-M-SB
- AVR, e200 (PowerPC NXP), SIMIA32, SIMX86_64
HAL ports in os/hal/ports/:
- ADUCM (Analog Devices), AVR, LPC (NXP), MAX32, RP (Raspberry Pi RP2040), SPC5 (ST PowerPC), STM32 (C0/F0/F1/F3/F4/F7/G0/G4/H5/H7/L0/L1/L4/L5/MP1/U0/U3/U5/WB/WL), simulator
Demo BSPs in demos/:
- ADUCM, AVR (Arduino UNO/MEGA/MINI/NANO/Leonardo, DigiSpark ATTiny, MT-DB-X4), LPC21xx, MAX32, RP, SPC5, STM32, various
Key takeaway: Broad ARM Cortex-M coverage (M0/M0+/M3/M4/M7/M23/M33/M55/M85 with TrustZone), AVR, PPC e200, simulator (x86/x86_64).
1.2 Memory Model
No MMU required. Flat memory model with static allocation as primary pattern.
File: os/rt/include/chmem.h
- Static working areas:
THD_WORKING_AREA(s, n)allocates thread stack +thread_tstruct in BSS - NUMA memory classes:
CH_MEM_GLOBAL_BSS,CH_MEM_LOCAL_BSS(c),CH_MEM_PRIVATE_BSS(c), plus coherent variants for SMP cache management - No heap allocator in kernel: ChibiOS relies on statically allocated pools. Dynamic allocation is optional via
oslib/(heap, mempool, mailbox abstractions) - Stack guard via MPU (
PORT_ENABLE_GUARD_PAGESon ARMv7-M)
Design pattern: Pre-allocated working areas, no malloc() in kernel. Thread stacks are compile-time sized arrays.
1.3 Kernel Primitives
Threads (os/rt/include/chthreads.h, os/rt/src/chthreads.c)
typedef void (*tfunc_t)(void *p);
// Static creation
thread_t *chThdCreateStatic(void *wsp, size_t size, tprio_t prio, tfunc_t tp, void *arg);
thread_t *chThdCreateI(...); // from ISR context
// Dynamic creation (requires CH_CFG_USE_DYNAMIC)
thread_t *chThdCreate(...);
// Control
void chThdExit(msg_t msg);
void chThdTerminate(thread_t *tp);
msg_t chThdWait(thread_t *tp); // join
void chThdSleep(sysinterval_t ticks);
void chThdSleepUntil(systime_t time);
void chThdYield(void);
void chThdSuspendS(thread_t *tpp);
void chThdResumeI(thread_t *tp, msg_t msg);
Thread states (16 states, chschd.h):
READY, CURRENT, WTSTART, SUSPENDED, QUEUED, WTSEM, WTMTX,
WTCOND, SLEEPING, WTEXIT, WTOREVT, WTANDEVT, SNDMSGQ, SNDMSG, WTMSG, FINAL
Scheduler (os/rt/include/chschd.h, os/rt/src/chschd.c)
void chSchReadyI(thread_t *tp); // insert in ready list
void chSchGoSleepS(tstate_t newstate); // sleep
msg_t chSchGoSleepTimeoutS(tstate_t, sysinterval_t);
void chSchWakeupS(thread_t *ntp, msg_t msg);
void chSchRescheduleS(void);
void chSchDoPreemption(void);
void chSchDoYieldS(void);
thread_t *chSchSelectFirst(void);
Priority-based preemptive ready list. Priorities 1-255 (IDLE=1, LOW=2, NORMAL=128, HIGH=255). ch_sch_prio_insert() walks sorted doubly-linked list. Supports CH_CFG_TIME_QUANTUM for round-robin among equal-priority threads.
IPC Primitives
Semaphores (chsem.h):
void chSemObjectInit(semaphore_t *sp, cnt_t n);
msg_t chSemWait(semaphore_t *sp);
msg_t chSemWaitTimeout(semaphore_t *sp, sysinterval_t timeout);
void chSemSignal(semaphore_t *sp);
void chSemSignalI(semaphore_t *sp); // ISR-safe
msg_t chSemSignalWait(semaphore_t *sps, semaphore_t *spw); // atomic signal+wait
Mutexes (chmtx.h) with priority inheritance:
void chMtxLock(mutex_t *mp);
bool chMtxTryLock(mutex_t *mp);
void chMtxUnlock(mutex_t *mp);
void chMtxUnlockAll(void); // unlock all mutexes held by current thread
Mutex structure has owner, next (linked list on thread), optional cnt for recursive.
Condition Variables (chcond.h):
void chCondSignal(condition_variable_t *cp);
void chCondBroadcast(condition_variable_t *cp);
msg_t chCondWait(condition_variable_t *cp);
Events (chevents.h) - event flags model:
void chEvtRegisterMaskWithFlags(event_source_t *esp, event_listener_t *elp, eventmask_t events, eventflags_t wflags);
void chEvtSignal(thread_t *tp, eventmask_t events);
eventmask_t chEvtWaitAny(eventmask_t events);
eventmask_t chEvtWaitAll(eventmask_t events);
void chEvtDispatch(const evhandler_t *handlers, eventmask_t events);
Event listeners link to event sources. Uses bitmask model (eventmask_t + eventflags_t).
Synchronous Messages (chmsg.h) - rendezvous-style:
msg_t chMsgSend(thread_t *tp, msg_t msg); // blocking send, returns reply
thread_t *chMsgWait(void); // blocking receive
void chMsgRelease(thread_t *tp, msg_t msg); // reply
ChibiOS/NIL (ultra-lightweight variant, os/nil/)
- Only 4 source files:
ch.c,chevt.c,chmsg.c,chsem.c - Cooperative only, no preemption
- Fixed number of threads (compile-time)
- Minimal states: WTSTART, READY, SLEEPING, SUSPENDED, WTEXIT, WTQUEUE, WTOREVT, WTANDEVT, SNDMSGQ, SNDMSG, WTMSG
- ~1KB footprint, ideal for 8-bit MCUs
1.4 API Surface
Single include: #include "ch.h" → pulls all kernel headers:
chearly.h → chrfcu.h → chdebug.h → chtime.h → chalign.h → chtrace.h →
chport.h → chsafety.h → chlists.h → chtmm → chstats.h → chobjects.h →
chmem.h → chsys.h → chinstances.h → chvt.h → chschd.h → chthreads.h →
chregistry.h → chsem.h → chmtx.h → chcond.h → chevents.h → chmsg.h →
chlib.h (OSLIB) → chdynamic.h
Locking model: Three-class API with S suffix (caller must hold system lock), I suffix (caller must be in ISR), no suffix (API-level, acquires lock internally):
chSysLock(); // enter critical section
chSysUnlock(); // exit critical
1.5 Hardware Abstraction
Port layer contract (os/rt/include/chport.h) requires every port to define:
PORT_COMPILER_NAME, PORT_IDLE_THREAD_STACK_SIZE, PORT_INT_REQUIRED_STACK,
PORT_SUPPORTS_RT, PORT_NATURAL_ALIGN, PORT_STACK_ALIGN, PORT_WORKING_AREA_ALIGN,
PORT_ARCHITECTURE_NAME, PORT_CORE_VARIANT_NAME, PORT_INFO,
PORT_IRQ_IS_VALID_PRIORITY, PORT_IRQ_IS_VALID_KERNEL_PRIORITY,
PORT_SETUP_CONTEXT, PORT_WA_SIZE, PORT_IRQ_PROLOGUE, PORT_IRQ_EPILOGUE,
PORT_IRQ_HANDLER, PORT_FAST_IRQ_HANDLER
HAL layer (os/hal/): Full driver abstraction for ADC, CAN, DAC, EXT, GPT, I2C, ICU, MAC, PAL (GPIO), PWM, RTC, SDC, SERIAL, SIO, SPI, UART, USB, WDG. Each has a hal_xxx_lld.h low-level driver per port.
1.6 Tick/Timer Infrastructure
File: os/rt/include/chvt.h, os/rt/src/chvt.c
Two modes:
- Tick mode (
CH_CFG_ST_TIMEDELTA == 0): Regular SysTick interrupt incrementscurrcore->vtlist.systime. Virtual timers stored in delta list. - Tickless mode (
CH_CFG_ST_TIMEDELTA > 0): Hardware timer programmed for exact next wakeup.port_timer_get_time()returns current time.
void chVTDoSetI(virtual_timer_t *vtp, sysinterval_t delay, vtfunc_t vtfunc, void *par);
void chVTDoSetContinuousI(virtual_timer_t *vtp, sysinterval_t delay, vtfunc_t vtfunc, void *par);
void chVTDoResetI(virtual_timer_t *vtp);
void chVTDoTickI(void); // called from tick ISR or timer ISR
systime_t chVTGetSystemTimeX(void);
Virtual timer is a delta-list node with callback. Supports one-shot and continuous (auto-reload).
1.7 Cooperative vs Preemptive
ChibiOS/RT: Fully preemptive by default. Priority-based preemption. Optional time quantum for round-robin at same priority (CH_CFG_TIME_QUANTUM > 0). No cooperative-only mode.
ChibiOS/NIL: Cooperative only. Threads yield explicitly. No preemption. Ultra-minimal for deeply constrained targets.
2. RT-Thread v5.3.0
2.1 Architecture Support
libcpu/ supports 21 architecture families:
- aarch64, arc, arm (23 sub-ports: am335x, arm926, armv6, AT91SAM7S/X, cortex-a/m0/m23/m3/m33/m4/m7/m85/r4/r52, dm36x, lpc214x/lpc24xx, s3c24x0/s3c44b0, sep4020, zynqmp-r5)
- avr32, blackfin, c-sky, ia32, m16c, mips, nios, ppc, risc-v, rx, sim, sparc-v8, ti-dsp, unicore32, v850, xilinx
BSP/: 107 board support packages including STM32 (full range), GD32, Infineon, Renesas, NXP, Allwinner, Raspberry Pi, QEMU (aarch64, riscv, virt64), RISC-V (hifive1, rv32m1_vega), x86, simulator, and many Chinese MCU vendors.
2.2 Memory Model
No MMU required. Three memory allocation strategies (compile-time selectable):
-
Small Memory (
src/mem.c,RT_USING_SMALL_MEM): First-fit allocator from contiguous heap.rt_smem_init()takes a begin address + size. Items linked vianext/prevoffsets. Coalescing on free. O(n) worst-case. -
SLAB allocator (
src/slab.c,RT_USING_SLAB): Derived from DragonFly BSD. 72 zones, chunk sizes 8B to 16KB. Page-based backing. O(1) alloc/free for common sizes. -
Memory heap (
src/memheap.c,RT_USING_MEMHEAP): Multiple disjoint memory regions managed as a single logical heap. Good for systems with non-contiguous RAM (e.g., SRAM + external SDRAM). -
Memory pool (
src/mempool.c): Fixed-size block pool, O(1) alloc/free.
Key API:
void *rt_malloc(rt_size_t nbytes);
void rt_free(void *ptr);
void *rt_realloc(void *ptr, rt_size_t nbytes);
void *rt_calloc(rt_size_t count, rt_size_t size);
rt_smem_t rt_smem_init(const char *name, void *begin_addr, rt_size_t size);
2.3 Kernel Primitives
Threads (src/thread.c, include/rtdef.h)
// Static creation
rt_err_t rt_thread_init(struct rt_thread *thread, const char *name,
void (*entry)(void *parameter), void *parameter,
void *stack_start, rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick);
// Dynamic creation
rt_thread_t rt_thread_create(const char *name, void (*entry)(void *parameter),
void *parameter, rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick);
rt_err_t rt_thread_startup(rt_thread_t thread);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_yield(void);
rt_err_t rt_thread_suspend(rt_thread_t thread);
rt_err_t rt_thread_resume(rt_thread_t thread);
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg);
rt_thread_t rt_thread_self(void);
Thread control block (rtdef.h:852):
struct rt_thread {
struct rt_object parent; // inherit from kernel object
void *sp, *entry, *parameter, *stack_addr;
rt_uint32_t stack_size;
rt_err_t error;
RT_SCHED_THREAD_CTX; // scheduler-specific context
struct rt_timer thread_timer; // built-in thread timer
rt_thread_cleanup_t cleanup;
rt_list_t taken_object_list; // mutex tracking for priority inheritance
rt_object_t pending_object;
rt_uint32_t event_set;
rt_uint8_t event_info;
// ... signals, pthreads, LWP fields
};
Scheduler (src/scheduler_comm.c, src/scheduler_up.c, src/scheduler_mp.c)
Two scheduler variants:
- UP (uniprocessor): Bit-map priority scheduler.
priority_group+ready_table[32]for O(1) highest-priority lookup. Up to 256 priority levels. - MP (multiprocessor/SMP): Per-CPU ready queues, IPI-based preemption, CPU affinity binding.
void rt_sched_thread_init_ctx(struct rt_thread *thread, rt_uint32_t tick, rt_uint8_t priority);
rt_err_t rt_sched_thread_ready(struct rt_thread *thread);
IPC (src/ipc.c)
Semaphores:
rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag);
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout);
rt_err_t rt_sem_release(rt_sem_t sem);
Mutexes with priority inheritance + priority ceiling:
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout);
rt_err_t rt_mutex_release(rt_mutex_t mutex);
Events (32-bit bitmask):
rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag);
rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved);
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);
Mailbox (fixed-size message slots):
rt_err_t rt_mb_init(rt_mailbox_t mb, const char *name, void *msgpool, rt_size_t size, rt_uint8_t flag);
rt_err_t rt_mb_send(rt_mailbox_t mb, rt_ubase_t value);
rt_err_t rt_mb_recv(rt_mailbox_t mb, rt_ubase_t *value, rt_int32_t timeout);
Message Queue (variable-size messages):
rt_err_t rt_mq_init(rt_mq_t mq, const char *name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag);
rt_err_t rt_mq_send(rt_mq_t mq, const void *buffer, rt_size_t size);
rt_err_t rt_mq_recv(rt_mq_t mq, void *buffer, rt_size_t size, rt_int32_t timeout);
IPC flags: RT_IPC_FLAG_PRIO (priority-ordered waiters) or RT_IPC_FLAG_FIFO.
2.4 API Surface
Single include: #include <rtthread.h> → rtdef.h, rtservice.h, rtm.h, rtatomic.h, rtklibc.h
Object system: All kernel objects inherit from struct rt_object with name, type, flag, list node. Enables runtime object discovery via rt_object_find().
Initialization export macros for component init ordering:
INIT_BOARD_EXPORT(fn) // level "1"
INIT_DEVICE_EXPORT(fn) // level "3"
INIT_COMPONENT_EXPORT(fn)// level "4"
INIT_APP_EXPORT(fn) // level "6"
Hook system: Per-operation hooks (rt_thread_suspend_sethook, rt_timer_enter_sethook, etc.) plus hook lists for multiple subscribers.
2.5 Hardware Abstraction
libcpu/ per-arch provides:
rt_uint8_t *rt_hw_stack_init(void *entry, void *parameter, rt_uint8_t *stack_addr, void *exit);
void rt_hw_context_switch(rt_ubase_t from, rt_ubase_t to);
void rt_hw_context_switch_interrupt(rt_ubase_t from, rt_ubase_t to, rt_thread_t from_thread, rt_thread_t to_thread);
rt_base_t rt_hw_interrupt_disable(void);
void rt_hw_interrupt_enable(rt_base_t level);
void rt_hw_cpu_reset(void);
void rt_hw_cpu_shutdown(void);
BSP/ per-board provides system clock, peripheral init, linker script, console.
Device framework (include/rtdef.h): struct rt_device with rt_device_ops (init, open, close, read, write, control).
2.6 Tick/Timer Infrastructure
File: src/clock.c, src/timer.c
rt_tick_t rt_tick_get(void);
void rt_tick_increase(void); // called from SysTick ISR
void rt_tick_increase_tick(rt_tick_t tick); // multi-tick advance
void rt_timer_init(rt_timer_t timer, const char *name,
void (*timeout)(void *parameter), void *parameter,
rt_tick_t time, rt_uint8_t flag);
rt_err_t rt_timer_start(rt_timer_t timer);
rt_err_t rt_timer_stop(rt_timer_t timer);
rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg);
Timer flags: RT_TIMER_FLAG_ONE_SHOT / RT_TIMER_FLAG_PERIODIC, RT_TIMER_FLAG_HARD_TIMER (callback in ISR) / RT_TIMER_FLAG_SOFT_TIMER (callback in timer thread). Uses skip list for O(log n) timer insertion (RT_TIMER_SKIP_LIST_LEVEL).
2.7 Cooperative vs Preemptive
Fully preemptive by default. Priority-based preemption with optional time-slice round-robin (tick parameter per thread). SMP variant adds per-CPU scheduling with IPI-triggered rescheduling.
No explicit cooperative mode, but a thread can yield voluntarily with rt_thread_yield().
3. Contiki OS (v3.x)
3.1 Architecture Support
Platform directory (platform/) with 33 platforms:
- AVR: atmega128rfa1, raven, ravenlcd, ravenusb, rcb, rss2, zigbit
- ARM: cc2538dk, nrf52dk, openmote-cc2538, srf06-cc26xx, stm32nucleo-spirit1, stm32test, zoul
- MSP430: exp5438, sky, wismote, z1
- x86: native (Linux host), win32, galileo, minimal-net
- Simulators: cooja, cooja-ip64
- Other: cc2530dk, econotag, ev-aducrf101mkxz, jn516x, mbxxx, micaz, seedeye
No libcpu separation — each platform provides its own clock, rtimer, and radio drivers directly.
3.2 Memory Model
No MMU. No dynamic memory allocation in the kernel.
Contiki uses no heap at all by default. Everything is statically allocated:
- Process structures are compile-time macros
- Event queue is a fixed-size array:
static struct event_data events[PROCESS_CONF_NUMEVENTS](default 32) - Protothread state is a single
charin the process struct (local continuation) - No thread stacks — protothreads share the main stack
This is the most memory-efficient model of all four RTOSes. A protothread is literally a single byte of state.
3.3 Kernel Primitives
Protothreads (core/sys/pt.h)
Protothreads are stackless threads implemented as C macros using Duff's device (local continuations):
struct pt { lc_t lc; }; // lc_t is typically a char
PT_THREAD(my_thread(struct pt *pt, process_event_t ev, process_data_t data));
#define PT_BEGIN(pt) // start
#define PT_END(pt) // end
#define PT_WAIT_UNTIL(pt, condition) // block until condition
#define PT_WAIT_WHILE(pt, condition) // block while condition
#define PT_YIELD(pt) // yield to scheduler
#define PT_SPAWN(pt, child, thread) // spawn child protothread
No per-thread stack. Each protothread is a function that returns PT_WAITING, PT_YIELDED, PT_EXITED, or PT_ENDED. The continuation point is stored in lc_t (typically 1 byte using GCC computed goto labels, or 2 bytes using switch-based Duff's device).
Processes (core/sys/process.h, core/sys/process.c)
PROCESS_THREAD(name, ev, data); // declare process
PROCESS_NAME(name); // get process name
void process_start(struct process *p, process_data_t data);
void process_exit(struct process *p);
int process_post(struct process *p, process_event_t ev, process_data_t data);
void process_poll(struct process *p);
process_event_t process_alloc_event(void);
Each process wraps a protothread:
struct process {
struct process *next;
const char *name;
PT_THREAD((*thread)(struct pt *, process_event_t, process_data_t));
struct pt pt;
unsigned char state, needspoll;
};
Event System
Synchronous event dispatch via process_run():
// In main loop:
while(1) {
do {} while(process_run() > 0); // dispatch events
// low-power sleep if no events
}
Events are posted to a fixed-size circular queue (events[PROCESS_CONF_NUMEVENTS]). Pre-defined events: PROCESS_EVENT_INIT, PROCESS_EVENT_POLL, PROCESS_EVENT_EXIT, PROCESS_EVENT_TIMER, PROCESS_EVENT_CONTINUE, etc.
No mutexes, semaphores, or message queues
Contiki has none of these. Synchronization is entirely event-driven + protothread blocking conditions. core/sys/pt-sem.h provides a minimal counting semaphore for protothreads.
3.4 API Surface
Main includes: contiki.h, contiki-net.h, contiki-lib.h
// Process macros
PROCESS_THREAD(), PROCESS_BEGIN(), PROCESS_END(), PROCESS_YIELD()
PROCESS_WAIT_EVENT(), PROCESS_WAIT_EVENT_UNTIL()
PROCESS_POLL_AND_EXIT()
// Timer APIs
timer_set(), timer_reset(), timer_expired()
etimer_set(), etimer_reset(), etimer_expired()
ctimer_set(), ctimer_reset(), ctimer_expired()
stimer_set(), stimer_expired()
rtimer_set()
// Event APIs
process_post(), process_poll()
3.5 Hardware Abstraction
Each platform provides:
clock.c/clock.h: System tickrtimer-arch.c: Real-time timer*contiki-conf.h: Platform configuration- Radio driver (for networking)
No unified HAL layer — each platform is self-contained.
3.6 Tick/Timer Infrastructure
Four timer layers (coexisting):
-
timer (
core/sys/timer.h): Simple interval timer, checked by pollingtimer_expired(). Usesclock_time_t(typically 16-bit). -
etimer (
core/sys/etimer.h): Event timer — postsPROCESS_EVENT_TIMERto the owning process when expired. Linked list managed byetimer_request_poll(). -
ctimer (
core/sys/ctimer.h): Callback timer — calls a function pointer on expiry. -
rtimer (
core/sys/rtimer.h): Real-time timer with microsecond precision, runs from ISR context. Used for time-critical radio operations.
// Clock layer
void clock_init(void);
clock_time_t clock_time(void); // typically 32 ticks/sec
unsigned long clock_seconds(void);
#define CLOCK_SECOND (clock_time_t)32
// rtimer
void rtimer_set(struct rtimer *rt, rtimer_clock_t time, rtimer_clock_t duration,
rtimer_callback_t func, void *ptr);
3.7 Cooperative vs Preemptive
Purely cooperative. Protothreads yield explicitly via PT_YIELD(), PT_WAIT_UNTIL(), etc. No preemption. The main loop runs process_run() which dispatches one event at a time.
The only "preemption" is ISR-level rtimer callbacks, which run in interrupt context and must not block.
4. RODOS (Realtime Onboard Dependable Operating System) v2.x
4.1 Architecture Support
Bare-metal ports (src/bare-metal/):
- Cortex-M (
src/bare-metal-cortex-m/): Generic Cortex-M port with PendSV-based context switch - STM32F4: STM32F4xx (F407, F411 discovery boards)
- STM32H7: STM32H7xx (H723, H735, H745, H753 — Nucleo, Discovery, custom boards)
- STM32L4: STM32L431, L432, L475, L496 (Nucleo L432KC, L496ZG, Discovery L475)
- STM32WB: STM32WB55 Nucleo (BLE-capable)
- EFR32FG1P: Silicon Labs EFR32 Flex Gecko (Thunderboard)
- VA41620: Vorago VA41620 (radiation-hardened Cortex-M4)
- SF2: Microsemi SmartFusion2 (Cortex-M3 + FPGA)
- Raspberry Pi 3: Bare-metal AArch64
- Linux x86: Linux-hosted with makecontext/setcontext
- Template: Porting template
POSIX simulation (src/on-posix/, src/on-posix-mac/): Run as Linux/macOS process using POSIX threads.
4.2 Memory Model
No MMU required. Fully static memory model in C++.
- Threads use either static stacks (via template) or dynamic
new(deprecated):
template <size_t STACK_SIZE>
Thread(char (&stack)[STACK_SIZE], const char* name, const int32_t priority);
// deprecated: Thread(const char* name, int32_t priority, size_t stackSize);
- StaticThread<STACK_SIZE> is the recommended pattern (compile-time sized stack array).
- No heap allocator in kernel. All kernel objects (
Thread,Semaphore,TimeEvent,Topic,Subscriber) are statically constructed C++ objects created beforemain(). - Stack grows downward. Sentinel value
0xdeaddeadplaced at stack base for overflow detection.
4.3 Kernel Primitives
Threads (api/thread.h, src/independent/thread.cpp)
C++ class with virtual run() method:
class Thread : public ListElement {
public:
Thread(const char* name, int32_t priority, size_t stackSize);
template <size_t STACK_SIZE>
Thread(char (&stack)[STACK_SIZE], const char* name, int32_t priority);
virtual void run() = 0; // user implements
virtual void init() {} // called before run()
static bool suspendCallerUntil(int64_t reactivationTime = END_OF_TIME, void* signaler = 0);
void suspendUntilNextBeat();
void setPeriodicBeat(int64_t begin, int64_t period);
void resume();
void resumeAndYield();
static void yield();
static Thread* getCurrentThread();
static Thread* findNextToRun(int64_t& selectedEarliestSuspendedUntil);
static Thread* findNextWaitingFor(void* signaler);
};
Thread state tracked via:
Atomic<long*> context{nullptr}; // saved stack pointer
Atomic<int32_t> priority{};
Atomic<int64_t> suspendedUntil{0}; // wake time
Atomic<void*> waitingFor{nullptr}; // synchronization target
Atomic<uint64_t> lastActivation{0};
Scheduler
Priority-based preemptive scheduler using PendSV on Cortex-M:
Thread::findNextToRun()iterates all threads, selects highest-priority ready thread- Context switch via PendSV interrupt: save R4-R11 + EXC_RETURN to PSP, call
schedulerWrapper(), restore next thread's context - SVC handler for first thread start (idle thread)
- Time-driven: scheduler triggered by
TimeEvent::propagate()in SysTick
// Context switch chain (Cortex-M):
__asmSaveContextAndCallScheduler() → SCB->ICSR |= PENDSVSET →
PendSV_Handler: save regs → schedulerWrapper() → restore next thread regs
Semaphore (api/rodos-semaphore.h)
Mutex semaphore with priority ceiling:
class Semaphore {
Atomic<Thread*> owner;
Atomic<int32_t> ownerEnterCnt; // reentrant lock count
Atomic<int32_t> ownerPriority;
public:
void enter(); // blocking lock
void leave(); // unlock + resume highest-priority waiter
};
// RAII guard
class ScopeProtector { ... };
#define PROTECT_IN_SCOPE(_sema)
Barrier (api/barrier.h)
class Barrier {
Thread* volatile owner;
public:
bool waitForSignal(bool condition = true, int64_t timeOutAt = END_OF_TIME);
bool unblock();
};
Event (api/event.h)
class Event {
bool state_;
Thread *waiter_; // only ONE waiter!
public:
bool set(void); // trigger + optionally resume waiter
bool suspendUntilTriggered(int64_t timeout = END_OF_TIME);
void reset(void);
};
Topic/Subscriber (Publish-Subscribe middleware, api/topic.h)
template <class Type>
class Topic : public TopicInterface {
public:
Topic(int64_t id, const char* name, bool onlyLocal = false);
uint32_t publish(Type &msg, bool shallSendToNetwork = true);
};
class Subscriber : public SubscriberInterface {
public:
Subscriber(TopicInterface &topic, const char* name = "Subscriber");
virtual void put(uint32_t topicId, size_t len, void *msg, ...) = 0;
};
Gateway (inter-node communication, api/gateway/)
class Gateway : public ListElement {
virtual bool sendNetworkMessage(NetworkMessage &msg);
virtual void onIncomingMessage(NetworkMessage &msg);
};
Link interfaces: CAN, UART, UDP, Shared Memory.
CommBuffer (api/commbuffer.h)
template <class Type>
class CommBuffer { ... }; // lock-free single-producer/single-consumer buffer
4.4 API Surface
Language: C++ (C++11 minimum). Single master header: #include <rodos.h> → api/rodos.h
Key headers:
thread.h, rodos-semaphore.h, barrier.h, event.h, topic.h, subscriber.h,
timeevent.h, timemodel.h, hal.h, gateway.h, commbuffer.h, application.h,
fifo.h, putter.h
Application model:
class Application : public ListElement {
virtual long init() { return 0; }
virtual void run() { }
};
All Application and Thread instances are global objects. main() calls initSystem() → initAllThreads() → startIdleThread().
4.5 Hardware Abstraction
File: api/hal.h, api/hal/hal_*.h
class HW_HAL { /* base for all HW peripherals */ };
class GPIO : public HW_HAL { ... };
class UART : public HW_HAL { ... };
class SPI : public HW_HAL { ... };
class I2C : public HW_HAL { ... };
class ADC : public HW_HAL { ... };
class PWM : public HW_HAL { ... };
class CAN : public HW_HAL { ... };
class Ethernet : public HW_HAL { ... };
class SharedMemory : public HW_HAL { ... };
Each port implements the HAL classes. Platform-specific code in src/bare-metal/<platform>/hal/.
4.6 Tick/Timer Infrastructure
File: api/timeevent.h, src/independent/timeevent.cpp, src/independent/timemodel.cpp
class TimeEvent : public ListElement {
static List timeEventList;
Atomic<int64_t> eventAt;
Atomic<int64_t> eventPeriod;
public:
virtual void handle(void) {} // override for custom handling
void activateAt(int64_t time);
void activatePeriodic(int64_t startAt, int64_t period);
static int32_t propagate(int64_t timeNow); // called from tick ISR
static int64_t getNextTriggerTime();
};
Time model: Uses nanosecond-precision int64_t time (Nanoseconds, Microseconds, Milliseconds, Seconds are all int64_t aliases). NOW() returns current time. TimeModel::computeNextBeat() handles period computation.
Tick ISR calls TimeEvent::propagate(NOW()) which:
- Iterates all TimeEvents
- Fires handlers where
eventAt < now - Updates
eventAtfor periodic events - Triggers scheduler if a thread became ready
4.7 Cooperative vs Preemptive
Fully preemptive via PendSV on Cortex-M:
TimeEvent::propagate()runs in SysTick ISR- When a higher-priority thread becomes ready,
__asmSaveContextAndCallScheduler()triggers PendSV - PendSV performs full context switch (R4-R11 + EXC_RETURN, optionally S16-S31 for FPU)
Thread::yield()callsresume()+ triggers reschedule
On POSIX platforms: preemption via POSIX signals (SIGALRM or timer threads).
Comparative Summary for UniversalisOS
| Feature | ChibiOS/RT | RT-Thread | Contiki | RODOS |
|---|---|---|---|---|
| Language | C | C | C | C++ |
| Arch support | ARM, AVR, PPC, x86 | 21 families, 107 BSPs | 33 platforms (sensor nodes) | ARM Cortex-M, RPi3, POSIX |
| MMU needed | No | No (optional RT-Smart uses it) | No | No |
| Memory model | Static working areas | Heap (small/slab/mempool) | Zero-allocation | Static C++ objects |
| Thread model | Preemptive, priority | Preemptive, priority | Stackless protothreads | Preemptive, priority |
| Context switch | SVC/PendSV (ARM) | arch-specific | None (continuation) | PendSV (ARM) |
| IPC | Sem, Mutex, CondVar, Events, Msg | Sem, Mutex, Event, MB, MQ | Event queue only | Sem, Barrier, Event, Topic/Subscriber |
| Priority inherit. | Yes (mutex) | Yes (mutex + ceiling) | N/A | Yes (semaphore) |
| SMP | Yes (CH_CFG_SMP_MODE) | Yes (RT_USING_SMP) | No | No (single-core) |
| Timer | Virtual timer delta list | Skip-list timer | 4-layer (timer/etimer/ctimer/rtimer) | TimeEvent list |
| Tickless | Yes (CH_CFG_ST_TIMEDELTA) | No (tick-based) | rtimer is real-time | Yes (nanosecond time) |
| Min footprint | ~1KB (NIL), ~5KB (RT) | ~3KB (nano mode) | ~1KB (protothreads) | ~2KB (Cortex-M) |
| Scheduling | Preemptive + round-robin | Preemptive + round-robin | Cooperative only | Preemptive |
Key Design Patterns for UniversalisOS
-
ChibiOS port contract (
chcore.h): Excellent model for hypervisor port layer. Defines exact macros every port must export (stack init, IRQ prologue/epilogue, context setup, priority validation). -
RT-Thread object system: Uniform kernel object model with name, type, list node. Enables
rt_object_find()for runtime discovery. Good pattern for hypervisor resource management. -
Contiki protothreads: Zero-overhead "threads" for monitoring/management tasks that never need to block on hardware. Could be used for hypervisor event handlers.
-
RODOS Topic/Subscriber: Built-in publish-subscribe middleware for inter-partition communication in a hypervisor. The Gateway/LinkInterface pattern maps directly to inter-VM communication.
-
ChibiOS SMP memory classes:
CH_MEM_GLOBAL_COHERENT_BSS,CH_MEM_PRIVATE_BSS(c)— directly applicable to hypervisor memory partitioning for cache-coherent shared regions. -
RT-Thread skip-list timers: O(log n) timer insertion vs O(n) delta lists. Better for large numbers of virtual timers.
-
RODOS nanosecond time:
int64_tnanoseconds avoids 32-bit wrap issues. Better for hypervisor wall-clock management than tick-based models.