同步-互斥量

使用

API

#define K_MUTEX_DEFINE(name)

  • 作用:定义一个k_mutex,并初始化

  • name:k_mutex name

int k_mutex_init(struct k_mutex *mutex)

  • 作用: 初始化mutex

  • mutex: 要初始化的mutex

  • 返回值: 0标示初始化成功

int k_mutex_lock(struct k_mutex *mutex, s32_t timeout)

  • 作用:加锁

  • mutex: 加锁的互斥量

  • timeout: 等待时间,单位ms。K_NO_WAIT不等待,K_FOREVER一直等

  • 返回值: 0表示加锁成功

int k_mutex_unlock(struct k_mutex *mutex)

  • 作用:解锁

  • mutex:解锁的互斥量

  • 返回值: 0表示解锁成功,-EPERM表示当前thread并不拥有这个互斥量,-EINVAL表示该互斥量没有被锁

使用说明

使用互斥量完成对资源的独占访问。 Mutex不能在ISR内使用。

初始化

先初始化一个互斥量,下面两种方式的效果是一样的

方法1,使用宏

K_MUTEX_DEFINE(my_mutex);

方法2,使用函数

struct k_mutex my_mutex;
k_mutex_init(&my_mutex);

资源独占访问

下列示例代码中Thread A和B都要去访问一个IO资源,但同一时间IO只能被独占访问,因此使用互斥量包含

thread_A()
{
    k_mutex_lock(&my_mutex, K_FOREVER);

    //Read IO
    ...

    k_mutex_unlock(&my_mutex);
}

thread_b()
{
    k_mutex_lock(&my_mutex, K_FOREVER);

    //Write IO
    ...

    k_mutex_unlock(&my_mutex);
}

实现

k_mutex结构体如下,可以看出其基本实现是用的wait_q

struct k_mutex {
    /** Mutex wait queue */
    _wait_q_t wait_q;
    /** Mutex owner */
    struct k_thread *owner; //表示该mutex目前属于哪个线程

    /** Current lock count */
    u32_t lock_count;       //可重入锁用

    /** Original thread priority */
    int owner_orig_prio;    //优先级倒置用
};

可重入锁是指如果一个线程已经拥有了互斥量,那么该线程可以继续多次对该互斥量加锁,同时也要做对应次数的解锁,才能完全释放该互斥量

初始化

k_mutex_init->z_impl_k_mutex_init,详细分析见注释

int z_impl_k_mutex_init(struct k_mutex *mutex)
{
    mutex->owner = NULL;        //全新的mutex是无owner的
    mutex->lock_count = 0U;     //次数也未加锁

    z_waitq_init(&mutex->wait_q);

    z_object_init(mutex);

    return 0;
}

用宏也可以进行初始化

#define _K_MUTEX_INITIALIZER(obj) \
    { \
    .wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \     //等同于z_waitq_init
    .owner = NULL, \
    .lock_count = 0, \
    .owner_orig_prio = K_LOWEST_THREAD_PRIO, \
    _OBJECT_TRACING_INIT \
    }

#define K_MUTEX_DEFINE(name) \
    Z_STRUCT_SECTION_ITERABLE(k_mutex, name) = \
        _K_MUTEX_INITIALIZER(name)

加锁

k_mutex_lock -> z_impl_k_mutex_unlock,会做下面几件事

  1. 如果互斥量没其它线程用,直接获得互斥量返回

  2. 如果互斥量是本线程在用,对可重入锁自加

  3. 如果互斥锁被其它线程用了,进行优先级倒置调整,等待其它线程解锁互斥量

  1. 如果超时内等到其它线程解锁互斥量,回去互斥量然后返回

  2. 如果等互斥量超时,则放弃等待,检查是否有其它线程还在等待,已等待线程的优先级重新计算要倒置的优先级,重设拥有互斥量线程的优先级

