Zephyr内核对象--同步之信号量

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

本文简要说明Zephyr信号量的使用和实现。

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

使用

API

#define K_SEM_DEFINE(name, initial_count, count_limit)
作用: 声明一个k_sem,并初始化
name: 声明一个name的k_sem
initial_count: 初始化count
count_limit: 允许最大的count
无返回值,如果初始化有问题,会在编译期间出错

int k_sem_init(struct k_sem *sem, unsigned int initial_count, unsigned int limit)
作用: 初始化sem
sem, 要初始化的sem
initial_count: 初始化count
count_limit: 允许最大的count
返回值: 0表示初始化成功

int k_sem_take(struct k_sem *sem, s32_t timeout)
作用:获取信号
sem: 要等待的信号量
timeout: 等待时间,单位ms。K_NO_WAIT不等待, K_FOREVER一直等
返回值: 0表示拿到sem

void k_sem_give(struct k_sem *sem)
作用:发送信号

void k_sem_reset(struct k_sem *sem)
作用:将信号的量的count reset为0

unsigned int k_sem_count_get(struct k_sem *sem)
作用:获取指定信号量当前的count值

使用说明

使用信号量来控制多个线程对一组资源的访问。
使用信号量在生产线程和消耗线程或ISR之间同步处理。

初始化

先初始化一个信号量,下面两种方式的效果是一样的
方法1,使用宏

1
K_SEM_DEFINE(my_sem, 0, 10);

方法2,使用函数

1
2
struct k_sem my_sem;
k_sem_init(&my_sem, 0, 10);

发送信号量

允许在thread或者ISR中发送信号量,一般情况下发送信号量的thread或者ISR都是资源的生产者。
例如中断被触发时数据有效,在ISR中通过发信号量通知接收线程接收数据。

1
2
3
4
5
6
7
void input_data_isr_handler(void *arg)
{
/* notify thread that data is available */
k_sem_give(&my_sem);

...
}

1
2
3
4
5
6
7
8
9
void productor_thread(void *arg)
{
while(1){
/* prepare data */
...
/* notify thread that data is available */
k_sem_give(&my_sem);
}
}

接收信号量

允许在thread中接收信号量,但实际过程中ISR中接收信号量的情况几乎没有,如果一定要用,timeout只能用K_NO_WAIT。
一般情况下接收信号量的thread是消费者。

1
2
3
4
5
6
7
8
9
10
11
12
13
void consumer_thread(void)
{
...
while(1){

if (k_sem_take(&my_sem, K_MSEC(50)) != 0) {
printk("Input data not available!");
} else {
/* fetch available data */
...
}
}
}

实现

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

1
2
3
4
5
6
struct k_sem {
_wait_q_t wait_q;
u32_t count; //记录当前sem cnt
u32_t limit; //最大cnt限制
_POLL_EVENT; //提供给poll用
};

初始化

k_sem_init -> z_impl_k_sem_init ,流程分析见注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int z_impl_k_sem_init(struct k_sem *sem, unsigned int initial_count,
unsigned int limit)
{
//参数检测
CHECKIF(limit == 0U || initial_count > limit) {
return -EINVAL;
}

sem->count = initial_count; //设置初始化的sem cnt
sem->limit = limit; //设置sem cnt最大限制
z_waitq_init(&sem->wait_q); //初始化wait_q
#if defined(CONFIG_POLL)
sys_dlist_init(&sem->poll_events); //如果配置了poll,由于sem可以作为poll的条件,因此这里要初始化sem的poll_event
#endif

z_object_init(sem);

return 0;
}

再看一下使用宏初始化信号量的实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#define Z_SEM_INITIALIZER(obj, initial_count, count_limit) \
{ \
.wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \
.count = initial_count, \
.limit = count_limit, \
_POLL_EVENT_OBJ_INIT(obj) \
_OBJECT_TRACING_INIT \
}

#define K_SEM_DEFINE(name, initial_count, count_limit) \
Z_STRUCT_SECTION_ITERABLE(k_sem, name) = \ 定义一个k_sem变量
Z_SEM_INITIALIZER(name, initial_count, count_limit); \ 初始化这个变量
BUILD_ASSERT(((count_limit) != 0) && \
((initial_count) <= (count_limit)));

发送

k_sem_give -> z_impl_k_sem_give,流程分析见注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void z_impl_k_sem_give(struct k_sem *sem)
{
k_spinlock_key_t key = k_spin_lock(&lock);

//获取正在等待该sem的thread
struct k_thread *thread = z_unpend_first_thread(&sem->wait_q);

if (thread != NULL) {
//如果存在等待sem的thread,将该thread转为就绪
arch_thread_return_value_set(thread, 0);
z_ready_thread(thread);
} else {
//如果不存在等待sem的thread, sem cnt +1, 将资源累计,但不能藏limit
sem->count += (sem->count != sem->limit) ? 1U : 0U;

//这里是通知poll该sem的对象条件已满足,这部分在poll分析
handle_poll_events(sem);
}

//重新调度,切换ready的thread上
z_reschedule(&lock, key);
}

接收

k_sem_take->z_impl_k_sem_take,流程分析见注释

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
int z_impl_k_sem_take(struct k_sem *sem, s32_t timeout)
{
int ret = 0;

//ISR中只能不等待的收取sem
__ASSERT(((arch_is_in_isr() == false) || (timeout == K_NO_WAIT)), "");

k_spinlock_key_t key = k_spin_lock(&lock);

//如果sem cnt不为0,可获取信号,直接返回
if (likely(sem->count > 0U)) {
sem->count--;
k_spin_unlock(&lock, key);
ret = 0;
goto out;
}

//如果没有信号,且不愿意等待,直接返回
if (timeout == K_NO_WAIT) {
k_spin_unlock(&lock, key);
ret = -EBUSY;
goto out;
}

//没有信号,有要等待,会将等待的线程加入了等待列表中,然后重新调度切换到其它thread运行
//等待超时或者等到sem后会从这里返回继续运行
ret = z_pend_curr(&lock, key, &sem->wait_q, timeout);

out:
return ret;
}

Reset

k_sem_reset->z_impl_k_sem_reset
非常简单,将计数请0

1
2
3
4
static inline void z_impl_k_sem_reset(struct k_sem *sem)
{
sem->count = 0U;
}

Get cnt

k_sem_count_get->z_impl_k_sem_count_get
也非常简单直接返回count

1
2
3
4
static inline unsigned int z_impl_k_sem_count_get(struct k_sem *sem)
{
return sem->count;
}

参考

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