From 44d7cff207c3f4e82e9bdfd53b61634e8248c834 Mon Sep 17 00:00:00 2001 From: Benjamin Walsh Date: Sun, 19 Feb 2017 00:35:05 -0500 Subject: [PATCH] doc: add polling API to the kernel primer Change-Id: I17578f8350f1a26d2ecf8c0886c8e93078a2cdca Signed-off-by: Benjamin Walsh --- doc/kernel/other/other.rst | 1 + doc/kernel/other/polling.rst | 291 +++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 doc/kernel/other/polling.rst diff --git a/doc/kernel/other/other.rst b/doc/kernel/other/other.rst index 45e11c3a0cb..8bb3e433f6e 100644 --- a/doc/kernel/other/other.rst +++ b/doc/kernel/other/other.rst @@ -10,6 +10,7 @@ This section describes other services provided by the kernel. interrupts.rst atomic.rst + polling.rst ring_buffers.rst float.rst cxx_support.rst diff --git a/doc/kernel/other/polling.rst b/doc/kernel/other/polling.rst new file mode 100644 index 00000000000..7c602e360a9 --- /dev/null +++ b/doc/kernel/other/polling.rst @@ -0,0 +1,291 @@ +.. _polling_v2: + +Polling API +########### + +The polling API is used to wait concurrently for any one of multiple conditions +to be fulfilled. + +.. contents:: + :local: + :depth: 2 + +Concepts +******** + +The polling API's main function is :cpp:func:`k_poll()`, which is very similar +in concept to the POSIX :cpp:func:`poll()` function, except that it operates on +kernel objects rather than on file descriptors. + +The polling API allows a single thread to wait concurrently for one or more +conditions to be fulfilled without actively looking at each one individually. + +There is a limited set of such conditions: + +- a semaphore becomes available +- a kernel FIFO contains data ready to be retrieved +- a poll signal is raised + +A thread that wants to wait on multiple conditions must define an array of +**poll events**, one for each condition. + +All events in the array must be initialized before the array can be polled on. + +Each event must specify which **type** of condition must be satisfied so that +its state is changed to signal the requested condition has been met. + +Each event must specify what **kernel object** it wants the condition to be +satisfied. + +Each event must specify which **mode** of operation is used when the condition +is satisfied. + +Each event can optionally specify a **tag** to group multiple events together, +to the user's discretion. + +Apart from the kernel objects, there is also a **poll signal** pseudo-object +type that be directly signaled. + +The :cpp:func:`k_poll()` function returns as soon as one of the conditions it +is waiting for is fulfilled. It is possible for more than one to be fulfilled +when :cpp:func:`k_poll()` returns, if they were fulfilled before +:cpp:func:`k_poll()` was called, or due to the preemptive multi-threading +nature of the kernel. The caller must look at the state of all the poll events +in the array to figured out which ones were fulfilled and what actions to take. + +Currently, there is only one mode of operation available: the object is not +acquired. As an example, this means that when :cpp:func:`k_poll()` returns and +the poll event states that the semaphore is available, the caller of +:cpp:func:`k_poll()` must then invoke :cpp:func:`k_sem_take()` to take +ownership of the semaphore. If the semaphore is contested, there is no +guarantee that it will be still available when :cpp:func:`k_sem_give()` is +called. + +Implementatioe +************** + +Using k_poll() +============== + +The main API is :cpp:func:`k_poll()`, which operates on an array of poll events +of type :c:type:`struct k_poll_event`. Each entry in the array represents one +event a call to :cpp:func:`k_poll()` will wait for its condition to be +fulfilled. + +They can be initialized using either the runtime initializers +:c:macro:`K_POLL_EVENT_INITIALIZER()` or :cpp:func:`k_poll_event_init()`, or +the static initializer :c:macro:`K_POLL_EVENT_STATIC_INITIALIZER()`. An object +that matches the **type** specified must be passed to the initializers. The +**mode** *must* be set to :c:macro:`K_POLL_MODE_NOTIFY_ONLY`. The state *must* +be set to :c:macro:`K_POLL_STATE_NOT_READY` (the initializers take care of +this). The user **tag** is optional and completely opaque to the API: it is +there to help a user to group similar events together. Being optional, it is +passed to the static initializer, but not the runtime ones for performance +reasons. If using runtime initializers, the user must set it separately in the +:c:type:`struct k_poll_event` data structure. If an event in the array is to be +ignored, most likely temporarily, its type can be set to K_POLL_TYPE_IGNORE. + +.. code-block:: c + + 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), + }; + +or at runtime + +.. code-block:: c + + struct k_poll_event events[2]; + void some_init(void) + { + k_poll_event_init(K_POLL_TYPE_SEM_AVAILABLE, + K_POLL_MODE_NOTIFY_ONLY, + &my_sem); + + k_poll_event_init(K_POLL_TYPE_FIFO_DATA_AVAILABLE, + K_POLL_MODE_NOTIFY_ONLY, + &my_fifo); + + // tags are left uninitialized if unused + } + + +After the events are initialized, the array can be passed to +:cpp:func:`k_poll()`. A timeout can be specified to wait only for a specified +amount of time, or the special values :c:macro:`K_NO_WAIT` and +:c:macro:`K_FOREVER` to either not wait or wait until an event condition is +satisfied and not sooner. + +Only one thread can poll on a semaphore or a FIFO at a time. If a second thread +tries to poll on the same semaphore or FIFO, :cpp:func:`k_poll()` immediately +returns with the return value :c:macro:`-EADDRINUSE`. In that case, if other +conditions passed to :cpp:func:`k_poll` were met, their state will be set in +the corresponding poll event. + +In case of success, :cpp:func:`k_poll()` returns 0. If it times out, it returns +:c:macro:`-EAGAIN`. + +.. code-block:: c + + // 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 + } + } + +When :cpp:func:`k_poll()` is called in a loop, the events state must be reset +to :c:macro:`K_POLL_STATE_NOT_READY` by the user. + +.. code-block:: c + + 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; + } + } + +Using k_poll_signal() +===================== + +One of the types of events is :c:macro:`K_POLL_TYPE_SIGNAL`: this is a "direct" +signal to a poll event. This can be seen as a lightweight binary semaphore only +one thread can wait for. + +A poll signal is a separate object of type :c:type:`struct k_poll_signal` that +must be attached to a k_poll_event, similar to a semaphore or FIFO. It must +first be initialized either via :c:macro:`K_POLL_SIGNAL_INITIALIZER()` or +:cpp:func:`k_poll_signal_init()`. + +.. code-block:: c + + struct k_poll_signal signal; + void do_stuff(void) + { + k_poll_signal_init(&signal); + } + +It is signaled via the :cpp:func:`k_poll_signal()` function. This function +takes a user **result** parameter that is opaque to the API and can be used to +pass extra information to the thread waiting on the event. + +.. code-block:: c + + 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(&signal, 0x1337); + } + +If the signal is to be polled in a loop, *both* its event state and its +**signaled** field *must* be reset on each iteration if it has been signaled. + +.. code-block:: c + + 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; + } + } + +Suggested Uses +************** + +Use :cpp:func:`k_poll()` to consolidate multiple threads that would be pending +on one object each, saving possibly large amounts of stack space. + +Use a poll signal as a lightweight binary semaphore if only one thread pends on +it. + +.. note:: + Because objects are only signaled if no other thread is waiting for them to + become available and only one thread can poll on a specific object, polling + is best used when objects are not subject of contention between multiple + threads, basicallly when a single thread operates as a main "server" or + "dispatcher" for multiple objects and is the only one trying to acquire + these objects. + +Configuration Options +********************* + +Related configuration options: + +* :option:`CONFIG_POLL` + +APIs +**** + +The following polling APIs are provided by :file:`kernel.h`: + +* :c:macro:`K_POLL_EVENT_INITIALIZER` +* :c:macro:`K_POLL_EVENT_STATIC_INITIALIZER` +* :cpp:func:`k_poll_event_init()` +* :cpp:func:`k_poll()` +* :cpp:func:`k_poll_signal_init()` +* :cpp:func:`k_poll_signal()`