摘要
    熟悉Linux的童鞋可能对工作队列比较熟悉,zephyr中的工作队列与Linux的工作队列功能类似,用于实现中断的底半部。也就是说中断ISR中比较耗时的操作,放到工作队列中去执行。zephyr中工作队列是基于线程的,简单来说,就是有一个线程一直在等待工作队列的api发来的工作项,当有工作项时(一个待执行的函数)就处理(把函数调用了),当有多个工作项时就按顺序处理,没有工作项时就休眠。

1 工作队列线程 Workqueue Threads
   工作队列是一个内核对象,用专用的线程以先进先出(first in, first out)的方式去处理被提交的工作元素(work item)。每个被处理的工作项会调用这个工作项指定的函数(通俗来讲,工作项就是一个一个等待调用的函数)。工作队列通常用于ISR或者高优先级线程把比较复杂的,非紧急的事情交给对时间不敏感的低于先级线程去处理。对Linux驱动比较了解的童鞋理解起来来叫简单,使用方式类似于Linux的工作队列和Tasklet。

工作队列使用之前必须被初始化,并且清空他的队列创建一个工作队列线程(都是初始化函数来完成的)。

2 工作项生命周期Work Item Lifecycle
    一个工作项(或者叫工作元素,我也不知道怎么翻译好,随便叫吧 )使用之前必须被初始化,初始化就是把处理函数赋值给工作项,并且标记工作项是为挂起的,等着工作队列线程去调用。

一个工作项可以从ISR或者线程中被提交,提交以后就是把工作项追加到工作队列内部的队列中,加入工作队列以后,当工作队列的线程会从他自己的队列中按顺序取出挂起的工作项,然后在执行这个工作项指定的函数。

工作项中可是使用任何的内核的API,但是注意使用那些导致线程阻塞的API,比如睡眠,获取信号量之类的API,这样会导致工作队列线程睡眠,那么同时处于这个工作队列的其他工作项,也将会被延时执行,因为工作项是按顺序,一个一个被串行处理的。如果ISR或者线程重复提交一个已经在工作队列中已经挂起的工作项,那么这个工作项不会受到影响,同时在工作队列中的位置将不会受到影响。

被提交的工作在没有被处理之前,是不能被重新初始化的。

3 延时工作队列 Delayed Work
   当一个ISR或者线程提交一个工作项,但是不想让工作项立即被执行,想让工作项等待一段时间在执行。这时可以使用延时工作项。简单理解就是,一个工作项提交的时候不是直接提交给工作队列,而是指定一个超时,当超时发生的时候,再由内核将这个工作项提交给工作队列,并保持工作项为挂起状态等着工作队列线程去处理。

4 触发工作项Triggered Work
  触发工作项是和POLL机制相关的工作项,用的比较少。POLL机制开发Linux应用的童鞋比较了解,在Linux中POLL机制是在一个线程中等待多个未就绪的文件描述有效,而zephyr是在一个线程中等待多个内核对象有效,比如等待信号量,FIFO等。

5 系统工作队列System Workqueue
   在文中要区分工作队列和工作项的区别,工作项(work item)只是一个被提交到工作队列(work queue)的一个元素。如果内核使能了工作队列的功能,内核会自动创建的一个被称为system workqueuede 的工作队列。这个系统工作队列的线程的优先级可以通过menuconfig去配置。相比于系统通过队列,用户也可以创建自己的工作队列。比如有比较复杂然后特别耗时的事情需要做,那么用户可以自己创建一个工作队列,然后把时间不敏感,不着急处理的事扔给自己创建的工作队列。但是zephyr官方不推荐自己创建工作队列,因为工作队列会消耗大量资源(RAM, flash),毕竟创建一个工作队列就会创建一个线程,一个线程就会包括何种内核对象,私有栈空间等,一般zephyr是跑在资源受限的MCU上的,所以不太推荐。

6 用户定义一个工作队列
 工作队列使用struct k_work_q类型去定义,工作队列需要使用自己定义的栈,然后调用k_work_q_start()去初始化:

#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_q_start(&my_work_q, my_stack_area,
               K_THREAD_STACK_SIZEOF(my_stack_area), MY_PRIORITY);
7 提交一个工作项
    一个工作项在被提交之前需要调用k_work_init()去初始化,然后一个被初始化的工作项可以使用 k_work_submit()函数把工作项提交到系统工作队列,也可以使用k_work_submit_to_queue()函数提交到用户自己定义的工作队列。

下面代码示例,一个ISR把打印信息提交给系统工作队列去执行:

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) */

8 提交一个延时工作项
   一个延时工作项通过struct k_delayed_work去定义,并通过 k_delayed_work_init()去初始化,通过调用k_delayed_work_submit()把延时工作项提交给系统工作队列,通过k_delayed_work_submit_to_queue()把延时工作项提交给用户自己定义的工作队列。当想取消一个超时工作项,可以使用 k_delayed_work_cancel()函数,但是需注意,取消只能在工作项指定的超时没发生之前,否则不能被取消。

