Zephyr系列文章,翻译自 https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr

人肉翻译,英文水平有限,仅供参考。

本文目录:Zephyr API reference->kernel services ->workqueue threads

https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/zephyr/reference/kernel/threads/workqueue.html


workqueue threads 工作队列线程

工作队列是一个内核对象,它使用专用的线程以先进先出的方式来处理工作项。通过调用工作项指定的函数来处理每个工作项。工作队列通常用在ISR或者高优先级线程里将非紧急的处理放到低优先级的线程中,因此不会影响对时间敏感事件的处理。

可以定义任何数量的工作队列。每个工作队列由其内存地址引用。

工作队列拥有以下关键属性:

  • 已经添加但尚未处理的工作项队列。
  • 处理队列中工作项的线程。线程的优先级是可以配置的,可以根据需要配制成合作型或者抢占型。

工作队列在使用前必须初始化。它将队列清空并产生工作队列的线程。


Work Item Lifecycle 工作项生命周期

可以定义任意数量的工作项。每个工作项由其内存地址引用。

工作项拥有以下关键属性:

  • 回调函数,当工作项被工作队列线程处理时会运行的函数。这个函数接受一个单独的参数,即工作项自己的地址。
  • 挂起标志,内核用来表示工作项是当前工作队列的成员。
  • 队列链接,内核用来将挂起的工作项链接到工作队列下一个挂起的工作项。

 

工作项在使用前必须初始化。这将记录工作项的处理函数,并将它置为非挂起。

 

工作项可以被ISR或线程提交。提交一个工作项,会将工作项添加到工作队列的队列中。一旦工作队列的线程已经处理它队列里所有前面的工作项,线程将从它的队列中移除一个挂起的工作项,并调用它的回调函数。根据工作队列线程的调用优先级,以及队列中其它项目需要的工作,一个挂起的工作项目可能会很快被处理,也可能会在队列中保留较长的一段时间。

回调函数可以使用任何线程可用的内核API。然而可能阻塞的操作(例如获取信号量)必须小心使用,因为直到回调函数处理完成前,工作队列不能处理队列里随后的工作项。

传递给回调函数的这个参数如果不需要的话可以忽略。如果回调函数需要关于工作的额外信息,工作项可以嵌入一个很大的数据结构。然后回调函数可以通过参数计算出封闭数据结构的地址,从而获取它需要的附加信息。

工作项目通常只初始化一次,然后当工作需要被执行时提交给指定的工作队列。如果ISR或县城尝试提交一个已经挂起的工作项目,这个工作项目不受影响;工作项目将保持他在工作队列中的位置,并且工作只会被执行一次。

自工作项不再处于挂起状态后,允许回调函数将工作项参数重新提交给工作队列。这允许回调阶段性的处理工作,不需要工作队列中其他的工作项过分的延时。

挂起的工作项目不可以变改变,直到它被工作队列线程处理。这说明工作项目在挂起的时候不可以被重复初始化。此外的,任何工作项的回调需要处理它的工作的附加信息,在回调执行完成之前,不可以被修改。

没有任何内核API可以用于确定回调函数已经被执行。使用和需要知道工作项的状态的基础结构必须在回调函数中处理状态。


Delayed Work 延时工作

ISR或线程可能会需要安排工作项在指定的时间之后处理,而不是立即处理。这可以通过提交一个延时工作项目给工作队列来实现,而不是一个标准的工作项。

延时工作项是在标准工作项的基础上增加了下面的特性:

  • 在工作项目实际提交给工作队列之前等待一个指定的时间间隔。
  • 一个工作队列指示器,用于识别工作项目提交给的工作队列。

 

延时工作项的初始化和提交跟标准工作项类似,虽然它们使用的内核API不一样。当提交请求发出了,内核将启动一个超时机制,当指定的延时结束之后触发。一旦时间到了内核将提交这个延时工作项给指定的工作队列,在这里它将保持挂起状态直到以标准方式处理为止。

