在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
2struct k_sem my_sem;
k_sem_init(&my_sem, 0, 10);
发送信号量
允许在thread或者ISR中发送信号量,一般情况下发送信号量的thread或者ISR都是资源的生产者。
例如中断被触发时数据有效,在ISR中通过发信号量通知接收线程接收数据。1
2
3
4
5
6
7void input_data_isr_handler(void *arg)
{
/* notify thread that data is available */
k_sem_give(&my_sem);
...
}
1 | void productor_thread(void *arg) |
接收信号量
允许在thread中接收信号量,但实际过程中ISR中接收信号量的情况几乎没有,如果一定要用,timeout只能用K_NO_WAIT。
一般情况下接收信号量的thread是消费者。1
2
3
4
5
6
7
8
9
10
11
12
13void 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_q1
2
3
4
5
6struct 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
19int 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
22void 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
31int 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
非常简单,将计数请01
2
3
4static 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
也非常简单直接返回count1
2
3
4static 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