9 参考链接
https://docs.zephyrproject.org/latest/reference/kernel/threads/workqueue.html

工作队列概念

工作队列的关键属性:
• 队列:包含若干已经被添加、且还未被处理(译注:在本节后面叫做“挂起的”)工作项。
• 线程:用于处理队列中的工作项。该线程的优先级是可配置的,既可以是协作式也可以是抢占式。
工作队列必须先初始化再使用。初始化时会清空该队列,并创建一个工作队列线程。

工作项的关键属性:
• 处理函数:当工作项被处理时,工作队列线程会执行该函数。该函数接收一个参数——工作项自身的地址。
• 挂起标志:内核使用该标志表示该工作项当前是否是一个工作队列的队列中的一个成员。
• 队列链接:内核使用该链接将其链接到工作队列的队列中的下一个工作项

工作项必须先初始化再使用。初始化时会记录该工作项的处理函数,并将其标记为非挂起

ISR 或者线程可以将某个工作项提交到某个工作队列中。提交工作项时,会将其追加到工作队列的队列中去。当工作队列的线程处理完它队列里面的所有工作项后,该线程会移除一个挂起工作项,并调用该工作项的处理函数。一个挂起的工作项可能很快就会被处理,也可能会在队列中保留一段时间,这依赖于工作队列线程的调度优先级和队列中其它项的工作需求。

处理函数可以利用任何可用的内核API。不过,使用可能引起阻塞的操作(例如拿取一个信号量)时一定要当心,因为工作队列在它的上一个处理函数完成前不能处理其队列中的其它工作项。

如果处理函数不需要参数,可以将接收到的参数直接忽略。如果处理函数需要额外的信息,可以将工作项内嵌到一个更大的数据结构当中。处理函数可以使用这个参数值计算封装后的地址,以此访问额外的信息。

一个工作项通常会被初始化一次,然后当它的工作需要执行的时候会被提交到工作队列中。如果ISR 或者线程尝试提交一个已经挂起的工作项,不会有任何效果;提交后,工作项会停留在工作队列中的当前位置,且只会被执行一次。

处理函数可以将工作项重新提交到工作队列中(因为此时工作项已经不再是挂起状态)。这样做的好处是,处理函数可以分阶段执行工作,而不会导致延迟处理工作队列的队列中的其它工作项。

延迟的工作队列
ISR 或者线程可能需要延迟一段指定的事时间后(而不是立即)再调度一个工作项。向工作队列中提交

延迟工作项
一个延迟的工作项(而不是标准工作项)就能达到此目的。
延迟工作项比标准工作项新增了如下属性:
• 延迟时间:指明需要延迟多久才将工作项提交到工作队列的队列中。
• 工作队列指示器:用于标识需要提交到的工作队列。
延迟工作项的初始化和提交过程与标准的工作项是类似的,只是所使用的内核API 略有区别。当发出提交请求时,内核会初始化一个超时机制,当指定的延迟达到时就会触发它。当超时发送时,内核会将延迟工作项提交到指定的工作队列中。之后,它会保持挂起状态,直到被以标准方式处理。

ISR 或者线程可以取消它提交的延迟工作项,但前提是该工作项的超时计数扔在继续。取消后,超时计数将停止计数,指定的工作也不会被执行。

取消已经到期的延时工作项不会有任何效果;除非工作项被移除并被工作队列的线程处理了,否它将一直保持挂起状态。因此,当工作项的超时服务到期后,它已经被处理过了,所以不能被取消

工作队列的使用方法
1.使用类型为struct k_work_q的变量定义一个工作队列

2:初始化工作队列

A:初始化了一个queue
B:创建了一个线程
C: 系统工作队列是一个协作式线程在处理,

3:定义一个工作项

4:初始化一个工作项
在k_work_init主要是注册函数,标挂起标志位,

在k_work_init主要是注册函数,标挂起标志位,

5:提交工作项到工作队列中
这个主要是把工作项与工作队列相关联

系统的工作队列定义在哪里?
\kernel\system_work_q.c

系统工作队列的初始化的调用是在设备初始化的时候调用
\kernel\init.c

系统工作队列是协作式线程还是抢占式线程?
优先级小于0的线程为协作式线程, 系统工作队列的优先级为-1,所以是协作式线程

协作式线程必须主动让出CPU的使用权,可以在处理函数里面调用k_yield,k_sleep.所以在协作式线程函数work_q_main()中,调用了k_yield()

调用k_yield() 将线程放到调度器维护的按照优先级排列的就绪线程链表中,然后调用调度器。在该线程被再次调度前,所有优先级高于或等于该线程的就绪线程都将得以执行。如果不存在优先级更高或相等的线程,调度器将不会进行上下文切换,立即再次调度该线程。

