#[repr(transparent)]pub struct Mutex<System: NotSupportedYet>(_);
Expand description
Represents a single mutex in a system.
This type is ABI-compatible with System::
RawMutexId
.
See MutexRef
for the borrowed counterpart.
See MutexMethods
for the operations provided by this handle
type.
Mutexes are similar to binary semaphores (semaphores restricted to one permit at maximum) but differ in some ways, such as the inclusion of a mechanism for preventing unbounded priority inversion.
When a mutex is locked, it is considered to be owned by the task while the lock is held and can only be unlocked by the same task. This also means that a mutex cannot be locked (even with a non-blocking operation) in a non-task context, where there is no task to hold the mutex.
See r3::sync::mutex
for a thread-safe container that uses this
Mutex
internally to protect shared data from concurrent access.
Relation to Other Specifications: Present in many general-purpose and real-time operating systems.
Examples
#![feature(const_trait_impl)]
#![feature(const_mut_refs)]
use r3_core::kernel::{
LockMutexError, StaticMutex, MutexProtocol, Cfg, traits, prelude::*,
};
struct Objects<System: traits::KernelMutex> {
mutex: StaticMutex<System>,
}
const fn configure<C>(cfg: &mut Cfg<C>) -> Objects<C::System>
where
C: ~const traits::CfgMutex,
{
let mutex = StaticMutex::define()
.protocol(MutexProtocol::Ceiling(1))
.finish(cfg);
Objects { mutex }
}
fn hoge<System: traits::KernelMutex>(app: &Objects<System>) {
match app.mutex.lock() {
Ok(()) => {},
Err(LockMutexError::Abandoned) => {
app.mutex.mark_consistent().unwrap();
}
Err(e) => panic!("failed to lock the mutex: {e:?}"),
}
app.mutex.unlock().unwrap();
}
Robustness
If a task exits while holding a mutex, the mutex is considered to be
abandoned. An abandoned mutex can still be locked, but the lock function
will return Err(Abandoned)
. Note that the calling task will receive the
ownership of the mutex in this case. The abandonment state will last until
Mutex::mark_consistent
is called on the mutex.
When a task exits while holding more than one mutex, the order in which the mutexes are abandoned is not specified.
Relation to Other Specifications
This behavior is based on robust mutexes from POSIX.1-2008 (
PTHREAD_MUTEX_ROBUST
) with one difference: A mutex never falls into an irrecoverable state —Mutex::lock
would repeatedly returnErr(Abandoned)
untilMutex::mark_consistent
is called. This change reduces the internal state bits and the complexity of the internal logic not to punish normal usage too much. It also loosely imitates the poisoning semantics ofstd::sync::Mutex
.A Win32 mutex incorporates a flag indicating if the mutex has been abandoned. An abandoned mutex can be locked as usual, but the wait function will return
WAIT_ABANDONED
. The flag is cleared automatically, i.e., unlike POSIX, an abandoned mutex doesn’t have to be explicitly marked consistent.In μITRON4.0 and μT-Kernel, abandoned mutexes are implicitly unlocked.
All of the other operating systems’ behavior described above can be emulated by having a per-mutex flag and performing additional tasks in the API translation layer.
Rationale
Every customization option brings an additional overhead. The overhead introduced by the robustness is likely to outweigh the overhead to provide choices. Therefore, we decided not to add an attribute to control the robustness.
We desired a predictable behavior in as many cases a possible, which excludes the option of leaving the behavior undefined. Failing to unlock a mutex usually indicates a serious programming error. A future version of R3 might include functionality to terminate an arbitrary task, e.g., to respond to a fatal condition such as panicking and a bus error by containing the fault to the faulting task. In these cases, the data protected by an abandoned mutex may be left in an inconsistent state and should be restored to a consistent state before it can be safely accessed again. To ensure this recommendation is followed correctly (unless explicitly opted out), we decided to make the robustness the default behavior.
Locking Protocols
Mutex
supports the immediate priority ceiling protocol to avoid
unbounded priority inversion.
A locking protocol can be chosen by MutexDefiner::protocol
.
Additional information can be found at MutexProtocol
.
Relation to Other Specifications
POSIX supports specifying a locking protocol by
pthread_mutexattr_setprotocol
. The following protocols are supported:PTHREAD_PRIO_NONE
(none),PTHREAD_PRIO_INHERIT
(the priority inheritance protocol), andPTHREAD_PRIO_PROTECT
(the immediate priority ceiling protocol).μITRON4.0 supports both the priority inheritance protocol and the immediate priority ceiling protocol. It permits an implementation to adhere to the simplified priority control rule, which lowers a task’s effective priority only when the task unlocks the last mutex lock held by the task.
Mutexes in ChibiOS/RT implements the priority inheritance protocol. Unlock operations must always be performed in lock-reverse order. This restriction is required for an efficient implementation of the priority inheritance protocol.
Mutexes in the TOPPERS next generation and third generation kernels only support the immediate priority ceiling protocol. The third generation kernels further restrict the unlock order to be a lock-reverse order.
The following table summaries the properties of mutexes in each operating system or operating system specification.
Specification PI PC Unlock Order Lower Priority ChibiOS/RT yes no lock-reverse immediate FreeRTOS yes no arbitrary last mutex POSIX yes yes arbitrary immediate RTEMS yes yes arbitrary last mutex TOPPERS 3rd Gen no yes lock-reverse immediate TOPPERS Next Gen no yes arbitrary immediate VxWorks yes yes arbitrary ? μITRON4.0 yes yes arbitrary R3 no yes lock-reverse immediate
The PI column indicates the availability of the priority inheritance protocol.
The PC column indicates the availability of the priority ceiling protocol.
The Unlock Order column indicates any restrictions imposed on the unlocking order.
The Lower Priority column indicates whether an owning task’s priority may be lowered whenever it unlocks a mutex or only when it unlocks the last mutex held.
Rationale
There are numerous reasons that led to the decision not to implement the priority inheritance protocol.
We couldn’t afford time to implement and test both protocols at this time. The entire project is at a prototyping stage, so we would better implement the other one when there is an actual need for it.
There are many arguments against using the priority inheritance protocol in real-time systems, although they are somewhat out-dated.
Victor Yodaiken. “Against priority inheritance.” (2004):
The RTLinux core does not support priority inheritance for a simple reason: priority inheritance is incompatible with reliable real-time system design. Priority inheritance is neither efficient nor reliable. Implementations are either incomplete (and unreliable) or surprisingly complex and intrusive. In fact, the original academic paper presenting priority inheritance [3] specifies (and “proves correct”) an inheritance algorithm that is wrong. Worse, the basic intent of the mechanism is to compensate for writing real-time software without taking care of the interaction between priority and mutual exclusion. All too often the result will be incorrect software with errors that are hard to find during test.
Inheritance algorithms are complicated and easy to get wrong. In practice putting priority inheritance into an operating system increases the inversion delays produced by the operating system.
The VxWorks designers originally tried to evade the issue by having a thread retain its highest inherited priority until it released all locks — but this can cause unbounded inversion.
Uresh Vahalia. Unix Internals: The New Frontiers. Prentice-Hall, 1996:
Priority inheritance reduces the amount of time a high-priority process must block on resources held by lower-priority processes. The worst-case delay, however, is still much greater than what is acceptable for many real-time applications. One reason is that the blocking chain can grow arbitrarily long.
We decided to restrict the unlocking order to a lock-reverse order to minimize the cost of maintaining the list of mutexes held by a task.
Trait Implementations§
source§impl<System: NotSupportedYet> MutexHandle for Mutex<System>
impl<System: NotSupportedYet> MutexHandle for Mutex<System>
source§const unsafe fn from_id(id: <System as KernelMutex>::RawMutexId) -> Self
const unsafe fn from_id(id: <System as KernelMutex>::RawMutexId) -> Self
source§const fn id(&self) -> System::RawMutexId
const fn id(&self) -> System::RawMutexId
RawMutexId
value representing this object.source§impl<System: NotSupportedYet> PartialEq<Mutex<System>> for Mutex<System>
impl<System: NotSupportedYet> PartialEq<Mutex<System>> for Mutex<System>
source§impl<System: NotSupportedYet> PartialEq<Mutex<System>> for MutexRef<'_, System>
impl<System: NotSupportedYet> PartialEq<Mutex<System>> for MutexRef<'_, System>
source§impl<System: NotSupportedYet> PartialEq<MutexRef<'_, System>> for Mutex<System>
impl<System: NotSupportedYet> PartialEq<MutexRef<'_, System>> for Mutex<System>
impl<System: NotSupportedYet> Eq for Mutex<System>
Auto Trait Implementations§
impl<System> RefUnwindSafe for Mutex<System>where <System as KernelMutex>::RawMutexId: RefUnwindSafe,
impl<System> Send for Mutex<System>
impl<System> Sync for Mutex<System>
impl<System> Unpin for Mutex<System>where <System as KernelMutex>::RawMutexId: Unpin,
impl<System> UnwindSafe for Mutex<System>where <System as KernelMutex>::RawMutexId: UnwindSafe,
Blanket Implementations§
source§impl<T> MutexMethods for Twhere
T: MutexHandle,
impl<T> MutexMethods for Twhere T: MutexHandle,
source§fn is_locked(&self) -> Result<bool, QueryMutexError>
fn is_locked(&self) -> Result<bool, QueryMutexError>
source§fn lock(&self) -> Result<(), LockMutexError>
fn lock(&self) -> Result<(), LockMutexError>
source§fn lock_timeout(&self, timeout: Duration) -> Result<(), LockMutexTimeoutError>
fn lock_timeout(&self, timeout: Duration) -> Result<(), LockMutexTimeoutError>
lock
with timeout.source§fn try_lock(&self) -> Result<(), TryLockMutexError>
fn try_lock(&self) -> Result<(), TryLockMutexError>
lock
. Returns
immediately with TryLockMutexError::Timeout
if the unblocking
condition is not satisfied. Read more