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

本文简要说明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,使用宏

K_SEM_DEFINE(my_sem, 0, 10);

方法2,使用函数

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

发送信号量 链接到标题

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

void input_data_isr_handler(void *arg)
{
    /* notify thread that data is available */
    k_sem_give(&my_sem);

    ...
}
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是消费者。

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

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 ,流程分析见注释

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;
}

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

#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,流程分析见注释

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,流程分析见注释

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

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

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