同步-事件¶
事件对象是一种线程同步对象,允许一个或者多个线程等待同一个事件对象,当事件被传递到事件对象时,满足条件的线程都变为就绪。通常使用事件来通知一系列条件已满足。在Zephyr中每个事件对象使用一个32bit数来跟踪传递的事件集,每个bit表示一个事件。事件可以由ISR或者线程发送,发送事件有两种方法:
设置:覆盖现有的事件集,重写这个32bit数
发布:以bit方式添加事件到事件基,注意这里只能添加,不能删除
线程可以等待一个或多个事件。在等待多个事件时可以指定等待其中部分或者全部事件。线程在提出等待事件对象时,可以选择在等待前清空等待事件对象上的所有事件。
API¶
宏定义¶
K_EVENT_DEFINE(name)
静态的定义和初始化事件对象。
name 事件对象名
函数¶
void k_event_init(struct k_event *event)
初始化事件对象,在使用事件对象前首先使用该函数对其进行初始化。
event 事件对象的地址
void k_event_post(struct k_event *event, uint32_t events)
发布一个或多个事件到事件对象。如果等待
event
的线程因为发布的事件满足等待条件,该线程被转为就绪,与设置事件不同,发布的事件是增加到事件对象中。event 事件对象的地址
events 发布的事件集合
void k_event_set(struct k_event *event, uint32_t events)
设置事件集合到事件对象,将事件对象中的事件设置为events
。如果等待event
的线程因为设置的事件满足等待条件,该线程被转为就绪。与发布事件不同,设置事件会取代事件对象中的事件集。
event 事件对象的地址
events 设置的事件集合
uint32_t k_event_wait(struct k_event *event, uint32_t events, bool reset, k_timeout_t timeout)
等待任意指定事件,在event
上等待,有任意指定的事件被发送,或者是等待时间超过timeout
。一个线程最多可以等待32个事件,这些事件由events
中的bit位置标识。
注意
reset = true
将在等待事件前,将event
中已发生的事件,使用的时候请特别注意。
event 事件对象的地址
events 等待的事件集
reset 如果为
true
,等待前清空event
中已发生的事件,如果为false则不清空timeout 指定等待事件的最长事件,可以指定为
K_NO_WAIT
和K_FOREVER
返回值 事件集 等待成功后返回收到匹配的事件集 0 指定时间内未收到匹配事件
uint32_t k_event_wait_all(struct k_event *event, uint32_t events, bool reset, k_timeout_t timeout)
等待所有指定事件,在event
上等待,指定的所有事件被发送,或者是等待时间超过timeout
。一个线程最多可以等待32个事件,这些事件由events
中的bit位置标识。
注意
reset = true
将在等待事件前,将event
中已发生的事件,使用的时候请特别注意。
event 事件对象的地址
events 等待的事件集
reset 如果为
true
,等待前清空event
中已发生的事件,如果为false则不清空timeout 指定等待事件的最长事件,可以指定为
K_NO_WAIT
和K_FOREVER
返回值
事件集 等待成功后返回收到匹配的事件集
0 指定时间内未收到匹配事件
使用¶
配置¶
事件对象的配置选项是CONFIG_EVENTS
,zephyr内核没有给事件对象配置选项设置默认值,编译时将被识别为未配置,因此要使用事件对象需要增加配置CONFIG_EVENTS=y
。
示例¶
初始化事件对象
使用函数
struct k_event my_event;
k_event_init(&my_event);
等效于
K_EVENT_DEFINE(my_event);
设置事件
下列示例在中断服务程序中设置事件为0x001
void input_available_interrupt_handler(void *arg)
{
/* 通知线程数据有效 */
k_event_set(&my_event, 0x001);
...
}
发布事件
下面示例在中断服务程序中发布事件0x120,如果前面的设置事件未被清除,此时my_event
中的事件为0x121
void input_available_interrupt_handler(void *arg)
{
...
/* notify threads that more data is available */
k_event_post(&my_event, 0x120);
...
}
等待事件
下面示例等待事件50毫秒,在50毫秒内只要前面的设置和发布示例中任意一个发生都会等待成功
void consumer_thread(void)
{
uint32_t events;
events = k_event_wait(&my_event, 0xFFF, false, K_MSEC(50));
if (events == 0) {
printk("No input devices are available!");
} else {
/* 收到事件,根据events进行处理 */
...
}
...
}
下面示例等待事件50毫秒,在50毫秒没只有前面的设置和发布示例都发生了才会等待成功
void consumer_thread(void)
{
uint32_t events;
events = k_event_wait_all(&my_event, 0x121, false, K_MSEC(50));
if (events == 0) {
printk("At least one input device is not available!");
} else {
/* 事件全部收齐,进行处理 */
...
}
...
}
代码分析¶
事件对象的代码实现在kernel\events.c
,事件对象是由struct k_event
进行管理
struct k_event {
_wait_q_t wait_q;
uint32_t events;
struct k_spinlock lock;
};
wait_q
用于管理等待该事件对象的线程events
用于保存当前事件对象收到的事件lock
用于保护内核对事件对象的操作的原子性
事件对象的所有操作都是围绕着struct k_event
进行的。
初始化¶
函数实现代码如下,就是对struct k_event
定义事件的各个成员进行初始化
void z_impl_k_event_init(struct k_event *event)
{
event->events = 0;
event->lock = (struct k_spinlock) {};
z_waitq_init(&event->wait_q);
z_object_init(event);
}
用宏可以达到同时定义和初始化的目的,实现如下
#define Z_EVENT_INITIALIZER(obj) \
{ \
.wait_q = Z_WAIT_Q_INIT(&obj.wait_q), \
.events = 0 \
}
#define K_EVENT_DEFINE(name) \
STRUCT_SECTION_ITERABLE(k_event, name) = \
Z_EVENT_INITIALIZER(name);
等待事件¶
等待事件可以等待任意指定事件和全部事件,在events.c
中都是由同一个内部函数k_event_wait_internal
实现,只是指定的参数不一样
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);
}
uint32_t z_impl_k_event_wait_all(struct k_event *event, uint32_t events,
bool reset, k_timeout_t timeout)
{
/* 使用K_EVENT_WAIT_ALL选项,表示要等待所有的事件收齐 */
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);
}
内部函数k_event_wait_internal
的第三个参数opetions
用于指定等待的选项,选项定义在events.c
中
#define K_EVENT_WAIT_ANY 0x00 /* 有1个或者以上事件满足就可退出等待 */
#define K_EVENT_WAIT_ALL 0x01 /* 所有事件满足才可退出等待 */
#define K_EVENT_WAIT_RESET 0x02 /* 等待事件前先清空已有事件 */
#define K_EVENT_WAIT_MASK 0x01 /* 用于获取等待类型 */
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;
/* isr中只能不做任何等待的等待事件 */
__ASSERT(((arch_is_in_isr() == false) ||
K_TIMEOUT_EQ(timeout, K_NO_WAIT)), "");
/* 不允许等待的事件集为0,相当于未等待任何事件 */
if (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);
/* 检查是否需要清空已有事件 */
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;
}
/* 如果等待的超时未立即退出,则不进行等待,此时rv=0 */
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
k_spin_unlock(&event->lock, key);
goto out;
}
/* 将线程要等待的事件集和方式保存到线程中 */
thread->events = events;
thread->event_options = options;
/* 等待事件发生,如果等待超时rv仍然保持为0 */
if (z_pend_curr(&event->lock, key, &event->wait_q, timeout) == 0) {
/* 等待事件已发生,发送事件者将把满足的事件交换到线程内的events中,
rv中保存了等待到的事件
*/
rv = thread->events;
}
out:
/* 由于发生的事件可能会超出等待的事件,因此需要做位与返回 */
return rv & events;
}
发送事件¶
发送事件分为设置和发布,在events.c
中都是由同一个内部函数k_event_post_internal
实现,只是指定的参数不一样
void z_impl_k_event_post(struct k_event *event, uint32_t events)
{
k_event_post_internal(event, events, true);
}
void z_impl_k_event_set(struct k_event *event, uint32_t events)
{
k_event_post_internal(event, events, false);
}
内部函数k_event_post_internal
的第三个参数accumulat
为true
时表示发送的events
是添加到event
内,为false
时表示是覆盖event
的已有事件
static void k_event_post_internal(struct k_event *event, uint32_t events,
bool accumulate)
{
k_spinlock_key_t key;
struct k_thread *thread;
unsigned int wait_condition;
struct k_thread *head = NULL;
/* 上锁,保证内核对事件对象操作的原子性 */
key = k_spin_lock(&event->lock);
/* 检查是附加事件,还是要重置事件 */
if (accumulate) {
/* 附加事件 */
events |= event->events;
}
/* 对发生的事件进行更新 */
event->events = events;
/* 遍历事件对象中wait_q内存放等待的thread,并将事件能满足的线程加入到单链表中 */
_WAIT_Q_FOR_EACH(&event->wait_q, thread) {
/* 获取等待类型K_EVENT_WAIT_MASK为0x01,只取最低位,
这里wait_condition是K_EVENT_WAIT_ANY或K_EVENT_WAIT_ALL
*/
wait_condition = thread->event_options & K_EVENT_WAIT_MASK;
/* 根据等待类型对线程进行事件匹配,匹配上的线程放入单链表head中 */
if (are_wait_conditions_met(thread->events, events,
wait_condition)) {
thread->next_event_link = head;
head = thread;
}
}
/* 对事件匹配上的线程单链表进行遍历,通知线程就绪 */
if (head != NULL) {
thread = head;
do {
z_unpend_thread(thread);
arch_thread_return_value_set(thread, 0);
/* 更新线程收到的事件 */
thread->events = events;
/* 线程恢复就绪 */
z_ready_thread(thread);
thread = thread->next_event_link;
} while (thread != NULL);
}
/* 发送完事件后,引发调度,让刚变为就绪线程有机会执行 */
z_reschedule(&event->lock, key);
}
参考¶
https://docs.zephyrproject.org/latest/reference/kernel/synchronization/events.html