diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/example.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/example.png new file mode 100644 index 0000000000000000000000000000000000000000..9fde4a28fb43b0c6022e22ed0356890a0ba75bb1 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/example.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/peak.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/peak.png new file mode 100644 index 0000000000000000000000000000000000000000..a8e50f387a8fb891ada87fb446e0932a8ae5a460 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/peak.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer.png new file mode 100644 index 0000000000000000000000000000000000000000..d4af5c1c8eff908ad8dd06c3744054bd0f83a2e5 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer_empty.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..1362219934dd5ce2c43b58654b5cafa2453ca703 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer_empty.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer_full.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer_full.png new file mode 100644 index 0000000000000000000000000000000000000000..3435355c71897dcbdc2375cc3a9aa8322ef9524c Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/figures/ringbuffer_full.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/ringbuffer.md b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/ringbuffer.md new file mode 100644 index 0000000000000000000000000000000000000000..dbfe1f1b80a9f397ed81686e0f0de5bbe8bdf2a9 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/ringbuffer/ringbuffer.md @@ -0,0 +1,298 @@ +# ringbuffer + +ringbuffer(环形缓冲区)是一个非常实用的工具,我们可以将缓冲区看成一个环形的,可以不断地向里面填充数据而不用担心缓冲区溢出。ringbuffer 的读取以字节为单位。 + +![](figures/ringbuffer.png) + +RT-Thread 内部也提供了该工具,通过导入头文件 `#include ` 即可直接使用。 + +## ringbuffer 结构体介绍 + +```C +struct rt_ringbuffer +{ + rt_uint8_t *buffer_ptr; + + rt_uint16_t read_mirror : 1; + rt_uint16_t read_index : 15; + rt_uint16_t write_mirror : 1; + rt_uint16_t write_index : 15; + + rt_int16_t buffer_size; +}; +``` + +`buffer_ptr` 是指向缓冲区的指针,`buffer_size` 是缓冲区的大小,`read_index` 是读索引,`write_index` 是写索引,而 `read_mirror` 和 `write_mirror` 可以理解为一种镜像值,每次向缓冲区写入数据,碰到缓冲区末尾时,切换到另一个镜像的缓冲区头部写入剩余数据。这种镜像操作可用于判断缓冲区内数据是满还是空。 + +当 `write_index == read_index` 且 `read_mirror == write_mirror` 时,缓冲区内数据为空。 + +![](figures/ringbuffer_empty.png) + +当 `write_index == read_index` 且 `read_mirror != write_mirror` 时,缓冲区内数据已满。 + +![](figures/ringbuffer_full.png) + +若是没有上述镜像值,我们就没有办法区分缓冲区空和缓冲区满这两种情况。 + +**注意:**RT-Thread 的 ringbuffer 组件并未提供线程阻塞的功能,因此 ringbuffer 本质上是一个全局共享的对象,多线程使用时注意使用互斥锁保护。 + +## ringbuffer 接口介绍 + +### 创建和销毁 ringbuffer + +当系统支持动态内存分配时,可以动态创建并销毁 ringbuffer 。创建 ringbuffer 使用下述接口: + +```C +struct rt_ringbuffer* rt_ringbuffer_create(rt_uint16_t length); +``` + +该接口会根据传入的 `length` 参数动态分配 length 个字节大小的缓冲区,创建 ringbuffer 结构体并返回。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| :-------------------- | :------------- | +| length | 缓冲区字节大小 | +| **返回** | —— | +| RT_NULL | 创建失败 | +| ringbuffer 结构体指针 | 创建成功 | + +当不再需要使用 ringbuffer 时,可以使用下述接口销毁之前创建的 ringbuffer, + +```C +void rt_ringbuffer_destroy(struct rt_ringbuffer *rb); +``` + +该接口会释放掉之前创建 ringbuffer 结构体时动态分配的缓冲区,下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | --------------------- | +| rb | ringbuffer 结构体指针 | +| **返回** | —— | + +### 初始化和复位 ringbuffer + +若系统不支持动态内存分配,也可以使用下述静态接口来初始化 ringbuffer: + +```C +void rt_ringbuffer_init(struct rt_ringbuffer *rb, rt_uint8_t *pool, rt_int16_t size); +``` + +该接口需要我们自己创建一个静态缓冲区,并传入缓冲区指针 `pool` 和缓冲区大小 `size` ,而后初始化 `rb` 所指向的 ringbuffer 结构体。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ------------------------ | +| rb | ringbuffer 结构体指针 | +| pool | 缓冲区指针 | +| size | 缓冲区大小,以字节为单位 | +| **返回** | —— | + +若我们需要还原 ringbuffer 为初始状态,则可以调用下述接口: + +```C +void rt_ringbuffer_reset(struct rt_ringbuffer *rb); +``` + +该接口清空了 ringbuffer 里的所有数据,将内部状态还原成了刚初始化的时候。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | --------------------- | +| rb | ringbuffer 结构体指针 | +| **返回** | —— | + +### 向 ringbuffer 写入数据 + +向 ringbuffer 写入数据的接口有许多,如果我们只需要写入一个字节,则可以使用下述接口: + +```C +rt_size_t rt_ringbuffer_putchar(struct rt_ringbuffer *rb, const rt_uint8_t ch); +``` + +该接口向 `rb` 指向的 ringbuffer 里写入一个字节 `ch`,并返回写入的字节数。若 ringbuffer 已满,则写入失败,并返回 0 。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | --------------------- | +| rb | ringbuffer 结构体指针 | +| ch | 待写入的字节 | +| **返回** | —— | +| 1 | 成功写入 1 字节 | +| 0 | 写入失败 | + +若是希望 ringbuffer 满了之后也能够成功写入一个字节,则可以调用下述接口: + +```C +rt_size_t rt_ringbuffer_putchar_force(struct rt_ringbuffer *rb, const rt_uint8_t ch); +``` + +该接口也是向 `rb` 指向的 ringbuffer 里写入一个字节 `ch`,并返回写入的字节数。不过当 ringbuffer 已满时,它也会执行写入操作,即用新的值覆盖掉旧的值。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | --------------------- | +| rb | ringbuffer 结构体指针 | +| ch | 待写入的字节 | +| **返回** | —— | +| 1 | 成功写入 1 字节 | + +当然,也有支持写入数据块的接口: + +```C +rt_size_t rt_ringbuffer_put(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length); +``` + +该接口向 `rb` 指向的 ringbuffer 里写入数据块,数据块的起始地址为 `ptr`,数据块大小为 `length`,并返回写入的字节数。若写入时 ringbuffer 已满,则剩余的写入数据被抛弃,不再继续写入。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | -------------------------- | +| rb | ringbuffer 结构体指针 | +| ptr | 数据块的起始地址 | +| length | 数据块的大小,以字节为单位 | +| **返回** | —— | +| N | 写入的字节数 | + +与写入一个字节类似,写入数据块也支持覆盖写入的接口: + +```C +rt_size_t rt_ringbuffer_put_force(struct rt_ringbuffer *rb, const rt_uint8_t *ptr, rt_uint16_t length); +``` + +该接口向 `rb` 指向的 ringbuffer 里写入数据块,数据块的起始地址为 `ptr`,数据块大小为 `length`,并返回写入的字节数。不过若写入时 ringbuffer 已满,该接口会继续写入剩余的数据,即用新的数据覆盖旧的数据。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | -------------------------- | +| rb | ringbuffer 结构体指针 | +| ptr | 数据块的起始地址 | +| length | 数据块的大小,以字节为单位 | +| **返回** | —— | +| N | 写入的字节数 | + +### 从 ringbuffer 读取数据 + +从 ringbuffer 里读取数据的接口分为两类,一类是**读数据并取出数据**,另一类是**读数据但并不取出数据**(peak 操作)。 + +先介绍第一类接口,读数据并取出数据。 + +从 ringbuffer 里读出一个字节的接口如下: + +```C +rt_size_t rt_ringbuffer_getchar(struct rt_ringbuffer *rb, rt_uint8_t *ch); +``` + +该接口从 `rb` 指向的 ringbuffer 里读出一个字节,该字节通过 `ch` 返回,ch 是一个字节的缓冲区,供接口内部填充数据,该接口返回读取的字节数。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | -------------------------------- | +| rb | ringbuffer 结构体指针 | +| ch | 字节数据指针,用于获取读取的字节 | +| **返回** | —— | +| 1 | 成功读取 1 个字节 | +| 0 | ringbuffer 为空 | + +若想要一次读出多个字节,则可以使用下述接口: + +```C +rt_size_t rt_ringbuffer_get(struct rt_ringbuffer *rb, rt_uint8_t *ptr, rt_uint16_t length); +``` + +该接口从 `rb` 指向的 ringbuffer 里读出 `length` 个字节,并存入 `ptr` 所指向的缓冲区,若 ringbuffer 内存储的数据量小于 length,则实际读取的字节数为 ringbuffer 内的存储量。该接口返回实际读取的字节数。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ---------------------------- | +| rb | ringbuffer 结构体指针 | +| ptr | 读出数据缓冲区的起始地址 | +| length | 读出数据的大小,以字节为单位 | +| **返回** | —— | +| > 0 | 实际读出的字节数 | +| 0 | 缓冲区为空 | + +下面介绍第二类接口,读数据但不取出数据: + +```C +rt_size_t rt_ringbuffer_peak(struct rt_ringbuffer *rb, rt_uint8_t **ptr); +``` + +该接口用于获取 `rb` 指向的 ringbuffer 内部缓冲区**第一个可读数据的地址**,并保存到 `*ptr`,该接口返回 ringbuffer 内存储的字节数。 + +**注意:**该接口建议只用来访问一个字节,否则极有可能造成数组越界,参考下图: + +![](figures/peak.png) + +此时 ringbuffer 内部存储了 3 个字节, `read_index` 索引到了内部数组的最后一个字节,此时调用 peak 接口,我们获取的就是内部数组的最后一个字节的地址 `p`,我们仅通过 `*p` 访问一个字节是没问题的,若访问第二个字节 `*(p+1)` 则就造成了数组越界。 + +下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | -------------------------------------------------------- | +| rb | ringbuffer 结构体指针 | +| ptr | 双重指针,用于获取 ringbuffer 内部第一个可读取数据的地址 | +| **返回** | —— | +| > 0 | ringbuffer 内存储的字节数 | +| 0 | 缓冲区为空 | + +### 获取 ringbuffer 内部状态 + +我们可以获取 ringbuffer 内存储的数据大小: + +```C +rt_size_t rt_ringbuffer_data_len(struct rt_ringbuffer *rb); +``` + +该接口用于获取 `rb` 指向的 ringbuffer 内存储的数据大小,下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ------------------------- | +| rb | ringbuffer 结构体指针 | +| **返回** | —— | +| > 0 | ringbuffer 内存储的字节数 | +| 0 | ringbuffer 为空 | + +还可获取 ringbuffer 内部缓冲区大小: + +```C +rt_inline rt_uint16_t rt_ringbuffer_get_size(struct rt_ringbuffer *rb); +``` + +该接口用于获取 `rb` 指向的 ringbuffer 内部缓冲区的大小,下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ----------------------------------------- | +| rb | ringbuffer 结构体指针 | +| **返回** | —— | +| N | ringbuffer 内部缓冲区的大小,以字节为单位 | + +## 示例代码 + +```C +#include +#include + +typedef struct rb_example { + int a; + int b; +} rb_example_t; + +int ringbuffer_example(void) +{ + rb_example_t data = { + .a = 1, + .b = 2, + }; + + struct rt_ringbuffer * rb = rt_ringbuffer_create(sizeof(rb_example_t) * 2); + RT_ASSERT(rb != RT_NULL); + + rt_kprintf("Put data to ringbuffer, a: %d b: %d size: %d\n", data.a, data.b, sizeof(data)); + rt_ringbuffer_put(rb, (rt_uint8_t *)&data, sizeof(data)); + + + rb_example_t recv_data; + rt_size_t recv = rt_ringbuffer_get(rb, (rt_uint8_t *)&recv_data, sizeof(recv_data)); + RT_ASSERT(recv == sizeof(recv_data)); + rt_kprintf("Get data from ringbuffer, a: %d b: %d size: %d\n", recv_data.a, recv_data.b, sizeof(recv_data)); + + return 0; +} + +MSH_CMD_EXPORT(ringbuffer_example, ringbuffer example); +``` + +运行效果如下: + +![](figures/example.png) \ No newline at end of file diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/system_workqueue.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/system_workqueue.png new file mode 100644 index 0000000000000000000000000000000000000000..e7469d1bcd4831d995d29d0ba526ba530df2a656 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/system_workqueue.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/workqueue.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/workqueue.png new file mode 100644 index 0000000000000000000000000000000000000000..3a2e0ae7ceb7829138bc6f940837ffc2e735767a Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/workqueue.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/workqueue_example.png b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/workqueue_example.png new file mode 100644 index 0000000000000000000000000000000000000000..42aa20943b10e863c3d13836371e02d46ba29204 Binary files /dev/null and b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/figures/workqueue_example.png differ diff --git a/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/workqueue.md b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/workqueue.md new file mode 100644 index 0000000000000000000000000000000000000000..346989449ae4cdf7a2a09790c4ba116a38bdca44 --- /dev/null +++ b/rt-thread-version/rt-thread-standard/programming-manual/device-ipc/workqueue/workqueue.md @@ -0,0 +1,299 @@ +# workqueue + +工作队列(workqueue)是一种转移任务执行环境的工具,例如当系统产生一个中断时,我们可以在中断处理函数里做一些紧急地操作,然后将另外一些不那么紧急,而且需要一定时间的任务封装成函数交给工作队列执行,此时该函数的执行环境就从**中断环境**变成了**线程环境**,这就是 Linux 里经常提及的中断处理“下半部”。 + +工作队列的原理也比较简单,可以参考下述图示: + +![](figures/workqueue.png) + +工作队列内部有一个工作链表(worklist),链表上有多个工作项(work item)节点,我们可以将工作项简单理解为函数,因此工作链表上就存储着一系列待执行的函数。而且工作队列内有个线程一直在轮询工作链表,每次都从工作链表中取出一个工作项,并执行其相关联的函数。当工作队列为空时,线程会被挂起。 + +--- + +RT-Thread 内有 workqueue 组件,导入头文件 `#include ` 即可使用。 + +使用注意事项: + +- 工作项里最好不要有会导致线程阻塞的代码,否则会影响后续工作项的执行。 + +RT-Thread 系统提供了一个**系统工作队列**,可以通过 env 开启 + +``` +RT-Thread Components + -> Device Drivers + -> Using device drivers IPC + -> Using system default workqueue +``` + +我们还可以配置系统工作队列内线程栈的大小,线程的优先级,图示如下: + +![](figures/system_workqueue.png) + +使用系统工作队列的好处是 RT-Thread 内部帮我们创建了一个工作队列,我们只需要提交工作项即可,使用简洁方便。 + +## workquque 结构体介绍 + +```C +struct rt_workqueue +{ + rt_list_t work_list; + rt_list_t delayed_list; + struct rt_work *work_current; /* current work */ + + struct rt_semaphore sem; + rt_thread_t work_thread; +}; +``` + +`work_list` 就是工作队列里的工作链表,`work_thread` 即工作队列里的线程。再来看看工作项结构体: + +```C +struct rt_work +{ + rt_list_t list; + + void (*work_func)(struct rt_work *work, void *work_data); + void *work_data; + rt_uint16_t flags; + rt_uint16_t type; + struct rt_timer timer; + struct rt_workqueue *workqueue; +}; +``` + +`list` 用于将该工作项挂载到工作链表上去,而 `work_func` 就是该工作项绑定的函数指针,`work_data` 是用户自定义数据,当工作项被执行时就会调用该函数。 + +## workqueue 接口介绍 + +### 初始化工作项 + +每一个工作项在被提交到工作队列之前,都需要调用下述接口初始化: + +```C +rt_inline void rt_work_init(struct rt_work *work, void (*work_func)(struct rt_work *work, void *work_data), void *work_data); +``` + +该接口初始化 `work` 指针指向的工作项,并绑定回调函数 `work_func` 以及用户自定义数据 `work_data`。若使用自定义数据,需要保证该数据对象为静态存储的。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | ------------------------------ | +| work | 工作项结构体指针 | +| work_func | 回调函数,工作项执行时会调用它 | +| work_data | 用户自定义数据,回调函数参数 | +| **返回** | —— | + +### 使用系统工作队列 + + + +开启系统工作队列后,我们就可以通过下述接口向系统工作队列提交一个工作项: + +```C +rt_err_t rt_work_submit(struct rt_work *work, rt_tick_t time); +``` + +该函数向系统工作队列里提交 `work` 指针指向的工作项,若 `time` 大于 0,则提交过程会延时 `time` 个 ticks 。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | --------------------------------------------------- | +| work | 工作项结构体指针 | +| time | 提交延时,以 tick 为单位,需小于 `RT_TICK_MAX / 2` | +| **返回** | —— | +| RT_EOK | 提交成功 | +| -RT_EBUSY | 该工作项正在执行 | +| -RT_ERROR | `time` 参数错误 | + +若我们想取消之前提交过的工作项,则可以调用下述接口: + +```C +rt_err_t rt_work_cancel(struct rt_work *work); +``` + +该接口会在系统工作队列里移除 `work` 指向的工作项。 下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | ---------------- | +| work | 工作项结构体指针 | +| **返回** | —— | +| RT_EOK | 取消成功 | +| -RT_EBUSY | 该工作项正在执行 | + +### 创建销毁工作队列 + +除了使用系统工作队列,我们也可以创建属于自己的工作队列,这样更加灵活。创建工作队列使用下述接口: + +```C +struct rt_workqueue *rt_workqueue_create(const char *name, rt_uint16_t stack_size, rt_uint8_t priority); +``` + +该函数创建并初始化一个工作队列,利用参数 `name`,`stack_size` 和 `priority` 创建工作队列内部线程,最终返回创建的工作队列。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| ----------------------- | ---------- | +| name | 线程名字 | +| stack_size | 线程栈大小 | +| priority | 线程优先级 | +| **返回** | —— | +| RT_NULL | 创建失败 | +| rt_workqueue 结构体指针 | 创建成功 | + +若不再使用工作队列,也可以销毁它: + +```C +rt_err_t rt_workqueue_destroy(struct rt_workqueue *queue); +``` + +该函数还原了内部所有状态,删除了内部线程,并释放了 `queue` 所指向的结构体空间,因此调用该函数后,不能再使用 `queue` ,下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ------------------ | +| queue | 工作队列结构体指针 | +| **返回** | —— | +| RT_EOK | 销毁成功 | + +### 提交工作项 + +向自己创建的工作队列提交工作项有三个接口,根据是否允许工作项延时提交可分为: + +1. 允许工作项延时提交 +2. 不允许工作项延时提交 + +**允许**工作项延时提交的接口如下: + +```C +rt_err_t rt_workqueue_submit_work(struct rt_workqueue *queue, struct rt_work *work, rt_tick_t time); +``` + +该接口将 `work` 指向的工作项提交到 `queue` 指向的工作队列中,若 `time` 大于 0,则该提交延时 `time` 个 tick 之后执行。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | --------------------------------------------------- | +| queue | 工作队列结构体指针 | +| work | 工作项结构体指针 | +| time | 提交延时,以 tick 为单位,需小于 `RT_TICK_MAX / 2` | +| **返回** | —— | +| RT_EOK | 提交成功 | +| -RT_EBUSY | 该工作项正在执行 | +| -RT_ERROR | `time` 参数错误 | + +**不允许**工作项延时提交的接口如下: + +```C +rt_err_t rt_workqueue_dowork(struct rt_workqueue *queue, struct rt_work *work); +``` + +该接口立即将 `work` 指向的工作项提交到 `queue` 指向的工作队列**尾部**,下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | ------------------ | +| queue | 工作队列结构体指针 | +| work | 工作项结构体指针 | +| **返回** | —— | +| RT_EOK | 提交成功 | +| -RT_EBUSY | 该工作项正在执行 | + +还有另一个类似接口: + +```C +rt_err_t rt_workqueue_critical_work(struct rt_workqueue *queue, struct rt_work *work); +``` + +该接口立即将 `work` 指向的工作项提交到 `queue` 指向的工作队列**头部**,因此该接口提交的工作项在当前工作项执行完毕后会被立即执行,适用于一些比较紧急(critical)的任务。而通过 dowork 提交的工作项会挂载到工作队列尾部,适用于不那么紧急的任务。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | ------------------ | +| queue | 工作队列结构体指针 | +| work | 工作项结构体指针 | +| **返回** | —— | +| RT_EOK | 提交成功 | +| -RT_EBUSY | 该工作项正在执行 | + +### 取消工作项 + +想要取消指定工作项可以调用下述接口: + +```C +rt_err_t rt_workqueue_cancel_work(struct rt_workqueue *queue, struct rt_work *work); +``` + +该接口会从 `queue` 指向的工作队列中将 `work` 指向的工作项移除,这样该工作项就不会被执行了。当该工作项正在执行时,返回 -RT_EBUSY 的错误。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| --------- | ------------------ | +| queue | 工作队列结构体指针 | +| work | 工作项结构体指针 | +| **返回** | —— | +| RT_EOK | 提交成功 | +| -RT_EBUSY | 该工作项正在执行 | + +还有一类 sync 接口不会返回错误,而是等待该工作项执行完毕: + +```C +rt_err_t rt_workqueue_cancel_work_sync(struct rt_workqueue *queue, struct rt_work *work); +``` + +该接口会从 `queue` 指向的工作队列中将 `work` 指向的工作项移除,这样该工作项就不会被执行了。不过当该工作项正在执行时,该接口内部会**阻塞**等待该工作项执行完毕,是一个同步接口。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ------------------ | +| queue | 工作队列结构体指针 | +| work | 工作项结构体指针 | +| **返回** | —— | +| RT_EOK | 提交成功 | + +我们还可以一次性取消所有的工作项: + +```C +rt_err_t rt_workqueue_cancel_all_work(struct rt_workqueue *queue); +``` + +该接口取消 `queue` 指向的工作队列里的所有工作项。下表描述了该函数的输入参数与返回值: + +| 参数 | 描述 | +| -------- | ------------------ | +| queue | 工作队列结构体指针 | +| work | 工作项结构体指针 | +| **返回** | —— | +| RT_EOK | 提交成功 | + +## 示例代码 + +```C +#include +#include + + +struct rt_work work1; +int work1_data = 1; + +struct rt_work work2; +int work2_data = 2; + +void work_func(struct rt_work *work, void *work_data) +{ + int data = *(int *)work_data; + rt_kprintf("recv work data: %d\n", data); +} + +int workqueue_example(void) +{ + printf("hello rt-thread!\n"); + + struct rt_workqueue *wq = rt_workqueue_create("my_wq", 2048, 20); + RT_ASSERT(wq); + + rt_work_init(&work1, work_func, &work1_data); + rt_work_init(&work2, work_func, &work2_data); + + rt_workqueue_submit_work(wq, &work1, 2); + rt_workqueue_submit_work(wq, &work2, 0); + + return 0; +} + +MSH_CMD_EXPORT(workqueue_example, workqueue example); +``` + +运行效果如下,与工作项绑定的任务被异步执行了,而且工作项 1 延迟了 2 个 tick 才执行: + +![](figures/workqueue_example.png) \ No newline at end of file