Linux kernel primitive -- mutex

Posted by jpearson on Wed, 09 Feb 2022 20:55:44 +0100

 

Linux kernel primitive (IX) -- mutex

Wolf@ http://blog.csdn.net/xiaolangyangyang

 

Mutex is a sleep lock. It is a simple sleep lock. Its behavior is similar to that of a semaphore with count of 1. (for semaphore reference: Linux kernel primitive (8) -- semaphore).

Mutexes are simple and efficient, but they have more restrictions than semaphores. Therefore, the use conditions of mutexes are more strict:

  • At any time, only one specified task is allowed to hold mutex, that is, the count of mutex is always 1;
  • To lock mutex, you must be responsible for unlocking it, that is, it is not allowed to lock in one context and unlock in another. This limitation doomed mutex to be unable to bear the complex scenario of kernel and user space synchronization. A common way is to lock / unlock in one context.
  • Recursive call locking and unlocking are not allowed. Recursion can't unlock the same lock, that is to say, recursion can't unlock the same lock.
  • When a mutex process is held, it is not allowed to exit
  • Mutex is not allowed to be used in interrupt context and soft interrupt context, even mutex_ Neither can trylock
  • mutex can only use the APIs provided by the kernel. Copying, manual initialization and repeated initialization are not allowed

 
Semaphores and mutexes

They are very similar. Mutex is preferred between the two unless the restriction of mutex hinders logic

 

Spinlocks and mutexes

In most cases, it's easy to distinguish. Only spin lock can be considered in interrupt, and task sleep uses mutex. If you can, choose spin lock for low overhead or short-term lock, and use mutex for long-term lock.

 

Use of mutexes

Function definition and function description
mutex_lock(struct mutex *lock) is locked. If it is not available, sleep (UNINTERRUPTIBLE)
mutex_lock_interruptible(struct mutex *lock); Lock, if not available, sleep (task_intermittent)
mutex_unlock(struct mutex *lock)
mutex_trylock(struct mutex *lock) attempts to get the specified mutex, or returns 1, otherwise returns 0
mutex_is_locked(struct mutex *lock) returns 1 if mutex is occupied, otherwise 0
 

Implementation of mutex

Mutex is defined in: Include / Linux / mutex h

1. Structure of mutex

struct mutex {
    atomic_long_t        owner;
    spinlock_t        wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
    struct list_head    wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
    void            *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map    dep_map;
#endif
};

2. mutex initialization

void
__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
{
    atomic_long_set(&lock->owner, 0);
    spin_lock_init(&lock->wait_lock);
    INIT_LIST_HEAD(&lock->wait_list);
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    osq_lock_init(&lock->osq);
#endif
 
    debug_mutex_init(lock, name, key);
}
EXPORT_SYMBOL(__mutex_init);

3. mutex locking

void __sched mutex_lock(struct mutex *lock)
{
    might_sleep();
 
    if (!__mutex_trylock_fast(lock))
        __mutex_lock_slowpath(lock);
}

First, check whether the lock can be obtained. Otherwise, call__ mutex_lock_slowpath:

static noinline void __sched
__mutex_lock_slowpath(struct mutex *lock)
{
    __mutex_lock(lock, TASK_UNINTERRUPTIBLE, 0, NULL, _RET_IP_);
}
static int __sched
__mutex_lock(struct mutex *lock, long state, unsigned int subclass,
         struct lockdep_map *nest_lock, unsigned long ip)
{
    return __mutex_lock_common(lock, state, subclass, nest_lock, ip, NULL, false);
}

So it's called__ mutex_lock_common function:

static __always_inline int __sched
__mutex_lock_common(struct mutex *lock, long state, unsigned int subclass,
            struct lockdep_map *nest_lock, unsigned long ip,
            struct ww_acquire_ctx *ww_ctx, const bool use_ww_ctx)
{
    struct mutex_waiter waiter;
    bool first = false;
    struct ww_mutex *ww;
    int ret;
 
    might_sleep();
 
    ww = container_of(lock, struct ww_mutex, base);
    if (use_ww_ctx && ww_ctx) {
        if (unlikely(ww_ctx == READ_ONCE(ww->ctx)))
            return -EALREADY;
 
        /*
         * Reset the wounded flag after a kill. No other process can
         * race and wound us here since they can't have a valid owner
         * pointer if we don't have any locks held.
         */
        if (ww_ctx->acquired == 0)
            ww_ctx->wounded = 0;
    }
 
    preempt_disable();
    mutex_acquire_nest(&lock->dep_map, subclass, 0, nest_lock, ip);
 
