| .. _workqueues_v2: |
| |
| Workqueue Threads |
| ################# |
| |
| .. contents:: |
| :local: |
| :depth: 1 |
| |
| A :dfn:`workqueue` is a kernel object that uses a dedicated thread to process |
| work items in a first in, first out manner. Each work item is processed by |
| calling the function specified by the work item. A workqueue is typically |
| used by an ISR or a high-priority thread to offload non-urgent processing |
| to a lower-priority thread so it does not impact time-sensitive processing. |
| |
| Any number of workqueues can be defined (limited only by available RAM). Each |
| workqueue is referenced by its memory address. |
| |
| A workqueue has the following key properties: |
| |
| * A **queue** of work items that have been added, but not yet processed. |
| |
| * A **thread** that processes the work items in the queue. The priority of the |
| thread is configurable, allowing it to be either cooperative or preemptive |
| as required. |
| |
| Regardless of workqueue thread priority the workqueue thread will yield |
| between each submitted work item, to prevent a cooperative workqueue from |
| starving other threads. |
| |
| A workqueue must be initialized before it can be used. This sets its queue to |
| empty and spawns the workqueue's thread. The thread runs forever, but sleeps |
| when no work items are available. |
| |
| .. note:: |
| The behavior described here is changed from the Zephyr workqueue |
| implementation used prior to release 2.6. Among the changes are: |
| |
| * Precise tracking of the status of cancelled work items, so that the |
| caller need not be concerned that an item may be processing when the |
| cancellation returns. Checking of return values on cancellation is still |
| required. |
| * Direct submission of delayable work items to the queue with |
| :c:macro:`K_NO_WAIT` rather than always going through the timeout API, |
| which could introduce delays. |
| * The ability to wait until a work item has completed or a queue has been |
| drained. |
| * Finer control of behavior when scheduling a delayable work item, |
| specifically allowing a previous deadline to remain unchanged when a work |
| item is scheduled again. |
| * Safe handling of work item resubmission when the item is being processed |
| on another workqueue. |
| |
| Using the return values of :c:func:`k_work_busy_get()` or |
| :c:func:`k_work_is_pending()`, or measurements of remaining time until |
| delayable work is scheduled, should be avoided to prevent race conditions |
| of the type observed with the previous implementation. See also `Workqueue |
| Best Practices`_. |
| |
| Work Item Lifecycle |
| ******************** |
| |
| Any number of **work items** can be defined. Each work item is referenced |
| by its memory address. |
| |
| A work item is assigned a **handler function**, which is the function |
| executed by the workqueue's thread when the work item is processed. This |
| function accepts a single argument, which is the address of the work item |
| itself. The work item also maintains information about its status. |
| |
| A work item must be initialized before it can be used. This records the work |
| item's handler function and marks it as not pending. |
| |
| A work item may be **queued** (:c:macro:`K_WORK_QUEUED`) by submitting it to a |
| workqueue by an ISR or a thread. Submitting a work item appends the work item |
| to the workqueue's queue. Once the workqueue's thread has processed all of |
| the preceding work items in its queue the thread will remove the next work |
| item from the queue and invoke the work item's handler function. Depending on |
| the scheduling priority of the workqueue's thread, and the work required by |
| other items in the queue, a queued work item may be processed quickly or it |
| may remain in the queue for an extended period of time. |
| |
| A delayable work item may be **scheduled** (:c:macro:`K_WORK_DELAYED`) to a |
| workqueue; see `Delayable Work`_. |
| |
| A work item will be **running** (:c:macro:`K_WORK_RUNNING`) when it is running |
| on a work queue, and may also be **canceling** (:c:macro:`K_WORK_CANCELING`) |
| if it started running before a thread has requested that it be cancelled. |
| |
| A work item can be in multiple states; for example it can be: |
| |
| * running on a queue; |
| * marked canceling (because a thread used :c:func:`k_work_cancel_sync()` to |
| wait until the work item completed); |
| * queued to run again on the same queue; |
| * scheduled to be submitted to a (possibly different) queue |
| |
| *all simultaneously*. A work item that is in any of these states is **pending** |
| (:c:func:`k_work_is_pending()`) or **busy** (:c:func:`k_work_busy_get()`). |
| |
| A handler function can use any kernel API available to threads. However, |
| operations that are potentially blocking (e.g. taking a semaphore) must be |
| used with care, since the workqueue cannot process subsequent work items in |
| its queue until the handler function finishes executing. |
| |
| The single argument that is passed to a handler function can be ignored if it |
| is not required. If the handler function requires additional information about |
| the work it is to perform, the work item can be embedded in a larger data |
| structure. The handler function can then use the argument value to compute the |
| address of the enclosing data structure with :c:macro:`CONTAINER_OF`, and |
| thereby obtain access to the additional information it needs. |
| |
| A work item is typically initialized once and then submitted to a specific |
| workqueue whenever work needs to be performed. If an ISR or a thread attempts |
| to submit a work item that is already queued the work item is not affected; |
| the work item remains in its current place in the workqueue's queue, and |
| the work is only performed once. |
| |
| A handler function is permitted to re-submit its work item argument |
| to the workqueue, since the work item is no longer queued at that time. |
| This allows the handler to execute work in stages, without unduly delaying |
| the processing of other work items in the workqueue's queue. |
| |
| .. important:: |
| A pending work item *must not* be altered until the item has been processed |
| by the workqueue thread. This means a work item must not be re-initialized |
| while it is busy. Furthermore, any additional information the work item's |
| handler function needs to perform its work must not be altered until |
| the handler function has finished executing. |
| |
| .. _k_delayable_work: |
| |
| Delayable Work |
| ************** |
| |
| An ISR or a thread may need to schedule a work item that is to be processed |
| only after a specified period of time, rather than immediately. This can be |
| done by **scheduling** a **delayable work item** to be submitted to a |
| workqueue at a future time. |
| |
| A delayable work item contains a standard work item but adds fields that |
| record when and where the item should be submitted. |
| |
| A delayable work item is initialized and scheduled to a workqueue in a similar |
| manner to a standard work item, although different kernel APIs are used. When |
| the schedule request is made the kernel initiates a timeout mechanism that is |
| triggered after the specified delay has elapsed. Once the timeout occurs the |
| kernel submits the work item to the specified workqueue, where it remains |
| queued until it is processed in the standard manner. |
| |
| Note that work handler used for delayable still receives a pointer to the |
| underlying non-delayable work structure, which is not publicly accessible from |
| :c:struct:`k_work_delayable`. To get access to an object that contains the |
| delayable work object use this idiom: |
| |
| .. code-block:: c |
| |
| static void work_handler(struct k_work *work) |
| { |
| struct k_work_delayable *dwork = k_work_delayable_from_work(work); |
| struct work_context *ctx = CONTAINER_OF(dwork, struct work_context, |
| timed_work); |
| ... |
| |
| |
| Triggered Work |
| ************** |
| |
| The :c:func:`k_work_poll_submit` interface schedules a triggered work |
| item in response to a **poll event** (see :ref:`polling_v2`), that will |
| call a user-defined function when a monitored resource becomes available |
| or poll signal is raised, or a timeout occurs. |
| In contrast to :c:func:`k_poll`, the triggered work does not require |
| a dedicated thread waiting or actively polling for a poll event. |
| |
| A triggered work item is a standard work item that has the following |
| added properties: |
| |
| * A pointer to an array of poll events that will trigger work item |
| submissions to the workqueue |
| |
| * A size of the array containing poll events. |
| |
| A triggered work item is initialized and submitted to a workqueue in a similar |
| manner to a standard work item, although dedicated kernel APIs are used. |
| When a submit request is made, the kernel begins observing kernel objects |
| specified by the poll events. Once at least one of the observed kernel |
| object's changes state, the work item is submitted to the specified workqueue, |
| where it remains queued until it is processed in the standard manner. |
| |
| .. important:: |
| The triggered work item as well as the referenced array of poll events |
| have to be valid and cannot be modified for a complete triggered work |
| item lifecycle, from submission to work item execution or cancellation. |
| |
| An ISR or a thread may **cancel** a triggered work item it has submitted |
| as long as it is still waiting for a poll event. In such case, the kernel |
| stops waiting for attached poll events and the specified work is not executed. |
| Otherwise the cancellation cannot be performed. |
| |
| System Workqueue |
| ***************** |
| |
| The kernel defines a workqueue known as the *system workqueue*, which is |
| available to any application or kernel code that requires workqueue support. |
| The system workqueue is optional, and only exists if the application makes |
| use of it. |
| |
| .. important:: |
| Additional workqueues should only be defined when it is not possible |
| to submit new work items to the system workqueue, since each new workqueue |
| incurs a significant cost in memory footprint. A new workqueue can be |
| justified if it is not possible for its work items to co-exist with |
| existing system workqueue work items without an unacceptable impact; |
| for example, if the new work items perform blocking operations that |
| would delay other system workqueue processing to an unacceptable degree. |
| |
| How to Use Workqueues |
| ********************* |
| |
| Defining and Controlling a Workqueue |
| ==================================== |
| |
| A workqueue is defined using a variable of type :c:struct:`k_work_q`. |
| The workqueue is initialized by defining the stack area used by its |
| thread, initializing the :c:struct:`k_work_q`, either zeroing its |
| memory or calling :c:func:`k_work_queue_init`, and then calling |
| :c:func:`k_work_queue_start`. The stack area must be defined using |
| :c:macro:`K_THREAD_STACK_DEFINE` to ensure it is properly set up in |
| memory. |
| |
| The following code defines and initializes a workqueue: |
| |
| .. code-block:: c |
| |
| #define MY_STACK_SIZE 512 |
| #define MY_PRIORITY 5 |
| |
| K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE); |
| |
| struct k_work_q my_work_q; |
| |
| k_work_queue_init(&my_work_q); |
| |
| k_work_queue_start(&my_work_q, my_stack_area, |
| K_THREAD_STACK_SIZEOF(my_stack_area), MY_PRIORITY, |
| NULL); |
| |
| In addition the queue identity and certain behavior related to thread |
| rescheduling can be controlled by the optional final parameter; see |
| :c:struct:`k_work_queue_start()` for details. |
| |
| The following API can be used to interact with a workqueue: |
| |
| * :c:func:`k_work_queue_drain()` can be used to block the caller until the |
| work queue has no items left. Work items resubmitted from the workqueue |
| thread are accepted while a queue is draining, but work items from any other |
| thread or ISR are rejected. The restriction on submitting more work can be |
| extended past the completion of the drain operation in order to allow the |
| blocking thread to perform additional work while the queue is "plugged". |
| Note that draining a queue has no effect on scheduling or processing |
| delayable items, but if the queue is plugged and the deadline expires the |
| item will silently fail to be submitted. |
| * :c:func:`k_work_queue_unplug()` removes any previous block on submission to |
| the queue due to a previous drain operation. |
| |
| Submitting a Work Item |
| ====================== |
| |
| A work item is defined using a variable of type :c:struct:`k_work`. It must |
| be initialized by calling :c:func:`k_work_init`, unless it is defined using |
| :c:macro:`K_WORK_DEFINE` in which case initialization is performed at |
| compile-time. |
| |
| An initialized work item can be submitted to the system workqueue by |
| calling :c:func:`k_work_submit`, or to a specified workqueue by |
| calling :c:func:`k_work_submit_to_queue`. |
| |
| The following code demonstrates how an ISR can offload the printing |
| of error messages to the system workqueue. Note that if the ISR attempts |
| to resubmit the work item while it is still queued, the work item is left |
| unchanged and the associated error message will not be printed. |
| |
| .. code-block:: c |
| |
| struct device_info { |
| struct k_work work; |
| char name[16] |
| } my_device; |
| |
| void my_isr(void *arg) |
| { |
| ... |
| if (error detected) { |
| k_work_submit(&my_device.work); |
| } |
| ... |
| } |
| |
| void print_error(struct k_work *item) |
| { |
| struct device_info *the_device = |
| CONTAINER_OF(item, struct device_info, work); |
| printk("Got error on device %s\n", the_device->name); |
| } |
| |
| /* initialize name info for a device */ |
| strcpy(my_device.name, "FOO_dev"); |
| |
| /* initialize work item for printing device's error messages */ |
| k_work_init(&my_device.work, print_error); |
| |
| /* install my_isr() as interrupt handler for the device (not shown) */ |
| ... |
| |
| |
| The following API can be used to check the status of or synchronize with the |
| work item: |
| |
| * :c:func:`k_work_busy_get()` returns a snapshot of flags indicating work item |
| state. A zero value indicates the work is not scheduled, submitted, being |
| executed, or otherwise still being referenced by the workqueue |
| infrastructure. |
| * :c:func:`k_work_is_pending()` is a helper that indicates ``true`` if and only |
| if the work is scheduled, queued, or running. |
| * :c:func:`k_work_flush()` may be invoked from threads to block until the work |
| item has completed. It returns immediately if the work is not pending. |
| * :c:func:`k_work_cancel()` attempts to prevent the work item from being |
| executed. This may or may not be successful. This is safe to invoke |
| from ISRs. |
| * :c:func:`k_work_cancel_sync()` may be invoked from threads to block until |
| the work completes; it will return immediately if the cancellation was |
| successful or not necessary (the work wasn't submitted or running). This |
| can be used after :c:func:`k_work_cancel()` is invoked (from an ISR)` to |
| confirm completion of an ISR-initiated cancellation. |
| |
| Scheduling a Delayable Work Item |
| ================================ |
| |
| A delayable work item is defined using a variable of type |
| :c:struct:`k_work_delayable`. It must be initialized by calling |
| :c:func:`k_work_init_delayable`. |
| |
| For delayed work there are two common use cases, depending on whether a |
| deadline should be extended if a new event occurs. An example is collecting |
| data that comes in asynchronously, e.g. characters from a UART associated with |
| a keyboard. There are two APIs that submit work after a delay: |
| |
| * :c:func:`k_work_schedule()` (or :c:func:`k_work_schedule_for_queue()`) |
| schedules work to be executed at a specific time or after a delay. Further |
| attempts to schedule the same item with this API before the delay completes |
| will not change the time at which the item will be submitted to its queue. |
| Use this if the policy is to keep collecting data until a specified delay |
| since the **first** unprocessed data was received; |
| * :c:func:`k_work_reschedule()` (or :c:func:`k_work_reschedule_for_queue()`) |
| unconditionally sets the deadline for the work, replacing any previous |
| incomplete delay and changing the destination queue if necessary. Use this |
| if the policy is to keep collecting data until a specified delay since the |
| **last** unprocessed data was received. |
| |
| If the work item is not scheduled both APIs behave the same. If |
| :c:macro:`K_NO_WAIT` is specified as the delay the behavior is as if the item |
| was immediately submitted directly to the target queue, without waiting for a |
| minimal timeout (unless :c:func:`k_work_schedule()` is used and a previous |
| delay has not completed). |
| |
| Both also have variants that allow |
| control of the queue used for submission. |
| |
| The helper function :c:func:`k_work_delayable_from_work()` can be used to get |
| a pointer to the containing :c:struct:`k_work_delayable` from a pointer to |
| :c:struct:`k_work` that is passed to a work handler function. |
| |
| The following additional API can be used to check the status of or synchronize |
| with the work item: |
| |
| * :c:func:`k_work_delayable_busy_get()` is the analog to :c:func:`k_work_busy_get()` |
| for delayable work. |
| * :c:func:`k_work_delayable_is_pending()` is the analog to |
| :c:func:`k_work_is_pending()` for delayable work. |
| * :c:func:`k_work_flush_delayable()` is the analog to :c:func:`k_work_flush()` |
| for delayable work. |
| * :c:func:`k_work_cancel_delayable()` is the analog to |
| :c:func:`k_work_cancel()` for delayable work; similarly with |
| :c:func:`k_work_cancel_delayable_sync()`. |
| |
| Synchronizing with Work Items |
| ============================= |
| |
| While the state of both regular and delayable work items can be determined |
| from any context using :c:func:`k_work_busy_get()` and |
| :c:func:`k_work_delayable_busy_get()` some use cases require synchronizing |
| with work items after they've been submitted. :c:func:`k_work_flush()`, |
| :c:func:`k_work_cancel_sync()`, and :c:func:`k_work_cancel_delayable_sync()` |
| can be invoked from thread context to wait until the requested state has been |
| reached. |
| |
| These APIs must be provided with a :c:struct:`k_work_sync` object that has no |
| application-inspectable components but is needed to provide the |
| synchronization objects. These objects should not be allocated on a stack if |
| the code is expected to work on architectures with |
| :kconfig:`CONFIG_KERNEL_COHERENCE`. |
| |
| Workqueue Best Practices |
| ************************ |
| |
| Avoid Race Conditions |
| ===================== |
| |
| Sometimes the data a work item must process is naturally thread-safe, for |
| example when it's put into a :c:struct:`k_queue` by some thread and processed |
| in the work thread. More often external synchronization is required to avoid |
| data races: cases where the work thread might inspect or manipulate shared |
| state that's being accessed by another thread or interrupt. Such state might |
| be a flag indicating that work needs to be done, or a shared object that is |
| filled by an ISR or thread and read by the work handler. |
| |
| For simple flags :ref:`atomic_v2` may be sufficient. In other cases spin |
| locks (:c:struct:`k_spinlock_t`) or thread-aware locks (:c:struct:`k_sem`, |
| :c:struct:`k_mutex` , ...) may be used to ensure data races don't occur. |
| |
| If the selected lock mechanism can :ref:`api_term_sleep` then allowing the |
| work thread to sleep will starve other work queue items, which may need to |
| make progress in order to get the lock released. Work handlers should try to |
| take the lock with its no-wait path. For example: |
| |
| .. code-block:: c |
| |
| static void work_handler(struct work *work) |
| { |
| struct work_context *parent = CONTAINER_OF(work, struct work_context, |
| work_item); |
| |
| if (k_mutex_lock(&parent->lock, K_NO_WAIT) != 0) { |
| /* NB: Submit will fail if the work item is being cancelled. */ |
| (void)k_work_submit(work); |
| return; |
| } |
| |
| /* do stuff under lock */ |
| k_mutex_unlock(&parent->lock); |
| /* do stuff without lock */ |
| } |
| |
| Be aware that if the lock is held by a thread with a lower priority than the |
| work queue the resubmission may starve the thread that would release the lock, |
| causing the application to fail. Where the idiom above is required a |
| delayable work item is preferred, and the work should be (re-)scheduled with a |
| non-zero delay to allow the thread holding the lock to make progress. |
| |
| Note that submitting from the work handler can fail if the work item had been |
| cancelled. Generally this is acceptable, since the cancellation will complete |
| once the handler finishes. If it is not, the code above must take other steps |
| to notify the application that the work could not be performed. |
| |
| Work items in isolation are self-locking, so you don't need to hold an |
| external lock just to submit or schedule them. Even if you use external state |
| protected by such a lock to prevent further resubmission, it's safe to do the |
| resubmit as long as you're sure that eventually the item will take its lock |
| and check that state to determine whether it should do anything. Where a |
| delayable work item is being rescheduled in its handler due to inability to |
| take the lock some other self-locking state, such as an atomic flag set by the |
| application/driver when the cancel is initiated, would be required to detect |
| the cancellation and avoid the cancelled work item being submitted again after |
| the deadline. |
| |
| Check Return Values |
| =================== |
| |
| All work API functions return status of the underlying operation, and in many |
| cases it is important to verify that the intended result was obtained. |
| |
| * Submitting a work item (:c:func:`k_work_submit_to_queue`) can fail if the |
| work is being cancelled or the queue is not accepting new items. If this |
| happens the work will not be executed, which could cause a subsystem that is |
| animated by work handler activity to become non-responsive. |
| * Asynchronous cancellation (:c:func:`k_work_cancel` or |
| :c:func:`k_work_cancel_delayable`) can complete while the work item is still |
| being run by a handler. Proceeding to manipulate state shared with the work |
| handler will result in data races that can cause failures. |
| |
| Many race conditions have been present in Zephyr code because the results of |
| an operation were not checked. |
| |
| There may be good reason to believe that a return value indicating that the |
| operation did not complete as expected is not a problem. In those cases the |
| code should clearly document this, by (1) casting the return value to ``void`` |
| to indicate that the result is intentionally ignored, and (2) documenting what |
| happens in the unexpected case. For example: |
| |
| .. code-block:: c |
| |
| /* If this fails, the work handler will check pub->active and |
| * exit without transmitting. |
| */ |
| (void)k_work_cancel_delayable(&pub->timer); |
| |
| However in such a case the following code must still avoid data races, as it |
| cannot guarantee that the work thread is not accessing work-related state. |
| |
| Don't Optimize Prematurely |
| ========================== |
| |
| The workqueue API is designed to be safe when invoked from multiple threads |
| and interrupts. Attempts to externally inspect a work item's state and make |
| decisions based on the result are likely to create new problems. |
| |
| So when new work comes in, just submit it. Don't attempt to "optimize" by |
| checking whether the work item is already submitted by inspecting snapshot |
| state with :c:func:`k_work_is_pending` or :c:func:`k_work_busy_get`, or |
| checking for a non-zero delay from |
| :c:func:`k_work_delayable_remaining_get()`. Those checks are fragile: a "busy" |
| indication can be obsolete by the time the test is returned, and a "not-busy" |
| indication can also be wrong if work is submitted from multiple contexts, or |
| (for delayable work) if the deadline has completed but the work is still in |
| queued or running state. |
| |
| A general best practice is to always maintain in shared state some condition |
| that can be checked by the handler to confirm whether there is work to be |
| done. This way you can use the work handler as the standard cleanup path: |
| rather than having to deal with cancellation and cleanup at points where items |
| are submitted, you may be able to have everything done in the work handler |
| itself. |
| |
| A rare case where you could safely use :c:func:`k_work_is_pending` is as a |
| check to avoid invoking :c:func:`k_work_flush` or |
| :c:func:`k_work_cancel_sync`, if you are *certain* that nothing else might |
| submit the work while you're checking (generally because you're holding a lock |
| that prevents access to state used for submission). |
| |
| Suggested Uses |
| ************** |
| |
| Use the system workqueue to defer complex interrupt-related processing from an |
| ISR to a shared thread. This allows the interrupt-related processing to be |
| done promptly without compromising the system's ability to respond to |
| subsequent interrupts, and does not require the application to define and |
| manage an additional thread to do the processing. |
| |
| Configuration Options |
| ********************** |
| |
| Related configuration options: |
| |
| * :kconfig:`CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE` |
| * :kconfig:`CONFIG_SYSTEM_WORKQUEUE_PRIORITY` |
| * :kconfig:`CONFIG_SYSTEM_WORKQUEUE_NO_YIELD` |
| |
| API Reference |
| ************** |
| |
| .. doxygengroup:: workqueue_apis |