Zephyr events

简介

  • events 是Zephyr 提供的一种多线程同步方式,其功能和 FreeRTOS 中的 eventGroup 一样,可以用于等待多个用户事件发生。
  • events 对象用于向一个或多个线程发出信号,指示发生了一组自定义事件。
  • 线程等待 events 对象,直到另一个线程或 ISR 将所需的事件集发布到事件对象。
  • 每次将事件发布到 events 对象时, events 对象会处理等待该事件对象的所有线程以确定是否存在匹配项。
  • 等待条件与事件对象中的事件集匹配的所有线程都将被唤醒。

数据结构

k_event

struct k_event {
	_wait_q_t         wait_q;
	uint32_t          events;
	struct k_spinlock lock;

	SYS_PORT_TRACING_TRACKING_FIELD(k_event)
};
  • 该结构体中包含2个主要成员:
    • wait_q 等待队列,当线程调用等待函数时,如果条件不满足,那么将会被添加到等待队列中。
    • events 事件的状态,已发生为1,未发生为0。
  • spinlock 是为了多线程安全操作。

event_walk_data

struct event_walk_data {
	struct k_thread  *head;
	uint32_t events;
};
  • 其中 events 为事件发布后事件对象中 events 成员变量的值,代表当前已经发生的事件。
  • 当线程或者中断向事件对象发布事件时,因为等待事件被挂起的线程可能需要被唤醒,但是这些线程等待的事件并不相同,需要从等待队列 wait_q 中查找出满足唤醒条件的线程,并将这些线程作为节点添加到被唤醒链表中,head即为链表头。
static int event_walk_op(struct k_thread *thread, void *data)
{
	unsigned int      wait_condition;
	struct event_walk_data *event_data = data;

	wait_condition = thread->event_options & K_EVENT_WAIT_MASK;

	if (are_wait_conditions_met(thread->events, event_data->events,
				    wait_condition)) {

		/*
		 * Events create a list of threads to wake up. We do
		 * not want z_thread_timeout to wake these threads; they
		 * will be woken up by k_event_post_internal once they
		 * have been processed.
		 */
		thread->no_wake_on_timeout = true;

		/*
		 * The wait conditions have been satisfied. Add this
		 * thread to the list of threads to unpend.
		 */
		thread->next_event_link = event_data->head;
		event_data->head = thread;
		z_abort_timeout(&thread->base.timeout);
	}

	return 0;
}
  • 程序依次遍历wait_q 中的线程,如果发现其等待的条件已满足,调用 event_walk_op 从链表头部插入线程。
  • 当遍历完成后依次取出单链表中的线程,将其唤醒。

events 初始化

Z_EVENT_INITIALIZER

  • 静态初始化使用于为 k_event 全局对象设置初始值
#define Z_EVENT_INITIALIZER(obj) \
	{ \
	.wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \
	.events = 0 \
	}
  • 该宏会将等待队列初始化为空
  • events 设置为0

void k_event_init(struct k_event *event)

  • k_event_init 为运行时初始化函数,其具体的实现由z_impl_k_event_init 完成, 该函数功能与 Z_EVENT_INITIALIZER 一致。
void z_impl_k_event_init(struct k_event *event)
{
	event->events = 0;
	event->lock = (struct k_spinlock) {};

	SYS_PORT_TRACING_OBJ_INIT(k_event, event);

	z_waitq_init(&event->wait_q);

	z_object_init(event);
}

发布事件

void k_event_post(struct k_event *event, uint32_t events)

  • k_event_post 用于向事件对象发布指定的事件集,其实现由 z_impl_k_event_post 完成。
void z_impl_k_event_post(struct k_event *event, uint32_t events)
{
	k_event_post_internal(event, events, events);
}

static void k_event_post_internal(struct k_event *event, uint32_t events,
				  uint32_t events_mask)
{
	k_spinlock_key_t  key;
	struct k_thread  *thread;
	struct event_walk_data data;

	data.head = NULL;
	key = k_spin_lock(&event->lock);

	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_event, post, event, events,
					events_mask);

	events = (event->events & ~events_mask) |
		 (events & events_mask);
	event->events = events;
	data.events = events;
	
	/* 发布一个事件可能会唤醒多个被挂起的线程,为了将被影响的线程同时解挂,
	 * 需要完成以下步骤:
	 *  1、遍历等待队列并创建一个单链表用于解除挂起
	 *  2、将单链表中的线程依次解除挂起
	 *  3、将单链表中的线程设置为就绪态
	 */
	
	/* 将等待队列中满足条件的线程添加到单链表中 */
	z_sched_waitq_walk(&event->wait_q, event_walk_op, &data);

	/* 唤醒所有单链表中的线程 */
	if (data.head != NULL) {
		thread = data.head;
		struct k_thread *next;
		do {
			arch_thread_return_value_set(thread, 0);
			thread->events = events;
			next = thread->next_event_link;
			z_sched_wake_thread(thread, false);
			thread = next;
		} while (thread != NULL);
	}

	z_reschedule(&event->lock, key);

	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, post, event, events,
				       events_mask);
}

设置事件

void k_event_set(struct k_event *event, uint32_t events)

  • k_event_set 和 k_event_post 的区别类似于C语言中 = 与 |= 之间的差别,k_event_set 会将事件对象中的事件赋值为 events,而 k_event_post 只会影响指定的位,未指定的位则保持不变。
