Mutex objects

Mutexes are synchronization objects used to manage access to critical sections. Code protected by a critical section and controlled by a mutex ensures that no more than one task can execute that section at any given time.

Mutex creation and deletion

The osCreateMutex function is used to create a mutex object. Mutexes can be created prior to system startup, from within an interrupt handler, or by a running task. During creation, an optional name may be assigned to the mutex, which allows other tasks to locate and open it using the osOpenMutex function. Both creating and opening a mutex returns a handle, which is assigned by the system to uniquely identify the object. All subsequent operations on the mutex require this handle. When a mutex is no longer required, it should be closed using osCloseHandle. A mutex is deleted from the system only after it has been closed by all tasks that held an open handle. For further details, refer to the system objects management section.

If mutexes are not required by the application, the OS_USE_MUTEX constant can be set to 0 to reduce the memory footprint of the output code.

Mutex ownership

A mutex is in a non-signaled state when it is owned by a task. For the task that currently owns the mutex, the object is always considered signaled, allowing the owner to acquire it multiple times (recursive acquisition). Only one task can own a mutex at any given time, ensuring that the code protected by the mutex is executed exclusively by that task.

A task can acquire ownership of a mutex in two ways: by setting the InitialOwner parameter of the osCreateMutex function to TRUE during creation, or by calling a wait operation (osWaitForObject or osWaitForObjects). To exit the critical section, the owner must release the mutex using the osReleaseMutex function, at which point the mutex becomes signaled. If the scheduler selects a task from the pending queue that is waiting for the mutex, that task becomes the new owner. If a higher-priority task attempts to acquire the mutex immediately after it is released, it will become the owner instantly.

An error condition occurs if a task owning a mutex is terminated or if osCloseHandle is called for an owned critical section. This suggests that the resource protected by the mutex may be in an inconsistent state (e.g., a multi-step operation was interrupted). In such cases, the kernel automatically releases the mutex and marks it as abandoned. If another task subsequently acquires this abandoned mutex, the wait operation will return a failure, and the last error code will be set to ERR_WAIT_ABANDON. While this situation is treated as an error, it provides the acquiring task with the information necessary to perform data recovery if possible.

Another critical concern is priority inversion. This occurs when a low-priority task (L) owns a critical section and a medium-priority task (M) becomes ready, preempting task L. If a high-priority task (H) then becomes ready and attempts to acquire the same critical section, it will be blocked by task L. In this scenario, task M effectively prevents task H from running. To resolve this, the kernel utilizes a priority inheritance algorithm: it immediately increases the priority of the owner (task L) to match the highest priority among the tasks waiting for the mutex (task H). This allows task L to finish its operation and release the mutex as quickly as possible. Once released, task L's original priority is restored, and task H proceeds to acquire the mutex.

In complex system designs, priority inversion can occur across a chain of multiple tasks owning various critical sections. In such cases, the kernel must spend linear time updating the priorities of all tasks in the chain. This can be detrimental to real-time performance and should be avoided through careful system architecture.

Improper system design can also lead to deadlocks. Sirius RTOS is capable of detecting deadlocks occurring within critical sections controlled by mutexes and semaphores. If a deadlock is detected, the wait operation will terminate, and the last error code will be set to ERR_WAIT_DEADLOCK.

When multiple tasks are waiting for a mutex, the highest-priority task will be the first to acquire it. If all waiting tasks have a lower priority than the task that just released the mutex, they will be granted access as they become ready. However, if a higher-priority task (including the task that just released the mutex) begins waiting, it will take precedence over the others. Tasks of equal or lower priority are appended to the end of the pending queue.

Mutexes should be utilized specifically when priority inversion protection is required. While well-designed applications should ideally be structured to prevent priority inversion entirely, if it is certain that inversion will not occur, using auto-reset events is recommended, as they provide similar synchronization with less overhead.

SpaceShadow documentation