工作队列线程
工作队列是一个内核对象,专门用来处理先进先出管理器管理的工作项成员。每一个工作项都是通过调用它指定的函数来处理的。工作队列的典型应用是在中断函数或者高优先级线程中把一些非紧急任务分给低优先级线程处理,这样非紧急任务就不会对时间敏感的任务造成影响了
工作队列线程相关概念
我们可以定义任意数量的工作队列。每个工作队列都通过它的内存地址引用。
一个工作队列有一下几个主要属性:
①队列中的工作项都是被添加进去、但是还没被处理的
②队列中的工作项是通过一个线程处理的,这个线程的优先级是可配置的,我们可以把它配置成协作式线程或者抢占式线程
工作队列在使用之前需要进行初始化。初始化时把工作队列设置为空并且派生了一个工作队列线程。
工作项生活周期
我们可以定义任意数量的工作项。每一个工作项通过他的内存地址来引用。
一个工作项有以下几个主要属性:
①一个处理函数,当工作线程处理工作项成员时这个函数就会被调用。这个函数有一个参数,这个参数是工作项的内存地址。
②一个挂起标志,内核使用这个标志表示当前的一个工作项是工作队列的一个成员
③一个队列链表,内核通过这个队列链表把工作队列中所有挂起项链接起来
一个工作项在使用前需要进行初始化,初始化时会记录工作项的处理函数并标记成非挂起状态
一个工作项可以在中断处理函数或者线程中提交到工作队列中,新提交的工作项会被追加到工作队列结尾。一旦工作队列线程处理完之前队列中的工作项后,线程将会从队列中移除一个挂起的工作项并调用工作项的处理函数。取决于工作队列线程的优先级和队列中其他成员的工作请求,一个工作项可能会被立即处理或者是在队列中等待一段时间。
工作队列的处理函数可以调用所有内核APIs。然而,我们需要小心操作被阻塞,因为工作队列在处理完当前处理函数之前是不能处理队列中其它工作项的(所以如果调用了阻塞是接口,那么工作队列中其它的队列项将会被阻塞不能被处理)
工作队列处理函数有一个函数参数,如果不使用可以忽略它。如果执行处理函数时请求一些额外的信息,工作项可以嵌入在一个大的数据构体中。处理函数可以通过这个参数值计算出大的数据构体地址,这样就可以访问一些需要的额外信息了
当有一个工作需要执行时可以先初始化一个工作项然后提交到指定的工作队列中。如果一个中断服务函数或者一个线程尝试提交一个已经挂起的工作项,是不会对这个工作项产生影响的,这个工作项会保持在当前工作队列中的位置,并且只会被执行一次
在工作项不是挂起状态时,可以重新提交处理函数的参数到工作队列中。这允许处理函数在工作执行阶段不会对工作队列中其他工作成员的处理造成过度延迟
*********重要注释*********
直到工作项被工作队列线程处理之前,一个挂起的工作项不能被告警(alert),这意味着一个工作项在挂起的时候不能被重新初始化。因此,工作项处理函数执行它的工作时需要的一些额外信息在处理函数执行完之前不可以被告警(alert)
延时工作
一个中断服务函数或者线程需要一个能在延时一个指定时间段后再被调用的工作项,而不是立即执行。可以通过提交一个延时工作项到工作队列中实现
一个延时工作项和标准工作项相比,有如下一些额外的属性:
①一个延时时间,这个延时时间指定的是延时工作项在提交到工作队列前需要延时的时间长度
②一个工作队列标示符,表示当前工作项需要被提交到哪个工作队列中
虽然谈事工作项和标准工作项操作时使用了不同的内核APIs,但是他们的初始化和提交到工作队列的操作是很相似的。当发出一个提交请求时内核会启动一个超市机制,当指定的时间到达时会被触发。当超时出现时内核会提交一个延时工作项到它指定的工作队列中、这个工作项在被处理之前会一直以挂起的状态保留在工作队列中
一个中断服务函数或者线程可以在延时工作项超时之前关闭这个工作项任务。这样,这个工作项超时任务会被异步终止并且不会执行指定的工作
如果关闭一个超时时间已经达到的延时工作项是不会对它产生任何影响的。一个工作项会维持在原来的工作队列中的挂起状态、除非工作项已经被移除并且被工作线程处理完毕。因此,一旦延时工作项的超时已经到达,那么这个工作项总是会被处理且不可以被取消
系统工作队列
内核定义了一个叫做系统工作队列的工作队列,这个工作队列可以被需要工作队列的应用程序或者内核使用。系统工作队列是可选功能,并且只有在应用程序使用它的时候才存在
*********重要注释*********
只有在一些新的工作项不能提交到系统工作队列中时才需要定义一些额外的工作队列,因为一共新的工作队列会有很多内存开销。只有在一个新的工作项和系统工作项中已经存在的工作项不可共存、有着不可接受的影响时才会定义新的工作队列。例如,如果一个新的工作项因为执行了阻塞操作引起系统工作队列延时了一个不可接受的时间时,就会定义新的工作队列
工作队列的实现
定义一个工作队列
一个工作队列通过 struct k_work_q 变量来定义。工作队列通过它的线程定义一个堆栈区域然后调用 k_work_q_start()来初始化。定义堆栈区域时需要使用K_THREAD_STACK_DEFINE宏定义,确保能正确设置堆栈内存空间
下面的代码定义并初始化了一个工作队列:
#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);
提交一个工作项
一个工作项通过首先需要通过struct k_work变量来定义,然后调用k_work_init()接口来初始化
一个已经初始化的工作项可以通过调用k_work_submit()提交到系统工作队列中,或者调用k_work_submit_to_queue()接口提交到指定的工作队列中
下面的代码演示了如何通过中断服务函数把打印错误消息函数移交到系统工作队列中。如果中断服务函数尝试重新提交一个仍然挂起的工作项,这个工作项是不会被改变的并且相关的错误信息也不会被打印
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) */ ...
提交一个延时工作项
一个延时工作项通过struct k_delayed_work变量来定义,然后通过k_delayed_work_init()接口初始化这个工作项
一个初始化的工作项可以通过调用k_delayed_work_submit()提交到系统工作队列中,或者调用k_delayed_work_submit_to_queue()提交到指定的工作队列中。一个延时工作项可以在提交到工作队列之前的延时期间通过调用k_delayed_work_cancel()接口取消提交请求
推荐用法
使用系统工作队列可以从中断处理函数中推迟一些和中断相关的复杂处理到协作式线程中。这样可以及时处理和中断相关操作的同时响应后续到来的中断,并且还不需要应用程序定义额外的线程来处理(而是通过系统工作队列线程来处理)
配置选项
相关的配置选项:
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE
CONFIG_SYSTEM_WORKQUEUE_PRIORITY
APIs
k_work_q_start()
k_work_init()
k_work_submit()
k_work_submit_to_queue()
k_delayed_work_init()
k_delayed_work_submit()
k_delayed_work_submit_to_queue()
k_delayed_work_cancel()
k_work_pending()