In GeMRTOS on Altera FPGA platforms with Nios V processors, newlib functions are reentrant — but reentrancy does not guarantee thread safety. In uniprocessor multiprocessing systems, _impure_ptr is used to give newlib functions per-task reentrancy state, making them effectively thread-safe through context switching. However, in multiprocessor systems, this approach breaks down: multiple tasks can execute simultaneously on different processors, so changing _impure_ptr per task is no longer valid. This article explains the distinction and the GeMRTOS solution.
Reentrancy vs Thread Safety in newlib #
Reentrancy means a function can be interrupted mid-execution and safely called again before the first call completes — typically by storing all state in caller-provided or per-call structures rather than global variables. newlib achieves reentrancy by storing per-task state in a struct _reent structure pointed to by _impure_ptr.
Thread safety is a stricter guarantee: a function can be called concurrently by multiple threads or tasks running at the same time without corrupting shared state. In a uniprocessor RTOS, tasks never truly run simultaneously — the scheduler switches _impure_ptr to each task’s own struct _reent during context switches, making newlib calls effectively thread-safe by serialization.
Why _impure_ptr Fails in Multiprocessor Systems #
In a multiprocessor system, two or more tasks can execute simultaneously on separate processors. If both tasks call a newlib function at the same time, both access _impure_ptr concurrently. Since _impure_ptr is a single global pointer, there is no guarantee that each processor is referencing its own task’s struct _reent — a context switch on one processor can change _impure_ptr while another processor is mid-execution of a newlib call that depends on it. This leads to corrupted reentrancy state and unpredictable behaviour.
The root problem is that the uniprocessor assumption — only one task runs at a time — no longer holds. Serializing _impure_ptr switches through context switching is insufficient when multiple processors execute tasks in parallel.
The GeMRTOS Solution: Hardware Mutex #
GeMRTOS provides a hardware mutex — GRTOS_USER_CRITICAL_SECTION_GET and GRTOS_USER_CRITICAL_SECTION_RELEASE — that enforces mutual exclusion across all processors at the hardware level. When a task needs to call a newlib function that is not multiprocessor-safe, it should bracket the call with the GeMRTOS mutex to prevent concurrent access:
- Acquire the GeMRTOS mutex with
GRTOS_USER_CRITICAL_SECTION_GET. - Execute the newlib function call.
- Release the mutex with
GRTOS_USER_CRITICAL_SECTION_RELEASE.
Important: while the GeMRTOS mutex is held, all GeMRTOS kernel functions are suspended until it is released. Avoid holding the mutex while accessing slow I/O devices or performing lengthy operations, as this will block the entire RTOS kernel across all processors for the duration.
Key Takeaways #
- Reentrancy ≠ thread safety: newlib functions are reentrant via
_impure_ptr/struct _reent, but this only provides thread safety when tasks are serialized by a uniprocessor scheduler. - In a GeMRTOS multiprocessor Nios V system, multiple tasks run simultaneously on separate processors — changing
_impure_ptrper task during context switches no longer prevents concurrent access. - The GeMRTOS hardware mutex (GRTOS_USER_CRITICAL_SECTION_GET / RELEASE) is the correct mechanism for making newlib calls safe across all processors.
- Keep mutex hold-time minimal — the entire GeMRTOS kernel is suspended while the hardware mutex is held, so it must not wrap slow I/O operations.
- This issue applies to any library or code that relies on global or shared state; the same mutex pattern should be used for any non-multiprocessor-safe function called from GeMRTOS tasks.
I think the above is not correct? newlib has a poorly documented feature… Build newlib adding -D__DYNAMIC_REENT__ to CFLAGS build options), and Newlib dynamically obtains `_REENT` through `__getreent()` instead of using a single global `-impure_ptr`. In turn, `__getreent()` must be implemented to return the appropriate reentrancy structure for the current processor, task management must allocate it, and maintain thread-specific reentrancy pointers during task switching etc. You should get better performance using this feature than a hardware mutex perhaps blocking for a long time…
Would appreciate a reply at DRN@Nadler.com, Thanks!
Best Regards, Dave
Hi Dave,
Please accept my apologies for the delayed reply. In the GeMRTOS implementation, we have evaluated Newlib’s reent structure, but not as a replacement for the mutex. The mutex serves a different purpose: preserving the consistency of the operating system while it is running.
There were two main reasons we decided not to adapt Newlib’s reent mechanism:
1) Newlib’s dynamic reentrancy support is primarily intended for multithreaded, single-processor systems. Adapting it for a multiprocessor system would require substantial work, which might need to be repeated with each new Newlib version.
2) Any such modification would require rebuilding Newlib, which could make the application development workflow considerably more cumbersome.
If these circumstances change, we will reconsider using this feature.
Thank you for reaching out. We appreciate your message and remain at your disposal.
Kind regards,
Ricardo Cayssials
GeMRTOS