Zephyr RTOS -- Polling API

Posted 搬砖-工人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zephyr RTOS -- Polling API相关的知识,希望对你有一定的参考价值。

本笔记基于 Zephyr 版本 2.6.0-rc2

 

前言

本人正在学习 Zephyr,一个可移植性较强,可以兼容多种开发板及物联网设备的操作系统,如果你感兴趣,可以点此查看我的 学习笔记总述 进行了解!

 

Polling API - (轮询 API)

轮询 API 用于并发地等待多个条件中的任何一个被满足。

 

1. Concepts - (概念)

轮询 API 的主要函数是 k_poll(),它在概念上与 POSIX poll() 函数非常相似,不同之处在于它是对内核对象操作,而不是文件描述符进行操作。

轮询 API 允许单个线程并发地等待一个或多个条件被满足,而不需要单独地主动查看每个条件。

这里有一组有限的条件:

  • 信号量可用
  • 内核 FIFO 包含准备检索的数据
  • 一个轮询信号被发出

想要等待多个条件的线程必须定义一个 轮询事件(poll events) 数组, 每个条件一个。

必须先初始化数组中的所有事件,然后才能轮询数组。

每个事件必须指定必须满足哪种 类型(type) 的条件,以便将其状态更改为表示已满足所请求的条件。

每个事件必须指定它希望满足条件的 内核对象(kernel object)

每个事件必须指定在满足条件时使用哪种操作 模式(mode)

每个事件可以选择指定一个 标记(tag) 来将多个事件组合在一起,这是由用户决定的。

除了内核对象之外,还有一个 轮询信号(poll signal) 伪对象类型可以直接发出信号。

k_poll() 函数一旦满足它所等待的条件之一就返回。如果在调用 k_poll() 之前完成,或者由于内核的优先多线程特性,在 k_poll() 返回时可能会有多个完成。调用者必须查看数组中所有轮询事件的状态,以确定完成了哪些事件以及采取哪些操作。

目前,只有一种操作模式可用:不获取对象。例如,这意味着当 k_poll() 返回并且 poll 事件表明信号量可用时, k_poll() 的调用者必须调用 k_sem_take() 以获取信号量的所有权。如果信号量被争用,则不能保证它在 k_sem_give() 被调用时仍然可用。

 

2. Implementation - (实现)

2.1 Using 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[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_WAITK_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, 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;
    }
}

 

2.2 Using k_poll_signal_raise()

事件类型之一是 K_POLL_TYPE_SIGNAL:这是轮询事件的 “direct” 信号。这可以看作是一个只有一个线程可以等待的轻量级二进制信号量。

轮询信号是 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;
    }
}

 

3. Suggested Uses - (建议用途)

使用 k_poll() 合并将在一个对象上暂挂的多个线程,从而节省大量堆栈空间。

因为对象只有在没有其他线程等待它们可用时才会发出信号,而且只有一个线程可以轮询特定的对象,所以轮询最好是在对象不是多个线程争用,基本上,当单个线程作为多个对象的主“服务器”或“分派器”运行,并且是唯一试图获取这些对象的线程时的情况下使用。

 

4. Configuration Options - (配置选项)

相关配置选项:

 

参考链接

https://docs.zephyrproject.org/latest/reference/kernel/other/polling.html#

以上是关于Zephyr RTOS -- Polling API的主要内容,如果未能解决你的问题,请参考以下文章

Zephyr RTOS -- Stacks

Zephyr RTOS -- Stacks

Zephyr RTOS -- Stacks

Zephyr RTOS -- Message Queues

Zephyr RTOS -- Message Queues

Zephyr RTOS -- Message Queues