LV080-自定义工作队列
共享队列是由内核管理的全局工作队列, 自定义工作队列是由内核或驱动程序创建的特定工作队列, 用于处理特定的任务。接下来就来学习一下自定义工作队列吧。
一、数据结构
1. 工作
在 Linux 内核中, 使用 work_struct 结构体表示一个工作项:
struct work_struct {
atomic_long_t data; // 工作项的数据字段
struct list_head entry; // 工作项在工作队列中的链表节点
work_func_t func; // 工作项的处理函数
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;// 锁依赖性映射
#endif
};这些结构被连接成链表。当一个工作者线程被唤醒时,它会执行它的链表上的所有工作。工作被执行完毕,它就将相应的 work_struct 对象从链表上移去。当链表上不再有对象的时候,它就会继续休眠。
2. 工作队列
工作以队列结构组织成工作队列(workqueue),工作队列使用 workqueue_struct 结构体表示:
struct workqueue_struct {
struct list_head pwqs; /* WR: all pwqs of this wq */
struct list_head list; /* PR: list of all workqueues */
//......
};二、相关的 api
1. 创建工作队列
1.1 create_workqueue()
#ifdef CONFIG_LOCKDEP
#define alloc_workqueue(fmt, flags, max_active, args...) \
({ \
static struct lock_class_key __key; \
const char *__lock_name; \
\
__lock_name = "(wq_completion)"#fmt#args; \
\
__alloc_workqueue_key((fmt), (flags), (max_active), \
&__key, __lock_name, ##args); \
})
#else
//......
#endif
#define create_workqueue(name) \
alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))参数 name 是创建的工作队列的名字。使用这个函数可以给每个 CPU 都创建一个 CPU 相关的工作队列。这是一个宏,传入的是名字,我们怎么得到返回值?返回值是什么样的?我们展开看一下,可以看到最后是调用了__alloc_workqueue_key()函数:
struct workqueue_struct *__alloc_workqueue_key(const char *fmt,
unsigned int flags,
int max_active,
struct lock_class_key *key,
const char *lock_name, ...)
{
//......
return wq;
err_free_wq:
//......
return NULL;
err_destroy:
//......
return NULL;
}
EXPORT_SYMBOL_GPL(__alloc_workqueue_key);这个函数创建成功返回一个 struct workqueue_struct 类型指针, 创建失败返回 NULL。
1.2 create_singlethread_workqueue()
#define create_singlethread_workqueue(name) \
alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)create_singlethread_workqueue() 函数只会给一个 CPU 创建一个 CPU 相关的工作队列。参数 name 是创建的工作队列的名字。 使用这个函数只会给一个 CPU 创建一个 CPU 相关的工作队列。 创建成功之后返回一个 struct workqueue_struct 类型指针, 创建失败返回 NULL。
2. 调度/取消工作队列
2.1 queue_work_on()
当工作队列创建好之后, 需要将要延迟执行的工作项放在工作队列上, 调度工作队列,可以使用 queue_work_on() 函数:
bool queue_work_on(int cpu, struct workqueue_struct *wq,
struct work_struct *work)
{
bool ret = false;
unsigned long flags;
local_irq_save(flags);
if (!test_and_set_bit(WORK_STRUCT_PENDING_BIT, work_data_bits(work))) {
__queue_work(cpu, wq, work);
ret = true;
}
local_irq_restore(flags);
return ret;
}
EXPORT_SYMBOL(queue_work_on);queue_work_on() 函数还有其他变种, 比如 queue_work() 函数, 这里略过, 其实思路是一致的, 用于将定义好的工作项立即添加到工作队列中, 并在工作队列可用时立即执行。
该函数有三个参数, 第一个参数是一个整数 cpu, 第二个参数是一个指向 struct workqueue_struct 的指针 wq, 第三个参数是一个指向 struct work_struct 的指针 work。
该函数的返回类型是布尔值, 表示是否成功调度工作队列。
2.2 cancel_work_sync()
如果要取消一个已经调度的工作, 使用 cancel_work_sync() 函数:
bool cancel_work_sync(struct work_struct *work)
{
return __cancel_work_timer(work, false);
}
EXPORT_SYMBOL_GPL(cancel_work_sync);函数的作用是取消一个已经调度的工作, 如果被取消的工作已经正在执行, 则会等待他执行完成再返回。
3. 刷新工作项
在 Linux 内核中,使用 flush_workqueue() 函数将刷新该工作队列中所有已提交但未执行的工作项。
void flush_workqueue(struct workqueue_struct *wq)
{
//......
}
EXPORT_SYMBOL(flush_workqueue);该函数参数是一个指向 struct workqueue_struct 类型的指针 wq。 函数的作用是刷新工作队列, 告诉内核尽快处理工作队列上的工作。
4. 删除工作队列
如果要删除自定义的工作队列, 使用 destroy_workqueue() 函数 :
void destroy_workqueue(struct workqueue_struct *wq)
{
//......
}
EXPORT_SYMBOL_GPL(destroy_workqueue);该函数参数是一个指向 struct workqueue_struct 类型的指针 wq。
三、自定义工作队列 demo
1. demo 源码
demo 源码可以看这里:13_interrupt/08_nodts_workqueue_custom
2. 开发板测试
我们拷贝驱动到开发板中,加载驱动:
insmod sdriver_demo.ko
然后按下按键,再抬起,可以看到如下打印信息:

我们还可以看到一条线程,名为 custom_workqueue :
ps -T # 我用的开发板文件系统不需要-T 参数也能查看线程情况
参考资料: