Zephyr内核对象--同步之互斥量

Creative Commons
本作品采用知识共享署名

本文简要说明Zephyr互斥量的使用和实现。

Zephyr内核对象-同步对象简介一文中已经大概介绍了mutex的特性,本文将继续说明互斥量的使用和实现。

使用

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,使用宏

1
K_MUTEX_DEFINE(my_mutex);

方法2,使用函数

1
2
struct k_mutex my_mutex;
k_mutex_init(&my_mutex);

资源独占访问

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

1
2
3
4
5
6
7
8
9
10
11
12
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; //优先级倒置用
};

Zephyr内核对象-同步对象简介一文中只说了优先级倒置,没有说可重入锁,这里简单的介绍一下,可以重入锁是指如果一个线程已经拥有了互斥量,那么该线程可以继续多次对该互斥量加锁,同时也要做对应次数的解锁,才能完全释放该互斥量

初始化

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

1
2
3
4
5
6
7
8
9
10
11
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;
}

用宏也可以进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
#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.如果互斥锁被其它线程用了,进行优先级倒置调整,等待其它线程解锁互斥量
3.如果超时内等到其它线程解锁互斥量,回去互斥量然后返回
4.如果等互斥量超时,则放弃等待,检查是否有其它线程还在等待,已等待线程的优先级重新计算要倒置的优先级,重设拥有互斥量线程的优先级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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