Linux vs RT-Smart vs NuttX on the Same RISC-V Board
24 August 2026 · Lance Harvie

A few weeks ago I made a post questioning whether we sometimes overengineer embedded systems.
It generated a lot of discussion, but it also left me with a question I wanted to answer properly.
Instead of arguing Linux vs RTOS in theory, what happens if you keep the hardware exactly the same and change only the operating-system architecture?
So I decided to test it.
One RISC-V board, three operating systems
The hardware platform was a Sipeed Lichee RV / 86 Panel built around the Allwinner D1 and XuanTie C906 RISC-V processor.
The important point was that the physical platform did not change.
Same processor.
Same memory.
Same board.
Same timer hardware.
Same boot environment.
I compared three operating-system environments:
Ubuntu Linux
RT-Thread RT-Smart 5.0.2
Apache NuttX 13.0.1-RC0
Linux represented the mature, protected-process environment.
RT-Smart provided MMU-backed userspace and process isolation in a much smaller embedded operating system.
NuttX was brought up in FLAT S-mode, where tasks share one address space.
That architectural difference became very important when interpreting the results.
The bring-up became more interesting than the benchmark
I originally expected most of the work to be benchmarking.
It wasn't.
RT-Smart required a fair amount of BSP work before I could get a stable userspace environment running on the D1.
NuttX initially looked easier. It reached the NSH shell quickly, which appeared to be a successful boot.
Then the board reset after roughly 20 seconds.
The problem turned out to be inherited hardware state.
U-Boot had left the Allwinner D1 watchdog running with an approximately 16-second timeout. NuttX did not initially disable it, so the operating system could boot successfully while still carrying a guaranteed reset condition from the bootloader.
That was the first useful reminder from the experiment:
A shell prompt does not mean board bring-up is complete.
The timer problem
Once the watchdog issue was fixed, timed sleeps still did not work correctly.
OpenSBI advertised support for the TIME extension and accepted the timer request, but the supervisor timer interrupt never arrived through the expected STIP path on the tested firmware setup.
Rather than modify the firmware, I moved NuttX to the native Allwinner D1 Timer1 peripheral.
That worked, but it exposed another problem.
The D1 uses a 24 MHz clock source.
For a 1 ms scheduler tick, the obvious value appears to be:
24,000 clocks.
So that is what I initially programmed.
It was wrong.
The timer counts down through zero before reloading, which meant programming 24,000 produced an effective period of 24,001 input clocks.
That is only about 41.7 ppm error.
It sounds insignificant.
But over a roughly 20-second benchmark the error accumulated to almost 0.84 ms.
That was large enough to invalidate the sub-microsecond latency measurements I was trying to make.
Changing the interval register to 23,999 produced the intended 24,000 input clocks per scheduler tick and removed the accumulated drift.
This became one of the more important findings from the whole project.
Tiny timer errors become significant when the measurement resolution gets small enough.
Measurement code can change the result
There was another issue.
During an early version of the benchmark I printed diagnostic information over UART at 115200 baud during the measurement window.
That output itself was enough to perturb the scheduling behaviour and create millisecond-scale apparent clock drift.
The final measurement interval therefore had to run silently.
It is easy to forget that the instrumentation used to observe a system can become part of the system you are measuring.
What did the benchmarks show?
Once the hardware timer and measurement methodology were corrected, the differences between the operating systems became clear.
For example, the loaded NuttX 1 ms periodic wake-up test produced:
p50: 0.542 µs
p99: 0.583 µs
max: 0.708 µs
missed periods: 0 / 20,000
Linux using SCHED_FIFO under load produced:
p50: 64.09 µs
p99: 76.51 µs
max: 228.72 µs
missed periods: 0
NuttX also showed very low synchronization and task-creation overhead in the tested configuration.
But there is an important qualification.
This does not mean NuttX is simply "100 times faster than Linux."
The tested NuttX build was FLAT shared-address-space S-mode.
Linux was providing protected processes, virtual memory, mature filesystem and networking support, process semantics and a much larger software ecosystem.
RT-Smart sat somewhere between the two. It provided process isolation and userspace while remaining significantly smaller than Linux.
The operating systems were paying for different things.
That is really what the benchmark demonstrated.
Linux was still faster in some areas
The experiment did not produce a simple progression where the smaller operating system always won.
Linux's mature userspace fast paths were extremely efficient.
An uncontended pthread mutex lock/unlock pair, for example, completed faster under Linux than under the tested NuttX configuration.
Linux also provided by far the broadest feature coverage.
The difference appeared when blocking, scheduling and task handoff entered the picture.
That is where the much smaller fixed-priority NuttX environment showed its advantage.
So which operating system won?
None of them.
That was never really the right question.
The useful question is what your application actually needs.
If you need protected processes, complex filesystems, networking, dynamic software, third-party packages and a mature development environment, Linux is extremely difficult to beat.
If deterministic scheduling, low synchronization latency and a small execution environment dominate the requirements, an RTOS may make much more sense.
RT-Smart was particularly interesting because it tries to occupy the middle ground. It provides MMU-backed userspace and process isolation while retaining a substantially smaller embedded architecture.
The benchmark numbers only make sense when you keep those architectural differences visible.
The project did not stop at benchmarking
Once the operating-system work was complete, I continued bringing up the rest of the Lichee RV / 86 Panel hardware.
The board now has:
stable 720×720 LCD output
capacitive touch input
XR829 Wi-Fi
framebuffer graphics
live system monitoring
a native RunTime touchscreen interface
I also built a second smart-home style interface to demonstrate what the panel could be used for as a physical embedded product.
The user can switch directly between the RunTime system dashboard and the Home interface using the touchscreen.
[INSERT RUN TIME / HOME GUI IMAGE HERE]
Watch the experiment
I recorded a video explaining why I ran the experiment, showing the hardware and discussing some of the problems and results.
Full experiment video:
I also recorded a separate demonstration of the working 720×720 touchscreen interface:
Source code and full report
The complete project is public.
GitHub repository:
https://github.com/lanceharvie/allwinner-d1-os-benchmark
The repository contains the RunTime Panel source, benchmark material, documentation, images and the v1.0.0 software release.
The complete 30-page engineering report is also published independently on Zenodo:
https://doi.org/10.5281/zenodo.22054539
The software release itself is archived separately:
https://doi.org/10.5281/zenodo.22054828
What I took away from it
The biggest lesson was not that one operating system was faster than another.
It was how easily the wrong conclusion can be reached.
A bootloader can leave hardware in a state the operating system never expected.
A timer can be wrong by one count.
Debug output can disturb the measurement.
Two operating systems can expose the same API while implementing completely different protection and scheduling models underneath it.
The benchmark number is only the end of the story.
Understanding how the system produced that number is the engineering work.
My next obvious experiment is PREEMPT_RT Linux on exactly the same hardware.
That should make the comparison considerably more interesting.