ISR或线程可以尝试取消一个延时工作项。如果成功了,指定的工作将不会被执行。然而,尝试成功取消一个延时工作项只有2个情况:

  • 还没有超过时间并被处理。
  • 它任然处于挂起状态,并在工作队列线程获取它之前成功的将它移除工作队列。

 

由于用于管理队列的锁定是瞬间的,有时候观测不到,但是如果观测到了就会导致取消失败。在这些情况下,工作项可能也可能不被调用。当下面情况时,这个瞬间壮态可以被观测到并引起失败:

  • 工作队列或应用线程是可抢占的。
  • API在ISR中被调用。
  • 这些代码在多处理器系统上运行。

 

注意,k_delayed_work_submit_to_queue()和k_delayed_work_cancel() 尝试取消前面提交的工作项时,可能会失败。当它们失败后,前面提交的工作回调可能会也可能不会被处理。

由于这些竞争条件,所有调用延时工作API的代码都需要检查返回值,并且准备在提交或取消失败时做出反应。


Triggered Work 触发工作

接口k_work_poll_submit()安排一个触发工作项到轮询事件(参考轮询API),当一个被监视的资源可用时或发出轮训信号时或超时发生时,将调用一个用户定义的函数。与k_poll()相反,触发工作不需要等待或主动轮询轮询事件的专用线程。

一个触发工作项是在标准工作项上增加了下面的特性:

  • 指向一系列轮询事件的指针,这些轮询事件将触发工作项向工作队列提交。
  • 一个包含轮询事件的数组的大小。

 

触发工作项的初始化和向工作队列提交与标准的工作项类似,尽管有专门的内核API调用。当提交申请了,内核开始观测被轮询事件指定的内核对象。一旦被观测的内核对象有一个改变了状态,该工作项被提交到指定的工作队列,该工作队列保持挂起直到它以标准形式被处理为止。

 

触发工作项与引用轮询事件的数组必须有效,并且在触发工作项的整个生命周期内不能被修改,从提交到执行或取消。

ISR或线程可以取消一个触发工作项,这个工作项已经被提交了,并且一直在等待轮询事件。这种情形下,内核停止等待附加的轮询事件及还没有被执行的指定工作。否则取消不会被执行。


Implementation 实现

Defining a Workqueue定义一个工作队列

工作队列是使用类型为k_work_q的变量定义的。通过定义其线程使用的栈区域并调用k_work_q_start()来初始化工作队列。栈区域必须使用K_THREAD_STACK_DEFINE来定义,以确保在内存中正确设置。

下面的代码定义并初始化了一个工作队列:

 

Submitting a Work Item提交一个工作项目

工作项是使用类型为k_work的变量定义的。它必须使用k_work_init()来初始化。

一个初始化后的工作项目可以使用k_work_submit()提交到系统工作队列,或者使用k_work_submit_to_queue()提交到指定的工作队列。

下面的代码展示了一个ISR如何将错误信息打印下发到系统工作队列中。注意如果ISR尝试重新提交一个还在挂起的工作项目,工作项将保持不变,并且不会打印相关的错误消息。

 

Submitting a Delayed Work Item提交一个延时工作项

延时工作项是使用类型为k_delayed_work的变量定义的。它必须使用k_delayed_work_init()来初始化。

延时工作项可以使用k_delayed_work_submit()提交给系统工作队列,或者使用k_delayed_work_submit_to_queue()提交给指定工作队列。一个延时工作项目已经被提交,但没有被它的工作队列执行时,可以使用k_delayed_work_cancel()来取消。

注意,所有这些操作如延时工作章节中介绍的,都可能失败。


Suggested Uses建议用法

使用系统队列将ISR里中断相关的复杂处理放到合作线程中延后处理。这允许中断相关的处理立刻被执行,而不影响系统对后续中断的响应能力,并且不需要应用程序定义额外的线程来处理。


Configuration Options配置选项

相关的配置选项:

  • CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE
  • CONFIG_SYSTEM_WORKQUEUE_PRIORITY

本节完。

 

 

发表评论