简介
- 在Zephyr中,polling是一种等待事件的机制,它可以同时等待多个 IPC 事件。
- 在Zephyr中,可以使用k_poll()函数来等待事件。k_poll() 函数接受一个k_poll_event数组,数组中的每个元素都描述了一个等待的事件。
- 在等待期间,线程会被阻塞,直到所有事件都发生或等待超时。
- 在等待结束后,k_poll() 函数会返回一个整数,返回0代表发生了一个或者多个事件,通过读取轮询状态进行判断,返回负数代表超时或者出错。
- poll 中除了提供等待机制,还存在一种轻量级的同步对象 k_poll_signal,与队列,fifo,这些同步通信方式类型,但是它比较简单,只能向等待线程发送一个信号被触发的通知,再加上一个返回值,除此之外没有其他的功能。
struct k_poll_event
struct k_poll_event {
/* _node 第一个成员,确保内存 k_poll_event 与双链表节点对齐,
* 可以将 k_poll_event 插入到双链表中,当等待的事件还未发生时,
* 调用 k_poll 的线程会将 k_poll_event 插入到目标对象中的 poll_events 双链表中,
* 当目标对象发生指定事件时,IPC 对象会从 poll_events 取出节点并唤醒线程
*/
sys_dnode_t _node;
/* poller 不需要创建,它指向需要等待的线程中的 poller,
* poller 代表的是线程的等待方式,调用 k_poll 函数时会自动设置对应的模式,
* 同时将 k_poll_event 中的 poller 进行初始化(指向当前线程),
* 除此之外 poller 还有一个额外的功能,此处 poller 记录的是线程中 poller 变量的地址,
* 线程中 poller 变量相对于其 tcb 首地址的偏移是固定的,
* 通过 poller 地址减去偏移可以获得对应线程 tcb
*/
struct z_poller *poller;
/* optional user-specified tag, opaque, untouched by the API */
uint32_t tag:8;
/* 等待的事件类型,此处类型需要与下方union中传入的指针类型相对应,
* 否则会出现无法预料的错误
*/
uint32_t type:_POLL_NUM_TYPES;
/* 等待事件的状态,在调用轮询api之前应设置为 K_POLL_STATE_NOT_READY
* 函数调用返回之后通过 state 的值判断对应的事件是否发生,
* 如需再次使用需要重新初始化 state 为 K_POLL_STATE_NOT_READY
*/
uint32_t state:_POLL_NUM_STATES;
/* 轮询线程在可用时不获取对象的所有权 */
uint32_t mode:1;
/* unused bits in 32-bit word */
uint32_t unused:_POLL_EVENT_NUM_UNUSED_BITS;
/* 保存具体对象的地址,用于访问对象中的 poll_events 双链表 */
union {
void *obj;
struct k_poll_signal *signal;
struct k_sem *sem;
struct k_fifo *fifo;
struct k_queue *queue;
struct k_msgq *msgq;
#ifdef CONFIG_PIPES
struct k_pipe *pipe;
#endif
};
};
struct k_poll_event 功能
-
struct k_poll_event 可以当作一个双链表节点,里面除了双链表的pre和next指针外,还包含等待的信息,将线程与IPC对象之间联系起来。文章来源:https://www.toymoban.com/news/detail-408117.html
-
当事件未发生,线程需要挂起时可以访问 IPC 对象中的 poll_events 链表,将 event 放入链表。文章来源地址https://www.toymoban.com/news/detail-408117.html
static inline void register_event(struct k_poll_event *event,
struct z_poller *poller)
{
switch (event->type) {
case K_POLL_TYPE_SEM_AVAILABLE:
__ASSERT(event->sem != NULL, "invalid semaphore\n");
add_event(&event->sem->poll_events, event, poller);
break;
/* ... */
}
event->poller = poller;
}
- 当线程调用 k_poll 进行等待时,线程可以获取 IPC 状态判断对应事件是否发生,以决定是否需要继续等待。
static inline bool is_condition_met(struct k_poll_event *event, uint32_t *state)
{
switch (event->type) {
case K_POLL_TYPE_SEM_AVAILABLE:
if (k_sem_count_get(event->sem) > 0U) {
*state = K_POLL_STATE_SEM_AVAILABLE;
return true;
}
break;
/* ... */
}
return false;
}
- 当 IPC 对象中发生指定事件时,会从 poll_events 链表中取出 event 节点,设置 event 的状态,然后把等待线程唤醒。
void z_handle_obj_poll_events(sys_dlist_t *events, uint32_t state)
{
struct k_poll_event *poll_event;
poll_event = (struct k_poll_event *)sys_dlist_get(events);
if (poll_event != NULL) {
(void) signal_poll_event(poll_event, state);
}
}
static int signal_poll_event(struct k_poll_event *event, uint32_t state)
{
struct z_poller *poller = event->poller;
int retcode = 0;
if (poller != NULL) {
if (poller->mode == MODE_POLL) {
/* 将等待的线程设置为就绪态 */
retcode = signal_poller(event, state);
} else if (poller->mode == MODE_TRIGGERED) {
retcode = signal_triggered_work(event, state);
} else {
/* Poller is not poll or triggered mode. No action needed.*/
;
}
poller->is_polling = false;
if (retcode < 0) {
return retcode;
}
}
/* 设置state */
set_event_ready(event, state);
return retcode;
}
轮询类型
- 下面是Zephyr中支持的等待类型:
#define K_POLL_TYPE_IGNORE 0
#define K_POLL_TYPE_SIGNAL Z_POLL_TYPE_BIT(_POLL_TYPE_SIGNAL) // 1
#define K_POLL_TYPE_SEM_AVAILABLE Z_POLL_TYPE_BIT(_POLL_TYPE_SEM_AVAILABLE) // 2
#define K_POLL_TYPE_DATA_AVAILABLE Z_POLL_TYPE_BIT(_POLL_TYPE_DATA_AVAILABLE) // 4
#define K_POLL_TYPE_FIFO_DATA_AVAILABLE K_POLL_TYPE_DATA_AVAILABLE // 4
#define K_POLL_TYPE_MSGQ_DATA_AVAILABLE Z_POLL_TYPE_BIT(_POLL_TYPE_MSGQ_DATA_AVAILABLE) // 8
#define K_POLL_TYPE_PIPE_DATA_AVAILABLE Z_POLL_TYPE_BIT(_POLL_TYPE_PIPE_DATA_AVAILABLE) // 16
- Zephyr内核支持等待信号,信号量,队列,FIFO,msgq,管道这几种类型的事件。
- 一般的IPC通信方式虽然会提供等待机制,但是其只能针对一种事件进行等待,而不能等待多个事件(此处的事件特指 IPC 事件),例如我们无法在等待信号量的同时等待队列,或者同时等待多个队列,Zephyr中为我们提供了Polling API,它可以同时等待多个事件到来。
轮询状态
#define K_POLL_STATE_NOT_READY 0
#define K_POLL_STATE_SIGNALED Z_POLL_STATE_BIT(_POLL_STATE_SIGNALED) // 1
#define K_POLL_STATE_SEM_AVAILABLE Z_POLL_STATE_BIT(_POLL_STATE_SEM_AVAILABLE) // 2
#define K_POLL_STATE_DATA_AVAILABLE Z_POLL_STATE_BIT(_POLL_STATE_DATA_AVAILABLE) // 4
#define K_POLL_STATE_FIFO_DATA_AVAILABLE K_POLL_STATE_DATA_AVAILABLE // 4
#define K_POLL_STATE_MSGQ_DATA_AVAILABLE Z_POLL_STATE_BIT(_POLL_STATE_MSGQ_DATA_AVAILABLE)// 16
#define K_POLL_STATE_PIPE_DATA_AVAILABLE Z_POLL_STATE_BIT(_POLL_STATE_PIPE_DATA_AVAILABLE)// 32
#define K_POLL_STATE_CANCELLED Z_POLL_STATE_BIT(_POLL_STATE_CANCELLED) // 8
- 在调用k_poll之前需将状态设置为 K_POLL_STATE_NOT_READY,避免调用返回后读取到错误的状态。
- 每个状态与轮询的类型一一对应,除此之外,如果在等待过程中其他线程调用了取消等待的函数,被等待的线程会被唤醒并返回一错误码。
初始化
#define K_POLL_EVENT_INITIALIZER(_event_type, _event_mode, _event_obj) \
{ \
.poller = NULL, \
.type = _event_type, \
.state = K_POLL_STATE_NOT_READY, \
.mode = _event_mode, \
.unused = 0, \
{ \
.obj = _event_obj, \
}, \
}
#define K_POLL_EVENT_STATIC_INITIALIZER(_event_type, _event_mode, _event_obj, \
event_tag) \
{ \
.tag = event_tag, \
.type = _event_type, \
.state = K_POLL_STATE_NOT_READY, \
.mode = _event_mode, \
.unused = 0, \
{ \
.obj = _event_obj, \
}, \
}
- 在Zephyr中提供了两个宏用于静态初始化。
- _event_type 代表等待的事件类型。
- _event_mode 固定为 K_POLL_MODE_NOTIFY_ONLY。
- _event_obj 代表目标对象地址,可以是struct k_poll_signal、struct k_sem、struct k_fifo、struct k_queue、struct k_msgq、struct k_pipe类型的变量,传入的对象需与等待的类型一致。
void k_poll_event_init(struct k_poll_event *event, uint32_t type, int mode, void *obj);
- 除了使用宏在定义变量时进行初始化之外,还可以使用 k_poll_event_init 进行初始化,它的功能和上面的两个宏功能一样,函数可以直接传入变量地址后即可初始化,而上面两个宏只能在变量定义时初始化,当执行完一次 k_poll 之后需要再次使用时,需要将 k_poll_event 中的 state 重设为 K_POLL_STATE_NOT_READY。
struct k_poll_event events[2] = {
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_sem, 0),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_fifo, 0),
};
struct k_poll_event events[2];
void some_init(void)
{
k_poll_event_init(&events[0],
K_POLL_TYPE_SEM_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_sem);
k_poll_event_init(&events[1],
K_POLL_TYPE_FIFO_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_fifo);
}
事件等待
- k_poll 在 poll 整个过程中最重要的函数之一,用于等待事件发生。
int z_impl_k_poll(struct k_poll_event *events, int num_events,
k_timeout_t timeout)
{
int events_registered;
k_spinlock_key_t key;
struct z_poller *poller = &_current->poller;
poller->is_polling = true;
poller->mode = MODE_POLL;
__ASSERT(!arch_is_in_isr(), "");
__ASSERT(events != NULL, "NULL events\n");
__ASSERT(num_events >= 0, "<0 events\n");
SYS_PORT_TRACING_FUNC_ENTER(k_poll_api, poll, events);
/* register_events 首先会轮询 events 中的IPC对象,等待的事件是否已经发生,如果已经发生会将 poller->is_polling 设为 false,
* 如果等待的事件没有发生则会将 event 添加到对应的 IPC 对象中的 poll_events 链表中, 等待事件来临并唤醒被挂起的线程
*/
events_registered = register_events(events, num_events, poller,
K_TIMEOUT_EQ(timeout, K_NO_WAIT));
key = k_spin_lock(&lock);
/* 如果poller->is_polling为false,说明已经有事件发生,就不需要再等待了 */
if (!poller->is_polling) {
/* 从queue、sem、msgq、pipe等中移除事件 */
clear_event_registrations(events, events_registered, key);
k_spin_unlock(&lock, key);
SYS_PORT_TRACING_FUNC_EXIT(k_poll_api, poll, events, 0);
return 0;
}
poller->is_polling = false;
/* 如果等待的事件没有发生,timeout等于K_NO_WAIT,就不需要等待了,返回-EAGAIN (超时)*/
if (K_TIMEOUT_EQ(timeout, K_NO_WAIT)) {
k_spin_unlock(&lock, key);
SYS_PORT_TRACING_FUNC_EXIT(k_poll_api, poll, events, -EAGAIN);
return -EAGAIN;
}
static _wait_q_t wait_q = Z_WAIT_Q_INIT(&wait_q);
// 如果等待的事件没有发生,timeout不等于K_NO_WAIT,就需要等待,调用z_pend_curr()等待
int swap_rc = z_pend_curr(&lock, key, &wait_q, timeout);
// 从queue、sem、msgq、pipe等中移除事件
key = k_spin_lock(&lock);
clear_event_registrations(events, events_registered, key);
k_spin_unlock(&lock, key);
SYS_PORT_TRACING_FUNC_EXIT(k_poll_api, poll, events, swap_rc);
// swap_rc 为0,说明等待的事件发生了,否则等待超时或者等待被中断
return swap_rc;
}
static inline int register_events(struct k_poll_event *events,
int num_events,
struct z_poller *poller,
bool just_check)
{
int events_registered = 0;
for (int ii = 0; ii < num_events; ii++) {
k_spinlock_key_t key;
uint32_t state;
key = k_spin_lock(&lock);
/* K_POLL_EVENT_INITIALIZER 会将 obj 进行初始化,而obj等价于queue、sem、msgq、pipe,
当queue、sem、msgq、pipe中不为空时,代表等待的事件已经发生,此时就会调用is_condition_met(),
*/
if (is_condition_met(&events[ii], &state)) {
/* set_event_ready 会设置初始状态并将 k_poll_event 中的poller设置为NULL */
set_event_ready(&events[ii], state);
poller->is_polling = false;
}
/* 如果等待的事件没有发生,就会调用register_event(),将事件添加到queue、sem、msgq、pipe等中,
当等待的事件发生时,IPC 对象调用 z_handle_obj_poll_events(),将事件从queue、sem、msgq、pipe等中移除,
从而唤醒等待的线程
*/
else if (!just_check && poller->is_polling) {
register_event(&events[ii], poller);
events_registered += 1;
} else {
/* Event is not one of those identified in is_condition_met()
* catching non-polling events, or is marked for just check,
* or not marked for polling. No action needed.
*/
;
}
k_spin_unlock(&lock, key);
}
return events_registered;
}
事件处理
- 在Zephyr中,IPC 对象发生指定事件时,会将 poll_events 链表中等待的线程进行唤醒,以 queue 为例子,向 queue 插入数据时,会发生 K_POLL_TYPE_DATA_AVAILABLE 类型的事件,而在 queue 的 poll_events 链表中,存放的都是等待该事件而被挂起的线程,当发生该事件时会调用 z_handle_obj_poll_events 从其中取出一个节点,唤醒对应线程。
void z_handle_obj_poll_events(sys_dlist_t *events, uint32_t state)
{
struct k_poll_event *poll_event;
/* 等待事件的线程会被放入到IPC对象的 poll_events 链表中,这里从链表中取出事件,
* 然后调用signal_poll_event()函数处理事件
*/
poll_event = (struct k_poll_event *)sys_dlist_get(events);
if (poll_event != NULL) {
(void) signal_poll_event(poll_event, state);
}
}
- signal_poll_event 中会调用 signal_poller,最终将唤醒等待线程。
static int signal_poll_event(struct k_poll_event *event, uint32_t state)
{
struct z_poller *poller = event->poller;
int retcode = 0;
/* k_poll_event 中的pooller可以根据mode的不同,进行不同处理 */
if (poller != NULL) {
if (poller->mode == MODE_POLL) {
retcode = signal_poller(event, state);
} else if (poller->mode == MODE_TRIGGERED) {
retcode = signal_triggered_work(event, state);
} else {
/* Poller is not poll or triggered mode. No action needed.*/
;
}
poller->is_polling = false;
if (retcode < 0) {
return retcode;
}
}
/* 修改对应事件的状态为就绪态 */
set_event_ready(event, state);
return retcode;
}
static int signal_poller(struct k_poll_event *event, uint32_t state)
{
struct k_thread *thread = poller_thread(event->poller);
__ASSERT(thread != NULL, "poller should have a thread\n");
// 如果线程不再等待,直接返回0
if (!z_is_thread_pending(thread)) {
return 0;
}
// 如果线程处于等待超时状态,直接返回-EAGAIN
if (z_is_thread_timeout_expired(thread)) {
return -EAGAIN;
}
// 将线程从等待队列中移除
z_unpend_thread(thread);
// 设置线程的返回值,如果state == K_POLL_STATE_CANCELLED,返回-EINTR,否则返回0
arch_thread_return_value_set(thread,
state == K_POLL_STATE_CANCELLED ? -EINTR : 0);
if (!z_is_thread_ready(thread)) {
return 0;
}
// 将线程添加到就绪队列中
z_ready_thread(thread);
return 0;
}
struct k_poll_signal
- 在上述轮询类型中其中一种是 K_POLL_TYPE_SIGNAL,是一个“直接”由信号通知的轮询事件,可以看作是一个轻量级的二进制信号量,只有一个线程可以等待。
初始化
- 轮询信号是一个类型为 struct k_poll_signal 的单独对象,类似于信号量或 FIFO,必须附加到 k_poll_event 上,它必须首先通过 K_POLL_SIGNAL_INITIALIZER宏 或者 k_poll_signal_init 函数进行初始化。
struct k_poll_signal signal;
void do_stuff(void)
{
k_poll_signal_init(&signal);
}
发出信号和等待信号
- 它通过 k_poll_signal_raise 发出信号,这个函数可以携带一个 result 参数,可以用来向等待的线程传递额外的信息,该参数对于API而言是不透明的,如果是在一个循环中使用k_poll,在每次使用之后应将 k_poll_signal 中的 signaled 设置为0, 同时将 k_poll_event 中的 state 重设为 K_POLL_STATE_NOT_READY。
struct k_poll_signal signal;
void thread_a(void)
{
k_poll_signal_init(&signal);
struct k_poll_event events[1] = {
K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY,
&signal),
};
for (;;)
{
k_poll(events, 1, K_FOREVER);
if (events[0].signal->result == 0xffffffff)
{
// A-OK!
}
else
{
// weird error
}
events[0].signal->signaled = 0;
events[0].state = K_POLL_STATE_NOT_READY;
}
}
void thread_b(void)
{
k_poll_signal_raise(&signal, 0xffffffff);
}
总结
- poll 机制实现了在同一时间内等待多个 IPC 事件的功能,当其中一个事件便会立即返回,程序通过判断事件状态判断哪些事件已经发生。
- poll 除了支持 fifo,queue,sem,msgq,pipe 这几种类型之外,还提供了一种轻量级的同步通信方式 poll_signal,当多个进程之间只需要传递事件发生或未发生,而不需要携带其他信息时,使用 poll_signal 会更加便捷。
到了这里,关于Zephyr poll的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!