[][src]Struct r3::kernel::Mutex

#[repr(transparent)]pub struct Mutex<System>(_, _);

Represents a single mutex in a system.

This type is ABI-compatible with Id.

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_fn)]
#![feature(const_mut_refs)]
use r3::kernel::{
    Kernel, LockMutexError, Mutex, MutexProtocol, Task, cfg::CfgBuilder,
};

struct Objects<System> {
    mutex: Mutex<System>,
}

const fn configure<System: Kernel>(b: &mut CfgBuilder<System>) -> Objects<System> {
    let mutex = Mutex::build()
        .protocol(MutexProtocol::Ceiling(1))
        .finish(b);
    Objects { mutex }
}

fn hoge<System: Kernel>(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 return Err(Abandoned) until Mutex::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 of std::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 CfgMutexBuilder::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), and PTHREAD_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.

SpecificationPIPCUnlock OrderLower Priority
ChibiOS/RTyesnolock-reverseimmediate
FreeRTOSyesnoarbitrarylast mutex
POSIXyesyesarbitraryimmediate
RTEMSyesyesarbitrarylast mutex
TOPPERS 3rd Gennoyeslock-reverseimmediate
TOPPERS Next Gennoyesarbitraryimmediate
VxWorksyesyesarbitrary?
μITRON4.0yesyesarbitrary
R3noyeslock-reverseimmediate
  • 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.

Implementations

impl<System: Port> Mutex<System>[src]

pub const fn build() -> CfgMutexBuilder<System>[src]

Construct a CfgTaskBuilder to define a mutex in a configuration function.

impl<System> Mutex<System>[src]

pub const unsafe fn from_id(id: Id) -> Self[src]

Construct a Mutex from Id.

Safety

The kernel can handle invalid IDs without a problem. However, the constructed Mutex may point to an object that is not intended to be manipulated except by its creator. This is usually prevented by making Mutex an opaque handle, but this safeguard can be circumvented by this method.

pub const fn id(self) -> Id[src]

Get the raw Id value representing this mutex.

impl<System: Kernel> Mutex<System>[src]

pub fn is_locked(self) -> Result<bool, QueryMutexError>[src]

Get a flag indicating whether the mutex is currently locked.

pub fn unlock(self) -> Result<(), UnlockMutexError>[src]

Unlock the mutex.

Mutexes must be unlocked in a lock-reverse order, or this method will return UnlockMutexError::BadObjectState.

pub fn lock(self) -> Result<(), LockMutexError>[src]

Acquire the mutex, blocking the current thread until it is able to do so.

An abandoned mutex can still be locked, but this method will return Err(Abandoned). Note that the current task will receive the ownership of the mutex even in this case.

This system service may block. Therefore, calling this method is not allowed in a non-waitable context and will return Err(BadContext).

pub fn lock_timeout(
    self,
    timeout: Duration
) -> Result<(), LockMutexTimeoutError>
[src]

lock with timeout.

pub fn try_lock(self) -> Result<(), TryLockMutexError>[src]

Non-blocking version of lock. Returns immediately with TryLockMutexError::Timeout if the unblocking condition is not satisfied.

Note that unlike Semaphore::poll_one, this operation is disallowed in a non-task context because a mutex lock needs an owning task.

pub fn mark_consistent(self) -> Result<(), MarkConsistentMutexError>[src]

Mark the state protected by the mutex as consistent.

Relation to Other Specifications: Equivalent to pthread_mutex_consistent from POSIX.1-2008.

Trait Implementations

impl<System> Clone for Mutex<System>[src]

impl<System> Copy for Mutex<System>[src]

impl<System> Debug for Mutex<System>[src]

impl<System> Eq for Mutex<System>[src]

impl<System> Hash for Mutex<System>[src]

impl<System> PartialEq<Mutex<System>> for Mutex<System>[src]

Auto Trait Implementations

impl<System> Send for Mutex<System> where
    System: Send

impl<System> Sync for Mutex<System> where
    System: Sync

impl<System> Unpin for Mutex<System> where
    System: Unpin

Blanket Implementations

impl<T> Any for T where
    T: 'static + ?Sized
[src]

impl<T> Borrow<T> for T where
    T: ?Sized
[src]

impl<T> BorrowMut<T> for T where
    T: ?Sized
[src]

impl<T> From<T> for T[src]

impl<T, U> Into<U> for T where
    U: From<T>, 
[src]

impl<T, U> TryFrom<U> for T where
    U: Into<T>, 
[src]

type Error = Infallible

The type returned in the event of a conversion error.

impl<T, U> TryInto<U> for T where
    U: TryFrom<T>, 
[src]

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.