OSAL-RS
Operating System Abstraction Layer for Rust - A cross-platform compatibility layer for embedded and real-time systems development.
Overview
OSAL-RS provides a unified API for developing multi-platform embedded applications in Rust. It abstracts operating system-specific functionality, allowing you to write portable code that can run on different platforms with minimal changes.
Workspace Components
- osal-rs: Main Operating System Abstraction Layer, with FreeRTOS and POSIX backends
- osal-rs-build: Build configuration tools and helpers (FreeRTOS type generation, POSIX porting shim compilation)
- osal-rs-porting: C FFI bridge layer for the FreeRTOS and POSIX backends
- osal-rs-tests: Comprehensive test suite for all components
- osal-rs-serde: โจ Extensible serialization/deserialization framework with derive macros
Core Features
- Thread Management: Create, manage, and synchronize threads with priorities
- Synchronization Primitives: Mutexes (recursive & non-recursive), binary & counting semaphores, event groups
- Message Queues: Type-safe inter-thread communication with blocking/non-blocking operations
- Software Timers: Periodic and one-shot timers with callbacks
- Memory Allocation: Custom allocator integration for heap management (
freertos) or the system allocator (posix) - Time Management: Duration handling and tick-based timing
- System Control: Scheduler control, task notifications, and system information
- No-std Support: Fully compatible with bare-metal embedded systems (
freertosbackend) - Host Testing: Native
stdexecution for tests, examples and simulation (posixbackend) - ๐งช EXPERIMENTAL Async/Await: Backend-agnostic
async/awaitsupport without Tokio (see Async/Await Support)
Current Implementation Status
- โ FreeRTOS: Fully implemented and tested
- โ POSIX: Fully implemented and tested (Linux/glibc and macOS on Apple Silicon) - native host backend for running, testing and simulating OSAL-RS applications without embedded hardware
- โ Serialization: Complete osal-rs-serde implementation with derive macros
- ๐งช Async/Await: Experimental, backend-agnostic, works on both FreeRTOS and POSIX
- ๐ง Other RTOSes: Under consideration
Supported Backends
OSAL-RS selects its implementation at compile time via Cargo features. There is no default backend - exactly one of freertos / posix must be enabled explicitly, or the crate fails to build. Enabling neither trips a compile_error!; enabling both is equally unsupported, since the two backends are mutually exclusive by design.
FreeRTOS Backend (freertos)
For bare-metal embedded targets (no_std). FreeRTOS provides preemptive multitasking with priority-based scheduling, mutexes with priority inheritance, semaphores, queues and software timers.
Requirements:
- FreeRTOS kernel properly configured and linked into your project
- C porting layer from
osal-rs-porting/freeretos/compiled and linked (an FFI bridge between Rust and FreeRTOS) - CMake build system set up for your embedded project (see CMake Integration)
- Rust toolchain with the appropriate embedded target installed
Configuration - ensure your FreeRTOSConfig.h includes:
Build:
# Install Rust target (example for ARM Cortex-M33)
# Build with FreeRTOS support
Custom FreeRTOSConfig.h Path
osal-rs generates its Rust types from FreeRTOSConfig.h at build time, so it must read the same header your kernel is built with. By default it looks for <workspace_root>/inc/FreeRTOSConfig.h; set FREERTOS_CONFIG_PATH to use another one:
Or, to avoid exporting it every time, in .cargo/config.toml:
[]
= { = "/path/to/your/FreeRTOSConfig.h" }
With CMake, pass it through cmake -E env as shown in Basic CMake Integration. Cargo rebuilds the bindings whenever the variable changes.
POSIX Backend (posix)
Runs on any POSIX/pthreads host so OSAL-RS applications - and their tests and doc examples - can execute for real on Linux/macOS without embedded hardware or a cross toolchain. Enabling posix disables no_std and builds the crate against std.
Requirements:
- A Linux/glibc or macOS (Apple Silicon) host (see note below) - the platform is picked automatically from the compilation target, only the
posixfeature is needed - No special build steps: unlike
freertos, the small POSIX porting shim inosal-rs-porting/posix/is compiled and linked automatically byosal-rs-build- no CMake, cross toolchain or RTOS kernel sources required
Build:
# Native development / host testing
POSIX Backend: Supported Platforms
The posix backend talks to the platform C library through hand-written bindings, so it depends on that library's struct layouts (pthread_attr_t, pthread_mutex_t, pthread_cond_t, sigset_t, etc.) and constant values. Two are supported, selected at compile time from target_os:
| Platform | Target | Notes |
|---|---|---|
| Linux / glibc | x86_64, aarch64, arm, x86, riscv64, riscv32 -unknown-linux-gnu |
Thread suspend/resume uses the first two application real-time signals (__libc_current_sigrtmin()) |
| macOS (Apple Silicon) | aarch64-apple-darwin |
Thread suspend/resume uses SIGUSR1/SIGUSR2, which the backend therefore reserves |
Everything the two platforms share is implemented once; only type sizes, constants and a few functions differ (osal-rs/src/posix/ffi/{linux,macos}.rs). A unit test compiles a small C program against the real system headers and checks every hand-written size and constant against it.
This means:
- The compiler doesn't matter - gcc or clang both work fine.
- The C library does matter - musl (e.g.
x86_64-unknown-linux-musl), macOS on Intel, BSDs and other platforms are not supported and fail to build.
Real-Time Scheduling (real_time)
Threads spawned by the posix backend normally inherit the creating thread's scheduling policy/priority. The real_time feature switches them to the real-time SCHED_FIFO policy instead.
You don't need to request this feature yourself: osal-rs-build's build script turns real_time on automatically for every supported target (Linux and macOS both provide SCHED_FIFO). It's a plain Cargo feature only so it can be inspected via cfg(feature = "real_time"); a plain cargo build --features posix is enough.
Whether a process may actually use SCHED_FIFO is only known at runtime: on Linux it needs root, CAP_SYS_NICE or a non-zero RLIMIT_RTPRIO (macOS grants it to every process). When pthread_create is refused with EPERM, the thread is created again inheriting the creating thread's policy/priority, so real_time never makes spawning fail - threads are real-time where allowed, ordinary otherwise.
Caveats of the POSIX Backend
System::start()simply spins until [System::stop()] is called from another thread - there is no scheduler to hand control to, unlike FreeRTOS where it never returns.- Timers each spawn their own background thread, which sleeps on a condition variable until the next deadline (no signals involved).
Quick Start
The snippets below run unchanged on both backends; only the freertos/posix feature changes.
Threads
use *;
use println; // works in no_std too
Mutex
use *;
use Arc;
let counter = new;
let counter_clone = counter.clone;
let mut thread = new;
thread.spawn_simple.unwrap;
Queue
use *;
let queue = new.unwrap;
// Send data
let data = ;
queue.post.unwrap;
// Receive data
let mut buffer = ;
queue.fetch.unwrap;
Event Groups
use *;
use ;
use Arc;
const READY: EventBits = 1 << 0;
const ERROR: EventBits = 1 << 1;
let events = new;
let events_clone = events.clone;
let mut thread = new;
thread.spawn_simple.unwrap;
events.set; // wakes the waiting thread
Cargo Features
OSAL-RS provides several Cargo features to customize the build configuration for different platforms and use cases:
Available Features
| Feature | Default | Description |
|---|---|---|
freertos |
โ | Enable the FreeRTOS backend implementation for embedded RTOS development. Mutually exclusive with posix - exactly one of the two is required. |
posix |
โ | Enable the POSIX/native backend implementation for host environments. Linux/glibc or macOS on Apple Silicon (see note above). Mutually exclusive with freertos - exactly one of the two is required. |
real_time |
โ | POSIX only: schedules spawned threads with the real-time SCHED_FIFO policy instead of inheriting the creating thread's policy/priority, falling back to the inherited policy when the process lacks the privilege (EPERM). Not meant to be requested by hand - osal-rs-build's build script enables it automatically on Linux and macOS targets. |
async |
โ | Enable backend-agnostic async/await support (block_on, AsyncQueue, AsyncSemaphore, AsyncMutex). Works with both freertos and posix. No Tokio required. |
serde |
โ | Enable serialization/deserialization support via osal-rs-serde. Includes derive macros for automatic implementation. |
There is no default feature set: you must explicitly pick freertos or posix or the build fails.
Feature Combinations
# FreeRTOS embedded development
# FreeRTOS with async support
# FreeRTOS with serialization support
# Native development (POSIX) - real_time is auto-detected, no need to request it
# Native development with async support
# Native development with serialization
Using Features in Cargo.toml
To use OSAL-RS in your project with specific features (exactly one of freertos/posix is required):
[]
= { = "1.4", = ["freertos"] }
# Or for host development/testing
= { = "1.4", = ["posix"] }
# Or with serialization support
= { = "1.4", = ["freertos", "serde"] }
๐งช EXPERIMENTAL Async/Await Support (feature async)
OSAL-RS includes a backend-agnostic async runtime that works on both FreeRTOS and POSIX without Tokio or any external async runtime.
Design
| Component | Description |
|---|---|
block_on(future) |
Drives a Future to completion on the calling RTOS task |
AsyncQueue |
Queue with fetch_async / post_async methods |
AsyncSemaphore |
Semaphore with wait_async |
AsyncMutex<T> |
Mutex whose lock() returns a Future |
- No Tokio, no
std: built oncore::future::Future+ OSAL semaphores as the blocking primitive. - Per-task executor:
block_onruns on the calling RTOS task; no thread pool is needed. - Lock-free waker storage:
WakerSlotusesAtomicPtr<Waker>- no RTOS overhead for waker updates. - Race-condition safe: the classic store-waker-then-retry double-check pattern is used in every
pollimplementation.
Quick Example
use ;
// Run async code inside any RTOS task โ no runtime setup required
block_on;
Enable it together with a backend, e.g. features = ["freertos", "async"] (see Cargo Features).
osal-rs-serde Features
A complete serialization framework designed specifically for embedded systems:
- No-std Compatible: Works in bare-metal environments without standard library
- Zero-Copy: Direct buffer operations with no intermediate allocations
- Derive Macros: Automatic
#[derive(Serialize, Deserialize)]implementation - Rich Type Support: Primitives, arrays, tuples, Option, Vec, nested structs
- Extensible Architecture: Create custom serializers for any format (JSON, MessagePack, CBOR, etc.)
- Memory Efficient: Little-endian binary format with predictable sizes
- Compile-Time Guarantees: Type-safe serialization with static checks
- Standalone: Can be used independently in any Rust project
osal-rs-serde Quick Example
use ;
let data = SensorData ;
// Serialize to stack buffer
let mut buffer = ;
let len = to_bytes.unwrap;
// Deserialize from buffer
let restored: SensorData = from_bytes.unwrap;
assert_eq!;
Integration with OSAL Queues
Perfect for inter-task communication:
use ;
use ;
For comprehensive documentation, examples, and advanced features, see:
- osal-rs-serde README - Complete feature documentation
- osal-rs-serde/derive README - Derive macro guide
osal-rs-serde/examples/- Working code examples
CMake Integration
CMake integration is only needed for the FreeRTOS backend, since it must link against your project's FreeRTOS kernel and C porting layer. The POSIX backend needs no CMake step - cargo build --features posix is enough (see Supported Backends).
Important: Always ensure that the C porting layer files from osal-rs-porting/freeretos/ are compiled and linked to your project, as they provide the necessary FFI bridge between Rust and FreeRTOS.
Basic CMake Integration
OSAL-RS is not linked on its own: your Rust code lives in an application crate built as a staticlib that depends on osal-rs, and CMake links that single archive into the firmware together with the FreeRTOS kernel and the C porting layer.
# app/Cargo.toml
[]
= ["staticlib"]
[]
= { = "1.4", = ["freertos"] }
[]
= "abort"
[]
= "abort"
The example below targets a Raspberry Pi Pico 2 (RP2350) with the Pico SDK and the FreeRTOS kernel port shipped with it; for another MCU, replace the Pico SDK lines and the FreeRTOS port with your own. It assumes FreeRTOS-Kernel/, osal-rs/ and the app/ crate sit next to CMakeLists.txt.
cmake_minimum_required(VERSION 3.16)
set(PICO_BOARD pico2_w)
# Pull in the Pico SDK (must be before project())
include(${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
# FreeRTOS kernel and the directory holding FreeRTOSConfig.h
set(FREERTOS_KERNEL_PATH ${CMAKE_SOURCE_DIR}/FreeRTOS-Kernel)
set(FREERTOS_CONFIG_FILE_DIRECTORY ${CMAKE_SOURCE_DIR}/inc/config)
add_subdirectory(${FREERTOS_KERNEL_PATH}/portable/ThirdParty/GCC/RP2350_ARM_NTZ/)
project(my_app C CXX ASM)
pico_sdk_init()
# --- Rust application crate (pulls in osal-rs) ---
set(CARGO_TARGET "thumbv8m.main-none-eabi")
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CARGO_PROFILE_FLAG "")
set(CARGO_PROFILE_DIR "debug")
else()
set(CARGO_PROFILE_FLAG "--release")
set(CARGO_PROFILE_DIR "release")
endif()
set(RUST_LIB ${CMAKE_SOURCE_DIR}/app/target/${CARGO_TARGET}/${CARGO_PROFILE_DIR}/libapp.a)
# Same FreeRTOSConfig.h used by the kernel: osal-rs generates its Rust types from it
set(FREERTOS_CONFIG_PATH ${FREERTOS_CONFIG_FILE_DIRECTORY}/FreeRTOSConfig.h)
# Rebuild the Rust library when the app or osal-rs sources change
file(GLOB_RECURSE RUST_SOURCES CONFIGURE_DEPENDS
"${CMAKE_SOURCE_DIR}/app/src/*.rs"
"${CMAKE_SOURCE_DIR}/app/Cargo.toml"
"${CMAKE_SOURCE_DIR}/osal-rs/*/src/*.rs"
"${CMAKE_SOURCE_DIR}/osal-rs/*/Cargo.toml"
"${CMAKE_SOURCE_DIR}/osal-rs/osal-rs/build.rs"
)
add_custom_command(
OUTPUT ${RUST_LIB}
COMMAND ${CMAKE_COMMAND} -E env
FREERTOS_CONFIG_PATH=${FREERTOS_CONFIG_PATH}
cargo build --target ${CARGO_TARGET} ${CARGO_PROFILE_FLAG}
DEPENDS ${RUST_SOURCES} ${FREERTOS_CONFIG_PATH}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/app
COMMENT "Building Rust library: app"
VERBATIM
USES_TERMINAL
)
add_custom_target(rust_app ALL DEPENDS ${RUST_LIB})
# --- Firmware: C sources + OSAL-RS porting layer ---
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS
"src/*.c"
"osal-rs/osal-rs-porting/freeretos/src/*.c"
)
add_executable(${PROJECT_NAME} ${SOURCES})
add_dependencies(${PROJECT_NAME} rust_app)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_LIST_DIR}/inc
${FREERTOS_CONFIG_FILE_DIRECTORY}
${CMAKE_CURRENT_LIST_DIR}/osal-rs/osal-rs-porting/freeretos/inc
)
target_link_libraries(${PROJECT_NAME}
${RUST_LIB}
pico_stdlib
FreeRTOS-Kernel
FreeRTOS-Kernel-Heap4
)
pico_add_extra_outputs(${PROJECT_NAME})
Key points:
- One Rust archive: only the application
staticlibis linked;osal-rsis compiled into it as a normal Cargo dependency. - Same
FreeRTOSConfig.hon both sides:FREERTOS_CONFIG_PATHmust point to the header the kernel is built with, otherwise the generated Rust types will not match the C ones. - Porting layer:
osal-rs-porting/freeretos/src/osal_rs.cis compiled into the firmware and itsinc/directory must be on the include path, next to the FreeRTOS headers. - Rebuilds:
DEPENDSlists the Rust sources and the config header, so CMake re-runscargo buildonly when one of them changes.
Breaking Changes
Only a short example per change is shown here; see the CHANGELOG for the full description.
BytesHasLen for arrays covers only single-byte elements (from version 1.3.0)
// Before: compiled, but len() returned 4 (elements) instead of 8 (bytes),
// so every post() failed with "Serialization error"
let queue = new?;
// After: [u8; N], [i8; N] and [bool; N] still work; wider arrays go in a struct
;
let queue = new?;
Details: 1.3.0.
Timer clones share one timer, and the last handle destroys it (from version 1.2.0)
let timer = new?;
let clone = timer.clone;
clone.stop; // the same timer: either handle drives it
drop; // not the last handle: the timer stays alive
drop; // last handle gone: the timer is destroyed here
Details: 1.2.0.
ThreadFn::join blocks on FreeRTOS instead of killing the thread (from version 1.2.0)
// Before, on FreeRTOS only: join() deleted the task, killing it mid-work
spawned.join?;
// After, on both backends: join() blocks until the thread's closure returns;
// use delete() to kill a task
let mut ret: *mut c_void = null_mut;
spawned.join?;
Details: 1.2.0.
EventGroupFn::wait gained a wait_for_all_bits parameter (from version 1.1.0)
// Before
let bits = event_group.wait;
// After
let bits = event_group.wait; // AND: every bit in `mask`
let bits = event_group.wait; // OR: any bit in `mask`
Details: 1.1.0.
Project Structure
osal-rs/
โโโ osal-rs/ # Main library crate (freertos + posix backends)
โโโ osal-rs-build/ # Build utilities
โโโ osal-rs-tests/ # Test suite
โโโ osal-rs-serde/ # Serialization framework
โโโ osal-rs-porting/ # Platform-specific C/C++ code
โโโ freeretos/ # FreeRTOS porting layer
โ โโโ inc/ # Header files
โ โโโ src/ # Implementation
โโโ posix/ # POSIX porting layer (glibc shim, built automatically)
โโโ inc/ # Header files
โโโ src/ # Implementation
License
This project is licensed under the LGPL-2.1-or-later License - see the LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit pull requests or open issues for bugs and feature requests.
Author
Antonio Salsi - passy.linux@zresa.it