Skip to content

LV080-自定义工作队列

共享队列是由内核管理的全局工作队列, 自定义工作队列是由内核或驱动程序创建的特定工作队列, 用于处理特定的任务。接下来就来学习一下自定义工作队列吧。

一、数据结构

1. 工作

在 Linux 内核中, 使用 work_struct 结构体表示一个工作项:

c
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 结构体表示:

c
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()

c
#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()函数:

c
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()

c
#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() 函数:

c
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() 函数:

c
bool cancel_work_sync(struct work_struct *work)
{
	return __cancel_work_timer(work, false);
}
EXPORT_SYMBOL_GPL(cancel_work_sync);

函数的作用是取消一个已经调度的工作, 如果被取消的工作已经正在执行, 则会等待他执行完成再返回。

3. 刷新工作项

在 Linux 内核中,使用 flush_workqueue() 函数将刷新该工作队列中所有已提交但未执行的工作项。

c
void flush_workqueue(struct workqueue_struct *wq)
{
	//......
}
EXPORT_SYMBOL(flush_workqueue);

该函数参数是一个指向 struct workqueue_struct 类型的指针 wq。 函数的作用是刷新工作队列, 告诉内核尽快处理工作队列上的工作。

4. 删除工作队列

如果要删除自定义的工作队列, 使用 destroy_workqueue() 函数 :

c
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. 开发板测试

我们拷贝驱动到开发板中,加载驱动:

shell
insmod sdriver_demo.ko
image-20250323125809743

然后按下按键,再抬起,可以看到如下打印信息:

image-20250323125926666

我们还可以看到一条线程,名为 custom_workqueue :

shell
ps -T # 我用的开发板文件系统不需要-T 参数也能查看线程情况
image-20250323163256690

参考资料:

中断处理下半部机制-CSDN 博客

软中断(softirq)机制_软中断机制-CSDN 博客