void z_impl_k_event_set(struct k_event *event, uint32_t events)
{
	k_event_post_internal(event, events, ~0);
}

设置或清除事件

void k_event_set_masked(struct k_event *event, uint32_t events, uint32_t events_mask)

  • k_event_set_masked 会将 events_mask 中指定的事件清除,并设置 events 中指定的事件。
void z_impl_k_event_set_masked(struct k_event *event, uint32_t events,
			       uint32_t events_mask)
{
	k_event_post_internal(event, events, events_mask);
}

清除事件

k_event_clear(struct k_event *event, uint32_t events)

  • k_event_clear 用于将 events 指定的事件清除
void z_impl_k_event_clear(struct k_event *event, uint32_t events)
{
	k_event_post_internal(event, 0, events);
}

等待事件

等待选项

  • 在Zephyr中包含如下选项,用于设置等待方式
#define K_EVENT_WAIT_ANY      0x00   /* Wait for any events */
#define K_EVENT_WAIT_ALL      0x01   /* Wait for all events */
#define K_EVENT_WAIT_MASK     0x01
#define K_EVENT_WAIT_RESET    0x02   /* Reset events prior to waiting */
  • 选项说明:
    • bit0 为 1,等待所有事件,bit0 为 0,等待其中任一事件
    • bit1 为 1,在调用 k_event_wait 时先清除所有事件,bit1 为 0,不清除。

uint32_t k_event_wait(struct k_event *event, uint32_t events, bool reset, k_timeout_t timeout)

  • k_event_wait 中包含4个参数:
    • events 等待的事件
    • reset 是否在调用 k_event_wait 时先清除所有事件
    • timeout 超时时间
  • k_event_wait 由 z_impl_k_event_wait 实现相应功能。
uint32_t z_impl_k_event_wait(struct k_event *event, uint32_t events,
			     bool reset, k_timeout_t timeout)
{
	uint32_t options = reset ? K_EVENT_WAIT_RESET : 0;

	return k_event_wait_internal(event, events, options, timeout);
}
  • 该函数中 options 的 bit0 为 0,代表任一事件发生即会返回
  • options 的 bit1 由 reset 决定,当 reset 为 true 时会先将所有事件清除,再等待事件。
static uint32_t k_event_wait_internal(struct k_event *event, uint32_t events,
				      unsigned int options, k_timeout_t timeout)
{
	uint32_t  rv = 0;
	unsigned int  wait_condition;
	struct k_thread  *thread;

	/* 在中断中调用 k_event_wait_internal 时不会进入等待 */
	__ASSERT(((arch_is_in_isr() == false) ||
		  K_TIMEOUT_EQ(timeout, K_NO_WAIT)), "");

	SYS_PORT_TRACING_OBJ_FUNC_ENTER(k_event, wait, event, events,
					options, timeout);

	/* 无等待事件,返回0 */
	if (events == 0) {
		SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, wait, event, events, 0);
		return 0;
	}

	wait_condition = options & K_EVENT_WAIT_MASK;
	thread = z_current_get();

	k_spinlock_key_t  key = k_spin_lock(&event->lock);

	/* K_EVENT_WAIT_RESET 选项开启后,首先会把事件对象中的events清0,
	 * 在某些使用场景下,需要先清除之前的状态,确保不会因为之前过时状态而立即返回
	 * 例如串口通讯中,发送一条指令并等待回应,通常会设置超时时间,
	 * 如果在超时后接收到应答,通常会将对应的事件位设置为1,
	 * 从而导致下一次运行时立即返回,得到错误的结果
	 */
	if (options & K_EVENT_WAIT_RESET) {
		event->events = 0;
	}

	/* 测试等待条件是否满足,满足返回等待结果,不满足则等待 */
	if (are_wait_conditions_met(events, event->events, wait_condition)) {
		rv = event->events;

		k_spin_unlock(&event->lock, key);
		goto out;
	}

	/* 等待事件未发生且等待时间为0,返回0 */
	if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
		k_spin_unlock(&event->lock, key);
		goto out;
	}

	/* 调用线程在TCB中设置等待的事件和选项,并进入等待,
	 * 等待线程或中断发布事件,将当前调用线程唤醒
	 */
	thread->events = events;
	thread->event_options = options;

	SYS_PORT_TRACING_OBJ_FUNC_BLOCKING(k_event, wait, event, events,
					   options, timeout);

	if (z_pend_curr(&event->lock, key, &event->wait_q, timeout) == 0) {
		/* Retrieve the set of events that woke the thread */
		rv = thread->events;
	}

out:
	SYS_PORT_TRACING_OBJ_FUNC_EXIT(k_event, wait, event,
				       events, rv & events);

	return rv & events;
}

uint32_t k_event_wait_all(struct k_event *event, uint32_t events, bool reset, k_timeout_t timeout)

  • k_event_wait_all 是 k_event_wait 的另一版本,两者差别在于等待条件的不同,前者是等待所有事件,后者等待任一事件。
uint32_t z_impl_k_event_wait_all(struct k_event *event, uint32_t events,
				 bool reset, k_timeout_t timeout)
{
	uint32_t options = reset ? (K_EVENT_WAIT_RESET | K_EVENT_WAIT_ALL)
				 : K_EVENT_WAIT_ALL;

	return k_event_wait_internal(event, events, options, timeout);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咕咚.萌西

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值