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.