EmbeddedRelated.com

Critical Section

Category: Rtos | Also known as: CRITICAL section

A critical section is a region of code that must execute with mutual exclusion with respect to other concurrent activities that share the same resources -- meaning no other task, thread, or interrupt handler that accesses that shared state may interleave while the critical section is in progress. Protecting a critical section prevents data corruption and race conditions caused by interleaved access to shared state.

In practice

In bare-metal embedded systems, the most common way to implement a critical section is to disable interrupts before entering the shared-resource code and re-enable them on exit. On ARM Cortex-M cores, this typically involves saving and restoring the interrupt-enable state around the protected region -- for example, by reading and restoring PRIMASK or by using compiler intrinsics or RTOS-provided helpers. Some targets offer finer-grained masking: Cortex-M3/M4/M7 support BASEPRI, which can mask only interrupts below a certain priority, allowing high-priority ISRs to remain active. On other architectures -- such as AVR or MSP430 -- a global interrupt-enable bit is cleared and restored in a similar pattern.

In RTOS-based designs, disabling interrupts for long periods defeats real-time guarantees, so dedicated synchronization primitives are preferred. Mutexes protect critical sections between tasks by having the scheduler block any task that tries to enter while another holds the lock. Spinlocks are used in multicore environments where busy-waiting for a short hold time may be cheaper than a context switch. Binary semaphores are sometimes used for the same purpose, though they lack the priority-inheritance that most RTOS mutexes provide, making them more prone to priority inversion.

A critical section should be kept as short as possible. Long critical sections increase interrupt latency (if interrupts are disabled) or reduce scheduling responsiveness (if a mutex is held). A common pitfall is accidentally returning or breaking out of a critical section without re-enabling interrupts or releasing the lock, leaving the system in a permanently protected -- and effectively frozen -- state. Macros or RAII-style wrappers (in C++) are frequently used to make entry and exit symmetric and less error-prone.

The blog post "Scorchers, Part 3: Bare-Metal Concurrency With Double-Buffering and the Revolving Fireplace" discusses practical bare-metal concurrency patterns where minimizing or avoiding critical sections entirely -- through careful data structure design such as double-buffering -- can improve real-time behavior while still preventing data corruption.

Frequently asked

What is the difference between a critical section and a mutex?
A critical section is a concept: a region of code that requires exclusive access. A mutex is one mechanism for implementing that protection between tasks in an RTOS. On bare-metal systems, disabling interrupts is the more common mechanism. In some RTOS APIs (notably Win32), 'critical section' is also the name of a specific lightweight mutex-like object, which can add to the confusion.
Is it safe to call RTOS API functions from inside a critical section that disables interrupts?
Generally no. Many RTOS kernels (including FreeRTOS and Zephyr in typical configurations) rely on interrupts for scheduling and tick handling. Calling blocking RTOS functions with interrupts disabled can corrupt kernel state or cause a deadlock. Many RTOSes provide separate 'ISR-safe' API variants and specific mechanisms for managing critical section nesting. Always check your RTOS documentation before mixing interrupt masking with kernel calls.
How long is 'too long' for a critical section?
It depends on your worst-case interrupt latency budget. If your system must respond to an interrupt within 10 microseconds, no critical section that disables interrupts can exceed that. A practical rule: keep interrupt-disabling critical sections to the minimum number of instructions needed to read or write the shared variable -- often just a handful of cycles. Mutex-based critical sections between tasks can be longer, but holding a mutex across blocking I/O or delays is a design smell.
Can critical sections be nested?
They can be, if the implementation saves and restores the interrupt-enable state rather than unconditionally re-enabling interrupts on exit. A naive implementation that calls 'enable interrupts' on every exit will prematurely re-enable interrupts when unwinding from a nested entry. Correct nesting usually involves saving the previous interrupt state into a local variable on entry and restoring that exact state on exit, or maintaining a nesting counter.
Do critical sections work on multicore MCUs?
Disabling interrupts on one core does not prevent another core from accessing shared memory simultaneously, so interrupt masking alone is insufficient on multicore SoCs (such as the dual-core RP2040 or multicore Cortex-A/M systems). True mutual exclusion across cores requires hardware-supported atomic operations or spinlocks, typically built on load-exclusive/store-exclusive instructions (such as LDREX/STREX on many ARM cores) or equivalent mechanisms provided by the architecture. RTOS-provided mutexes on multicore targets usually handle this transparently.

Differentiators vs similar concepts

A critical section is often confused with a mutex, a semaphore, and an atomic operation. A mutex is a specific RTOS primitive that can implement a critical section between tasks, but it uses scheduler-level blocking rather than interrupt masking. A binary semaphore can guard a critical section but lacks priority inheritance, making it more vulnerable to priority inversion. An atomic operation (e.g., a single aligned 32-bit read or write on a Cortex-M) is inherently safe without any critical-section wrapper -- but only for that single operation; a critical section is needed when multiple reads and writes must appear as a unit. A spinlock is another mechanism that protects a critical section by busy-waiting rather than yielding, typically used between cores on multicore hardware.