目录
1. 基本概念
1.1 工作队列与工作项
工作队列(work queue)是一个内核对象,它实际上就是一个特殊的数据结构(队列),每一个数据项称为工作项(work item)。每一个工作项都绑定了一个处理句柄函数,该函数处理具体的工作内容。
工作队列应用的场合:(1)中断服务子程序(ISR),比如在中断下文中处理一些比较耗时的或者数据量较大的任务的时候,就可以把这些操作提交到工作队列中进行;(2)高优先级线程将非紧急的处理任务移交给低优先级线程。
可以定义任意数量的工作队列,每个工作队列有自己独立的内存地址。队列是一个先进先出[FIFO]的数据结构,因此,当我们向某个指定的队列中添加多个工作项的时候,有些工作项还没有被处理,这些工作项就处于“挂起”的状态。队列中的工作项由专门的线程来处理,称之为工作队列线程,初始化一个工作队列的时候,该线程会被创建,线程的优先级是可配置的,线程可以是协作式线程也可以是抢占式线程。
1.2 工作项的分类
ISR 或者线程可以延迟一段指定的时间之后(而不是立即)再调度一个工作项。即延迟一段时间之后,向工作队列中提交一个工作项,这种工作项称之为“延迟的工作项”。而与之对立的是立刻提交立刻被执行的“标准工作项”。
1.3 工作项的属性和生命周期
1.3.1 标准工作项
可以定义任意数量的工作项。每个工作项有自己独立的内存地址。
标准工作项(对象)的属性:
(1)处理函数:当轮到某个工作项被处理时,工作队列线程就会去执行该工作项的处理函数。该函数接收一个参数 ------工作项自身的地址。 (2)挂起标志:内核会标志该工作项是否是当前工作队列里面的一个成员。 (3)队列链接:内核使用该链接将其链接到工作队列的队列中的下一个工作项。 |
关注第一个属性即可。
工作项必须先初始化再使用。初始化时会记录该工作项的处理函数,并将其标记为非挂起。
ISR 或者线程可以将某个工作项提交(submit)到某个工作队列中。提交工作项时,会将其追加到工作队列的队列中去。当工作队列线程处理完它有工作项后,该线程会移除一个挂起工作项,并调用该工作项的处理函数。一个挂起的工作项可能很快就会被处理,也可能会在队列中保留一段时间,这依赖于工作队列线程的调度优先级和队列中其它项的工作需求。
处理函数可以利用任何可用的内核 API。不过,使用可能引起阻塞的操作(例如获取一个信号量)时一定要当心,因为工作队列在它的上一个处理函数完成前不能处理其队列中的其它工作项(先进先出,最先提交的会先被执行)。
如果处理函数不需要参数,可以将接收到的参数直接忽略。如果处理函数需要额外的信息,可以将工作项内嵌到一个更大的数据结构当中。处理函数可以使用这个参数值计算封装后的地址,以此访问额外的信息。
一个工作项通常会被初始化一次,然后当它的工作需要执行的时候会被提交到工作队列中。如果 ISR 或者线程尝试提交一个已经挂起的工作项,不会有任何效果;提交后,工作项会停留在工作队列中的当前位置,且只会被执行一次。
可以在处理函数中将当前的工作项重新提交到工作队列中(因为此时的工作项已经不再是挂起状态)。这样做的好处是,处理函数可以分阶段执行工作(设定一个标志位,执行不同的任务),而不会导致延迟处理工作队列的队列中的其它工作项。
一个挂起的工作项在被工作队列线程处理前不能被改变(一般我们也不会去修改它)。即当工作项处于挂起状态时,它不能被再次初始化。此外,在处理函数执行完成前,处理函数需要的额外信息也不能被改变(一般是程序中先设定好,之后传入参数)。
1.3.2 延迟的工作项
延迟工作项比标准工作项多了两个属性:
(1)延迟时间:指明需要延迟多久才将工作项提交到工作队列的队列中(毫秒级)。 (2)工作队列指示器:用于标识需要提交到哪一个工作队列中 |
延迟工作项的初始化过程和提交过程与标准的工作项是类似的,只是所使用的内核 API 略有区别(API名称和参数个数)。当发出提交请求时,内核会初始化一个超时机制,当指定的延迟达到时就会触发它。当超时发送时,内核会将延迟工作项提交到指定的工作队列中。之后,它会保持挂起状态。
ISR 或者线程可以取消它提交的延迟工作项,但是前提是该工作项的超时计数扔在继续。取消后,超时计数将停止计数,指定的工作也不会被执行。
取消已经到期的延时工作项不会有任何效果;除非工作项被移除并被工作队列线程处理了,否它将一直保持挂起状态。因此,当工作项的超时服务到期后,它已经被处理过了,所以不能被取消。
1.4 系统工作队列和自定义工作队列
Zephyr内核默认定义了一个工作队列,称之为“系统工作队列”。所有的应用程序或者内核代码都可以将工作项提交到该工作队列中去处理(下文有API解析)。
除了系统工作队列,还可以定义自己的工作队列。
2. API解析
2.1 创建工作队列
(1)使用类型为 struct k_work_q来定义一个工作队列变量。
(2)初始化工作队列时,需要先定义一个栈区,然后调用函数 k_work_q_start()初始化一个工作队列。栈区是一个数组,定义栈区时必须使用属性 __stack,以确保它被正确地对齐。
2.1.1 工作队列初始化函数
函数原型 | void k_work_q_start(struct k_work_q *work_q, k_thread_stack_t stack, size_t stack_size, int prio) { …………………………………………………………………………………………………. } |
函数功能 | 初始化一个工作队列 |
参数 | struct k_work_q *work_q:自定义的工作队列 k_thread_stack_t stack:栈区 size_t stack_size:栈大小 int prio:工作队列线程的优先级 |
返回值 | 无 |
定义处(源文件) | \kernel\work_q.c |
声明处(头文件) | \include\kernel.h |
2.1.2 初始化工作队列示例
#include <zephyr.h> #include "my_work_queue.h" #define CONFIG_SYSTEM_MY_WORKQUEUE_STACK_SIZE 1024 #define MY_STACK_SIZE (CONFIG_SYSTEM_MY_WORKQUEUE_STACK_SIZE + 500) #define MY_PRIORITY 5 char __noinit __stack my_stack_area[MY_STACK_SIZE]; struct k_work_q my_work_q; void create_my_sys_work_q(void) { k_work_q_start(&my_work_q, my_stack_area, MY_STACK_SIZE, MY_PRIORITY); } |
2.2 系统工作队列
Zephye内核默认创建了一个系统工作队列
文件:\kernel\system_work_q.c,在该文件中创建并初始化了一个系统工作队列。
注:所有的应用程序或者内核代码都可以将工作项提交到该工作队列中去处理。
2.3 工作项的创建和初始化
使用类型为 struct
k_work来定义一个工作项(变量/对象)。工作项创建之后必须使用函数 k_work_init() 进行初始化
2.3.1 工作项初始化函数
函数原型 | static inline void k_work_init(struct k_work *work, k_work_handler_t handler) { …………………………………………………………………………………………………. } |
函数功能 | 初始化一个工作项 |
参数 | struct k_work *work:工作项地址 k_work_handler_t handler:工作项处理句柄函数 |
返回值 | 无 |
定义处(源文件) | \include\kernel.h |
声明处(头文件) |
2.3.2 工作项初始化示例
略(下文有完整例子)
2.4 工作项的提交
2.4.1 提交到指定的工作队列中
函数原型 | static inline int k_work_submit_to_queue(struct k_work_q *work_q, struct k_work *work) { …………………………………………………………………………………………………………… } |
函数功能 | 将工作项提交到指定的工作队列中 |
参数 | struct k_work_q *work_q:工作队列的地址 struct k_work *work:工作项地址 |
返回值 | 无 |
定义处(源文件) | \include\kernel.h |
声明处(头文件) |
2.4.2 提交到系统工作队列中
将已经初始化的工作项提交到系统工作队列中,使用的API是k_work_submit()。
函数原型 | static inline void k_work_submit(struct k_work *work) { k_work_submit_to_queue(&k_sys_work_q, work); } |
函数功能 | 将工作项提交到系统工作队列中 |
参数 | struct k_work *work:工作项地址 |
返回值 | 无 |
定义处(源文件) | \include\kernel.h |
声明处(头文件) |
注:
(1)k_work_submit()内部分装的是k_work_submit_to_queue(),并且第一个参数固定为系统工作队列。
(2)如果需要提交一个延迟执行的工作队列,使用的是:
static inline int k_delayed_work_submit(struct k_delayed_work *work, k_timeout_t delay) |
对应的初始化API就是:
static inline void k_delayed_work_init(struct k_delayed_work *work, k_work_handler_t handler)
(3)工作队列在被调度执行之前,可以取消任务,使用的API是:
int k_delayed_work_cancel(struct k_delayed_work *work)
2.5 完整示例
示例中的代码包括自定义工作队列的初始化,工作项的提交。
文件:shell_input_sys.c
#include <zephyr.h> #include <string.h> #include <shell/shell.h> #include <logging/sys_log.h> struct work_item{ struct k_work my_work_item; struct k_delayed_work delay_work_item; char str_info[16]; }my_item; /* * Decide whether to upload recording data * Examples: * Send 0 data -- hid cmd 0 0 * Send audio data -- hid cmd 1 1 */ static int shell_hid_audio_recoder_cmd(int argc, char *argv[]) { u8_t cmd_dat[2]; cmd_dat[0] = strtoul(argv[1], NULL, 16); cmd_dat[1] = strtoul(argv[2], NULL, 16); if (usb_audio_source_enabled()) { if (cmd_dat[0] == 0 && cmd_dat[1] == 0) { ; } else if (cmd_dat[0] == 1 && cmd_dat[1] == 1) { ; } else { return -1; } } else { ; return -1; } return 0; } /* Work item processing function */ void handle_func(struct k_work *item) { strcpy(my_item.str_info, "handle_func"); SYS_LOG_WRN("my_item.str_info=%s\r\n", my_item.str_info); } void delay_handle_func(struct k_work *item) { strcpy(my_item.str_info, "delay_handle_func"); SYS_LOG_WRN("my_item.str_info=%s\r\n", my_item.str_info); } /* * Submit work items to the system work queue, * Shell command line simulates interrupt content. */ #include "my_work_queue.h" #define SUBMIT_TO_SYS_WORK_Q 0 #define DELAY_TIME 2000 static int shell_hid_submit_work_items(int argc, char *argv[]) { //k_work_submit(&my_item.my_work_item); k_work_submit_to_queue(&my_work_q, &my_item.my_work_item); //k_delayed_work_submit_to_queue(&my_work_q, &my_item.delay_work_item, 3000); } static const struct shell_cmd hid_commands[] = { { "cmd", shell_hid_audio_recoder_cmd, "audio record ctrl cmd" }, { "work", shell_hid_submit_work_items, "Submit work items to the system work queue" }, { NULL, NULL, NULL }, }; /* initialize work item */ void init_work_item(void) { k_work_init(&my_item.my_work_item, handle_func); //k_delayed_work_init(&my_item.delay_work_item, delay_handle_func); } int hid_shell_init(struct device *unused) { ARG_UNUSED(unused); SHELL_REGISTER("hid", hid_commands); init_work_item();
return 0; } |