    if (__mutex_trylock(lock) ||
        mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, NULL)) {
        /* got the lock, yay! */
        lock_acquired(&lock->dep_map, ip);
        if (use_ww_ctx && ww_ctx)
            ww_mutex_set_context_fastpath(ww, ww_ctx);
        preempt_enable();
        return 0;
    }
 
    spin_lock(&lock->wait_lock);
    /*
     * After waiting to acquire the wait_lock, try again.
     */
    if (__mutex_trylock(lock)) {
        if (use_ww_ctx && ww_ctx)
            __ww_mutex_check_waiters(lock, ww_ctx);
 
        goto skip_wait;
    }
 
    debug_mutex_lock_common(lock, &waiter);
 
    lock_contended(&lock->dep_map, ip);
 
    if (!use_ww_ctx) {
        /* add waiting tasks to the end of the waitqueue (FIFO): */
        __mutex_add_waiter(lock, &waiter, &lock->wait_list);
 
 
#ifdef CONFIG_DEBUG_MUTEXES
        waiter.ww_ctx = MUTEX_POISON_WW_CTX;
#endif
    } else {
        /*
         * Add in stamp order, waking up waiters that must kill
         * themselves.
         */
        ret = __ww_mutex_add_waiter(&waiter, lock, ww_ctx);
        if (ret)
            goto err_early_kill;
 
        waiter.ww_ctx = ww_ctx;
    }
 
    waiter.task = current;
 
    set_current_state(state);
    for (;;) {
        /*
         * Once we hold wait_lock, we're serialized against
         * mutex_unlock() handing the lock off to us, do a trylock
         * before testing the error conditions to make sure we pick up
         * the handoff.
         */
        if (__mutex_trylock(lock))
            goto acquired;
 
        /*
         * Check for signals and kill conditions while holding
         * wait_lock. This ensures the lock cancellation is ordered
         * against mutex_unlock() and wake-ups do not go missing.
         */
        if (unlikely(signal_pending_state(state, current))) {
            ret = -EINTR;
            goto err;
        }
 
        if (use_ww_ctx && ww_ctx) {
            ret = __ww_mutex_check_kill(lock, &waiter, ww_ctx);
            if (ret)
                goto err;
        }
 
        spin_unlock(&lock->wait_lock);
        schedule_preempt_disabled();
 
        /*
         * ww_mutex needs to always recheck its position since its waiter
         * list is not FIFO ordered.
         */
        if ((use_ww_ctx && ww_ctx) || !first) {
            first = __mutex_waiter_is_first(lock, &waiter);
            if (first)
                __mutex_set_flag(lock, MUTEX_FLAG_HANDOFF);
        }
 
        set_current_state(state);
        /*
         * Here we order against unlock; we must either see it change
         * state back to RUNNING and fall through the next schedule(),
         * or we must see its unlock and acquire.
         */
        if (__mutex_trylock(lock) ||
            (first && mutex_optimistic_spin(lock, ww_ctx, use_ww_ctx, &waiter)))
            break;
 
        spin_lock(&lock->wait_lock);
    }
    spin_lock(&lock->wait_lock);
acquired:
    __set_current_state(TASK_RUNNING);
 
    if (use_ww_ctx && ww_ctx) {
        /*
         * Wound-Wait; we stole the lock (!first_waiter), check the
         * waiters as anyone might want to wound us.
         */
        if (!ww_ctx->is_wait_die &&
            !__mutex_waiter_is_first(lock, &waiter))
            __ww_mutex_check_waiters(lock, ww_ctx);
    }
 
    mutex_remove_waiter(lock, &waiter, current);
    if (likely(list_empty(&lock->wait_list)))
        __mutex_clear_flag(lock, MUTEX_FLAGS);
 
    debug_mutex_free_waiter(&waiter);
 
skip_wait:
    /* got the lock - cleanup and rejoice! */
    lock_acquired(&lock->dep_map, ip);
 
    if (use_ww_ctx && ww_ctx)
        ww_mutex_lock_acquired(ww, ww_ctx);
 
    spin_unlock(&lock->wait_lock);
    preempt_enable();
    return 0;
 
err:
    __set_current_state(TASK_RUNNING);
    mutex_remove_waiter(lock, &waiter, current);
err_early_kill:
    spin_unlock(&lock->wait_lock);
    debug_mutex_free_waiter(&waiter);
    mutex_release(&lock->dep_map, 1, ip);
    preempt_enable();
    return ret;
}

Enter the waiting queue.

 

4. mutex unlock

void __sched mutex_unlock(struct mutex *lock)
{
#ifndef CONFIG_DEBUG_LOCK_ALLOC
    if (__mutex_unlock_fast(lock))
        return;
#endif
    __mutex_unlock_slowpath(lock, _RET_IP_);
}
EXPORT_SYMBOL(mutex_unlock);

Call to__ mutex_unlock_slowpath :

static noinline void __sched __mutex_unlock_slowpath(struct mutex *lock, unsigned long ip)
{
    struct task_struct *next = NULL;
    DEFINE_WAKE_Q(wake_q);
    unsigned long owner;
 
    mutex_release(&lock->dep_map, 1, ip);
 
    /*
     * Release the lock before (potentially) taking the spinlock such that
     * other contenders can get on with things ASAP.
     *
     * Except when HANDOFF, in that case we must not clear the owner field,
     * but instead set it to the top waiter.
     */
    owner = atomic_long_read(&lock->owner);
    for (;;) {
        unsigned long old;
 
#ifdef CONFIG_DEBUG_MUTEXES
        DEBUG_LOCKS_WARN_ON(__owner_task(owner) != current);
        DEBUG_LOCKS_WARN_ON(owner & MUTEX_FLAG_PICKUP);
#endif
 
        if (owner & MUTEX_FLAG_HANDOFF)
            break;
 
        old = atomic_long_cmpxchg_release(&lock->owner, owner,
                          __owner_flags(owner));
        if (old == owner) {
            if (owner & MUTEX_FLAG_WAITERS)
                break;
 
            return;
        }
 
        owner = old;
    }
 
    spin_lock(&lock->wait_lock);
    debug_mutex_unlock(lock);
    if (!list_empty(&lock->wait_list)) {
        /* get the first entry from the wait-list: */
        struct mutex_waiter *waiter =
            list_first_entry(&lock->wait_list,
                     struct mutex_waiter, list);
 
        next = waiter->task;
 
        debug_mutex_wake_waiter(lock, waiter);
        wake_q_add(&wake_q, next);
    }
 
    if (owner & MUTEX_FLAG_HANDOFF)
        __mutex_handoff(lock, next);
 
    spin_unlock(&lock->wait_lock);
 
    wake_up_q(&wake_q);
}

Do wake-up operation.