在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