int z_impl_k_mutex_lock(struct k_mutex *mutex, s32_t timeout)
{
    int new_prio;
    k_spinlock_key_t key;
    bool resched = false;

    key = k_spin_lock(&lock);


    //当前互斥量没被锁(lock_count ==0) 或是 当前thread已经拥有该锁(mutex->owner == _current)
    if (likely((mutex->lock_count == 0U) || (mutex->owner == _current))) {

        //记录thread当前的优先级,用于之后优先级倒置用
        mutex->owner_orig_prio = (mutex->lock_count == 0U) ?
                    _current->base.prio :
                    mutex->owner_orig_prio;

        mutex->lock_count++;        //对于未使用的锁这里lock_count会变成1,对于重入锁,这里lock_count会在原来的基础上增加然后返回
        mutex->owner = _current;    //更新owner

        k_spin_unlock(&lock, key);

        return 0;
    }

    //互斥量被其它thread占用,如果不等就立即返回
    if (unlikely(timeout == (s32_t)K_NO_WAIT)) {
        k_spin_unlock(&lock, key);
        return -EBUSY;
    }

    //如果要等,就进行判断,看自己线程的优先级和拥有互斥量的线程优先级谁高,计算一个新的优先级
    new_prio = new_prio_for_inheritance(_current->base.prio,
                        mutex->owner->base.prio);

    //如果互斥量拥有者线程的优先级比较低,则重设优先级,让优先级倒置
    if (z_is_prio_higher(new_prio, mutex->owner->base.prio)) {
        resched = adjust_owner_prio(mutex, new_prio);
    }

    //等待mutex释放,会引发调度
    int got_mutex = z_pend_curr(&lock, key, &mutex->wait_q, timeout);

    //等到mutex,返回
    if (got_mutex == 0) {
        return 0;
    }

    //等mutex超时
    key = k_spin_lock(&lock);

    //检查释放有其它线程在等待
    struct k_thread *waiter = z_waitq_head(&mutex->wait_q);

    //如果有其它线程在等待,比较Mutex拥有者线程和其它线程的优先级
    new_prio = (waiter != NULL) ?
        new_prio_for_inheritance(waiter->base.prio, mutex->owner_orig_prio) :
        mutex->owner_orig_prio;

    //重设拥有互斥量线程的优先级,并引发调度
    resched = adjust_owner_prio(mutex, new_prio) || resched;

    if (resched) {
        z_reschedule(&lock, key);
    } else {
        k_spin_unlock(&lock, key);
    }

    return -EAGAIN;
}

解锁

k_mutex_unlock->z_impl_k_mutex_unlock,做下面几件事

  1. 检查解锁者合法性

  2. 接触重入锁

  3. 恢复优先级倒置

  4. 等待锁的线程获取mutex

int z_impl_k_mutex_unlock(struct k_mutex *mutex)
{
    struct k_thread *new_owner;

    //互斥量检查,不能解锁无owner的mutex
    CHECKIF(mutex->owner == NULL) {
        return -EINVAL;
    }

    //互斥量检查,不能解锁其它thread拥有的mutex
    CHECKIF(mutex->owner != _current) {
        return -EPERM;
    }

    //不允许解锁一个已经被完全
    __ASSERT_NO_MSG(mutex->lock_count > 0U);

    z_sched_lock();


    //可重入锁检查,如果没有全部解锁,直接退出
    if (mutex->lock_count - 1U != 0U) {
        mutex->lock_count--;
        goto k_mutex_unlock_return;
    }


    k_spinlock_key_t key = k_spin_lock(&lock);

    //mutex可重入锁已全部解完,对优先级倒置进行恢复
    adjust_owner_prio(mutex, mutex->owner_orig_prio);

    //检查释放有线程在等mutex
    new_owner = z_unpend_first_thread(&mutex->wait_q);

    mutex->owner = new_owner;

    if (new_owner != NULL) {
        //如果有线程在等mutex,该线程获取mutex并开始调度
        mutex->owner_orig_prio = new_owner->base.prio;
        arch_thread_return_value_set(new_owner, 0);
        z_ready_thread(new_owner);
        z_reschedule(&lock, key);
    } else {
        //如果没有线程等mutex,mutex空闲
        mutex->lock_count = 0U;
        k_spin_unlock(&lock, key);
    }


k_mutex_unlock_return:
    k_sched_unlock();
    return 0;
}

参考

https://docs.zephyrproject.org/latest/reference/kernel/synchronization/mutexes.html