目录
本学笔记基于zephyr 工程版本 2.2.99,主机环境为ubuntu18.04,开发平台 nrf52840dk_nrf52840
摘要
熟悉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
转载:https://blog.csdn.net/huohongpeng/article/details/105669566