Zephyr poll

简介

  • 在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对象之间联系起来。

  • 当事件未发生,线程需要挂起时可以访问 IPC 对象中的 poll_events 链表,将 event 放入链表。

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 会更加便捷。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

咕咚.萌西

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

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

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

打赏作者

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

抵扣说明:

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

余额充值