工作队列(workqueue
)是一个使用特定线程来运行工作项(work items
)的内核对象,其方式为先进先出,通过调用工作项指定的函数来处理每个工作项。工作队列的典型应用是在中断或者高优先级线程中去分担部分工作到一个低优先级的线程中,其目的是减少中断或高优先级线程的处理时长,所以它不影响时间敏感的处理。
可以定义无上限的工作队列(仅受RAM限制)。每个工作队列通过地址引用。
工作队列有下列关键属性:
不管工作队列优先级如何,工作队列线程将在提交工作项之后进行让出(yield)CPU,防止协作式工作队列饿死其他线程。
工作队列必须在使用前初始化。工作队列创建时会清空队列和创建一个工作队列的线程。这个线程在没有工作项时睡眠,其他时间处于就绪态。
可以定义无上限的工作项。每个工作项可以通过地址进行引用。
每个工作项都被分配一个处理函数(handler function
),这是工作队列线程在执行工作项时执行的函数。处理函数唯一的形参就是工作项句柄,可在工作项句柄中获取工作项状态。
工作项必须在使用前初始化。初始化时工作项时会分配处理函数,然后标记为未完成。
在ISR或线程中,工作项可被添加到工作队列中,此时工作项处于K_WORK_QUEUED
状态。
一旦工作队列的线程处理完其队列中所有前面的工作项,该线程将从工作队列中移除下一个工作项并调用该工作项的处理函数。
根据工作队列线程的调度优先级,以及队列中其他工作项所需的时间长短,那么队列中的工作项可能被迅速处理,也可能在队列中停留较长的一段时间。
工作项可以标记为延迟工作项(K_WORK_DELAYED
),参考可延迟工作。
当工作队列正在运行时,工作项可被执行(K_WORK_RUNNING
)。如果该工作项还未被执行,也可被取消(K_WORK_CANCELING
)。
工作项可处于多种状态,比如说:
K_WORK_RUNNING
)K_WORK_CANCELING
)可以通过k_work_is_pending()
或k_work_busy_get()
获取工作项状态。
处理函数可以调用任何适用于线程的内核API。但是,带阻塞相关的接口(如k_sem_take
)需要斟酌使用。这是因为,在当前处理函数执行完成之前,工作队列无法处理其队列中的后续工作项。
根据实际需求,选择性使用处理函数的形参。如果处理函数需要更多的信息,可以将工作项句柄作为结构体成员。处理函数调用CONTAINER_OF
宏获取到结构体地址,从而获取更多的信息。
一个工作项目通常被初始化一次,在需要执行工作项时提交给工作队列。提交一个已经在工作队列中工作项是不会产生任何影响,不会影响该工作项在工作队列中的位置,并且只会执行一次。
在工作项执行完成以后,工作项允许重新分配处理函数,在不同阶段时分配不同的处理函数,能避免工作队列中有冗余的工作项。
如果在中断或线程中需要调度一个工作项在指定周期后执行,那么通过可延迟的工作项(delayable work item
)在将来的某个时间提交到工作队列中完成。
一个可延迟工作项结构在标准工作项结构的基础上额外添加字段,用于描述何时将工作项添加到指定的队列。
可延迟工作项的初始化和排到工作队列中的行为与标准工作项一致,只是调用的内核API不同。
当发出该工作队列的调度请求后,内核启动超时机制,在指定的延迟后被触发。一旦超时发生,内核就会把工作项目提交给指定的工作队列,工作项一直被排在工作队列中,以标准工作项方式被处理。
延迟工作项处理函数的形参是标准工作项句柄,如果想获取延迟工作项的句柄,需要使用如下方法:
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);
...
内核定义了系统工作队列,用于任何应用或者内核代码。系统工作队列是可选项,只有在应用程序使用它的情况下才存在。
NOTE
只有在系统工作队列无法满足需求的时候,才建议新创建一个工作队列, 因为每个工作队列都会占用较大的内存。如果新的工作项执行阻塞性操作,会使其他系统工作队的处理延迟到不可接受的程度。那么这种情况下,可以新建一个工作队列。