轮询 API
轮询 API 用于同时等待多个条件中的任意一个得到满足。
概念
轮询 API 的主要函数是 k_poll()。它在概念上与 POSIX 的 poll() 函数非常相似,但操作的是内核对象,而不是文件描述符。
轮询 API 允许单个线程同时等待一个或多个条件满足,无需逐个主动检查。
支持的条件种类有限:
信号量变为可用
内核 FIFO 中有可读取的数据
内核 LIFO 中有可读取的数据
内核消息队列中有可读取的数据
内核管道中有可读取的数据
轮询信号被触发
需要等待多个条件的线程必须定义一个 轮询事件 数组,每个条件对应一个事件。
轮询数组之前,必须初始化数组中的所有事件。
每个事件必须指定待满足条件的 类型,以便通过改变状态来表示请求的条件已满足。
每个事件必须指定希望其条件得到满足的 内核对象。
每个事件必须指定条件满足时使用的操作 模式。
每个事件还可根据用户需要指定一个 标签,用于将多个事件分组。
除内核对象外,还有一种 轮询信号 伪对象类型,可以直接向其发出信号。
k_poll() 等待的任意一个条件满足后,函数就会返回。由于条件可能在调用 k_poll() 之前就已满足,或者由于内核具有抢占式多线程特性,k_poll() 返回时可能已有多个条件满足。调用者必须检查数组中所有轮询事件的状态,确定哪些条件已满足以及应采取什么操作。
目前只有一种操作模式:不获取对象。例如,k_poll() 返回且轮询事件状态表示信号量可用时,k_poll() 的调用者还必须调用 k_sem_take() 获取信号量。如果存在对该信号量的竞争,则不能保证调用 k_sem_take() 时它仍然可用。
实现
使用 k_poll()
主要 API 是 k_poll(),它操作由 k_poll_event 类型组成的轮询事件数组。数组中的每个元素表示一个带有条件的事件,调用 k_poll() 会等待其中的条件得到满足。
轮询事件可以使用运行时初始化器 K_POLL_EVENT_INITIALIZER() 或 k_poll_event_init(),也可以使用静态初始化器 K_POLL_EVENT_STATIC_INITIALIZER() 初始化。必须向初始化器传入与指定 类型 匹配的对象。模式 必须 设置为 K_POLL_MODE_NOTIFY_ONLY。状态 必须 设置为 K_POLL_STATE_NOT_READY (初始化器会完成此操作)。用户 标签 是可选的,API 完全不解释其含义:它用于帮助用户将相似事件分组。由于标签是可选的,出于性能考虑,只有静态初始化器接受它,运行时初始化器不接受。使用运行时初始化器时,用户必须在 k_poll_event 数据结构中单独设置标签。如果需要忽略数组中的某个事件(通常是临时忽略),可以将其类型设置为 K_POLL_TYPE_IGNORE。
struct k_poll_event events[4] = {
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),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_MSGQ_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_msgq, 0),
K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_PIPE_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_pipe, 0),
};
或在运行时初始化
struct k_poll_event events[4];
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_event_init(&events[2],
K_POLL_TYPE_MSGQ_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_msgq);
k_poll_event_init(&events[3],
K_POLL_TYPE_PIPE_DATA_AVAILABLE,
K_POLL_MODE_NOTIFY_ONLY,
&my_pipe);
// tags are left uninitialized if unused
}
事件初始化后,可将数组传递给 k_poll()。可以指定超时,仅等待指定时长;也可以使用特殊值 K_NO_WAIT 和 K_FOREVER,分别表示不等待,或一直等待到某个事件条件满足才返回。
每个信号量或 FIFO 都提供一个轮询等待者列表,应用程序可以按需让任意数量的事件在其中等待。请注意,等待者按先到先服务的顺序处理,而不是按优先级顺序。
成功时,k_poll() 返回 0。超时时,返回 -EAGAIN。
// assume there is no contention on this semaphore and FIFO
// -EADDRINUSE will not occur; the semaphore and/or data will be available
void do_stuff(void)
{
rc = k_poll(events, ARRAY_SIZE(events), K_MSEC(1000));
if (rc == 0) {
if (events[0].state == K_POLL_STATE_SEM_AVAILABLE) {
k_sem_take(events[0].sem, 0);
} else if (events[1].state == K_POLL_STATE_FIFO_DATA_AVAILABLE) {
data = k_fifo_get(events[1].fifo, 0);
// handle data
} else if (events[2].state == K_POLL_STATE_MSGQ_DATA_AVAILABLE) {
ret = k_msgq_get(events[2].msgq, buf, K_NO_WAIT);
// handle data
} else if (events[3].state == K_POLL_STATE_PIPE_DATA_AVAILABLE) {
bytes_read = k_pipe_read(events[3].pipe, buf, bytes_to_read, K_NO_WAIT);
// handle data
}
} else {
// handle timeout
}
}
在循环中调用 k_poll() 时,用户必须将事件状态重置为 K_POLL_STATE_NOT_READY。
void do_stuff(void)
{
for(;;) {
rc = k_poll(events, ARRAY_SIZE(events), K_FOREVER);
if (events[0].state == K_POLL_STATE_SEM_AVAILABLE) {
k_sem_take(events[0].sem, 0);
}
if (events[1].state == K_POLL_STATE_FIFO_DATA_AVAILABLE) {
data = k_fifo_get(events[1].fifo, 0);
// handle data
}
if (events[2].state == K_POLL_STATE_MSGQ_DATA_AVAILABLE) {
ret = k_msgq_get(events[2].msgq, buf, K_NO_WAIT);
// handle data
}
if (events[3].state == K_POLL_STATE_PIPE_DATA_AVAILABLE) {
bytes_read = k_pipe_read(events[3].pipe, buf, bytes_to_read, K_NO_WAIT);
// handle data
}
events[0].state = K_POLL_STATE_NOT_READY;
events[1].state = K_POLL_STATE_NOT_READY;
events[2].state = K_POLL_STATE_NOT_READY;
events[3].state = K_POLL_STATE_NOT_READY;
}
}
使用 k_poll_signal_raise()
其中一种事件类型是 K_POLL_TYPE_SIGNAL,它是直接发给轮询事件的信号。可以将其视为一种只允许一个线程等待的轻量级二值信号量。
轮询信号是一个独立的 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() 函数触发信号。该函数接受一个用户 结果 参数,API 不解释其含义,可用它向等待事件的线程传递额外信息。
struct k_poll_signal signal;
// thread A
void do_stuff(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),
};
k_poll(events, 1, K_FOREVER);
int signaled, result;
k_poll_signal_check(&signal, &signaled, &result);
if (signaled && (result == 0x1337)) {
// A-OK!
} else {
// weird error
}
}
// thread B
void signal_do_stuff(void)
{
k_poll_signal_raise(&signal, 0x1337);
}
如果在循环中轮询信号,则每次迭代中,若信号已触发,既要 将事件状态重置为 K_POLL_STATE_NOT_READY,也要 使用 k_poll_signal_reset() 重置其 result。
struct k_poll_signal signal;
void do_stuff(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);
int signaled, result;
k_poll_signal_check(&signal, &signaled, &result);
if (signaled && (result == 0x1337)) {
// A-OK!
} else {
// weird error
}
k_poll_signal_reset(&signal);
events[0].state = K_POLL_STATE_NOT_READY;
}
}
请注意,轮询信号内部不进行同步。向 k_poll() 传入信号后,只要系统中的任何代码调用 k_poll_signal_raise(),该调用就会返回。但如果信号由外部代码管理,并通过 k_poll_signal_init() 重置,应用程序检查时事件状态可能已经不再等于 K_POLL_STATE_SIGNALED,简单实现的应用程序就可能遗漏事件。最佳做法是始终只在执行 k_poll() 循环的线程内重置信号,或使用其他能够跟踪事件计数的事件类型:在这一点上,信号量和 FIFO 更不易出错,因为它们在机制上不会“遗漏”事件。
使用建议
使用 k_poll() 合并原本分别等待单个对象的多个线程,可能节省大量栈空间。
如果只有一个线程等待,可以将轮询信号用作轻量级二值信号量。
备注
只有在没有其他线程等待对象变为可用时,才会为对象发出轮询通知,而且同一对象只能由一个线程轮询,因此轮询最适合用于多个线程之间不会竞争对象的场景。典型情况是,单个线程充当多个对象的主要“服务器”或“分发器”,且只有该线程尝试获取这些对象。
配置选项
相关配置选项: