NCS-OS系列11 :轮询API
前言
ncs 相关文章,部分为原始文档翻译,水平有限,如果有错误,欢迎指出。
本文参考链接:
https://docs.zephyrproject.org/latest/reference/kernel/other/polling.html
概念
轮询API用于同时等待多个条件中的任何一个得到满足,轮询API的主要函数是k_poll()
,它在概念上与POSIX poll()
函数非常相似,不同之处是它操作的是内核对象而不是文件描述符。
轮询API允许单个线程同时等待一个或多个条件得到满足,而无需单独主动查看每个条件。
下面是这种状态的一些限制性条件:
- 信号量变为可获得状态
- 内核的FIFO包含了准备取出的数据
- 轮询信号拉起
想要等待多个条件的线程必须定义一个轮询事件数组,每个事件对应一个条件,在对数组进行轮询之前,必须初始化数组中的所有事件。
每个事件必须指定必须满足哪种类型的条件,以便将其状态更改为满足所请求的条件的信号。
每个事件必须指定它想要满足的条件是什么内核对象。
每个事件必须指定在满足条件时使用哪种操作模式。
每个事件都可以指定一个标记从而将多个事件分组在一起,由用户自行决定。
除了内核对象之外,还有一个直接发出信号的轮询信号(poll signal
)伪对象类型。
一旦k_poll()
函数满足了它所等待的条件之一,它就会返回。如果k_poll()
在调用k_poll()
之前就完成了这些任务,或者由于内核的抢占式多线程特性,那么在k_poll()
返回时可能会完成多个任务。调用者必须查看数组中所有轮询事件的状态,以确定完成了哪些事件以及采取什么操作。
目前,只有一种操作模式可用:对象不被获取。例如,当k_poll()
返回并且poll事件声明信号量可用时,k_poll()
的调用者必须调用k_sem_take()
来获得信号量的所有权。如果信号量是被多个线程竞争时,不能保证在调用k_sem_give()
时它仍然可用。
实现
使用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
(由初始化接口负责),用户tag
是可选的,并且对API完全不透明:它的作用是帮助用户将类似的事件分组放在一起。由于是可选的,由于性能原因,它被传递给静态初始化接口,而不是运行时初始化接口。如果使用运行时初始化接口,用户必须在k_poll_event
数据结构中单独设置它。如果要忽略数组中的某个事件(很可能是临时的),则可以将其类型设置为K_POLL_TYPE_IGNORE
。
下面是静态初始化接口:
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);
// tags are left uninitialized if unused
}
事件初始化后,可以将数组传递给k_poll()
。可以指定一个超时来只等待指定的时间,或者指定特殊值K_NO_WAIT
和K_FOREVER
来不等待或等待直到满足事件条件。
一次只能有一个线程轮询一个信号量或FIFO。如果第二个线程试图轮询相同的信号量或FIFO, k_poll()
立即返回返回值-EADDRINUSE
。在这种情况下,如果传递给k_poll()
的其他条件得到满足,则它们的状态将在相应的poll事件中设置。
如果成功,k_poll()
返回0
。如果超时,它返回-EAGAIN
。
// -EADDRINUSE will not occur; the semaphore and/or data will be available
void do_stuff(void)
{
rc = k_poll(events, 2, 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 {
// handle timeout
}
}
当在循环中调用k_poll()
时,用户必须将事件状态重置为K_POLL_STATE_NOT_READY
。
void do_stuff(void)
{
for(;;) {
rc = k_poll(events, 2, K_FOREVER);
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
}
events[0].state = K_POLL_STATE_NOT_READY;
events[1].state = K_POLL_STATE_NOT_READY;
}
}
使用k_poll_signal_raise()
事件类型的K_POLL_TYPE_SIGNAL
是poll事件的“直接”信号。这可以看作是一个轻量级的二进制信号量,只有一个线程在等待。
轮询信号是一个独立的k_poll_signal
类型的对象,必须附加到k_poll_event
上,类似于信号量或FIFO。它必须首先通过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);
if (events.signal->result == 0x1337) {
// A-OK!
} else {
// weird error
}
}
// thread B
void signal_do_stuff(void)
{
k_poll_signal_raise(&signal, 0x1337);
}
如果要在循环中轮询信号,那么它的事件状态和它的已发出信号的字段都必须在每次迭代时重置(如果已发出信号)。
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);
if (events[0].signal->result == 0x1337) {
// A-OK!
} else {
// weird error
}
events[0].signal->signaled = 0;
events[0].state = K_POLL_STATE_NOT_READY;
}
}
推荐用法
使用k_poll()
来合并将挂起的多个线程,每个线程挂起一个对象,从而可能节省大量的堆栈空间。
如果只有一个线程挂起,则使用轮询信号作为轻量级二进制信号量。
配置选项
涉及到的配置如下:
- CONFIG_POLL