调用k_sleep()让该线程在一段指定时间内变为非就绪线程。所有优先级的就绪线程都可能得以执行;不过,不能保证优先级低于该睡眠线程的其它线程都能在睡眠线程再次变为就绪线程前执行完。

zephyr 工作队列相关推荐

  1. zephyr笔记 2.1.5 工作队列线程

    我正在学习 Zephyr,一个很可能会用到很多物联网设备上的操作系统,如果你也感兴趣,可点此查看帖子zephyr学习笔记汇总. 1 前言 工作队列是一个内核对象,它使用专用线程以先进先出的方式处理工作 ...

  2. zephyr笔记 2.2.2 定时器

    1 前言 计时器是一个内核对象,它使用内核的系统时钟来度量时间的流逝. 当达到定时器的指定时间限制时,它可以执行应用程序定义的操作,或者它可以简单地记录到期并等待应用程序读取其状态. 我正在学习 Ze ...

  3. 设计一条简单的等待工作队列之软件模型设计与实现(二)

    上节实现了一条最简单的线程等待工作队列. http://blog.csdn.net/morixinguan/article/details/77758206 但设计还有诸多因素需要考虑和改进,例如以下 ...

  4. Linux2.6内核--中断下半部实现方法 工作队列

          工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行由内核其他部分排到队列里的任务.它创建的这些内核线程称作工作者线程.工作队列可以让你的驱动程序创建一个专门的工作者线程来 ...

  5. 可延迟函数、内核微线程以及工作队列

    本文研究多个用于在内核环境当中延迟处理的方法(特别是在 Linux 内核版本 2.6.27.14 当中). 尽管这些方法针对 Linux 内核,但方法背后的理念, 对于系统架构研究具有更广泛的意义.例 ...

  6. 下半部机制之工作队列

    工作队列是一种不同于软中断和微线程的一种下半部延迟机制.工作队列将工作延迟到一个内核线程中执行,它运行在进程上下文中,它是可调度的,并且可以休眠.通常,如果延迟的工作中需要休眠,就使用工作队列,否则使 ...

  7. linux平台下 延迟工作队列实例

    工作队列(work queue)是Linux内核中将操作延期执行的一种机制.因为它们是通过守护进程在用户上下文执行,函数可以睡眠的时间,与内核是无关的.在内核版本2.5开发期间,设计了工作队列,用以替 ...

  8. RabbitMQ 官方NET教程(二)【工作队列】

    这篇中我们将会创建一个工作队列用来在工作者(consumer)间分发耗时任务. 工作队列的主要任务是:避免立刻执行资源密集型任务和避免必须等待其完成.相反地,我们进行任务调度:我们把任务封装为消息发送 ...

  9. Zephyr内核到1.5版本的改进

    Zephyr内核从1.0到1.5版本发生了很大改变,这些改变给开发人员带来了很大方便,具体如下: ①消除微内核和超微内核构建类型的分离 ②消除微内核应用程序中的MDEF ③更简单易用的内核API ④宽 ...

最新文章

  1. JavaScript数据类型
  2. ABAP动态生成经典应用之Table数据Upload 程序
  3. 文件下载--服务器端编程操作
  4. 定时任务四种实现方式
  5. spring mvc学习(19):cookievalue注解(显示cookie的值,默认必须有值)
  6. Qt+ArcGIS Engine 10.1 开发(一)
  7. Linux下实现多线程异步管道
  8. 在React中测试和调试
  9. 【辨异】inner, internal, interior, inward
  10. 计算机图形图像项目教程素材,计算机图形图像应用教程
  11. 2022考研资料每日更新(2021.07.28)
  12. 解决白天黑夜模式切换导致Fragment崩溃问题
  13. 饭后七个好习惯吃饱吃好不发胖
  14. IoT Analytics:物联网2020年回顾,十大重要进展
  15. 构造和析构:construct,destory
  16. 是面试官放水,还是实在公司太缺人?这都没挂,阿里巴巴原来这么容易进...
  17. 《前端技巧》清理微信浏览网站的缓存,Cookie
  18. 如何制作wordpress短代码
  19. ISO26262解析(十二)——HARA分析
  20. python第三方库文件传输_慢步学习,python库文件概述,再来点第三方库文件安装的干货...

热门文章

  1. php image gallery in metro ui,终于搞明白糟糕的METRO UI是怎么回事了
  2. 前端经典面试题:js必懂的原型和原型链
  3. 案例11:高层综合楼防火案例分析(一)
  4. 读书记录|《2001 太空漫游》
  5. Html 制作简易时钟
  6. java ee会员功能项目_基于jsp的会员管理-JavaEE实现会员管理 - java项目源码
  7. 以太坊 云养鱼 Fishbank
  8. AutoLisp从入门到放弃(十六)
  9. 深入浅出matplotlib(25):模块patches绘制几何图形
  10. 《高效时间管理》--司铭宇老师