Skip to content

LV075-共享工作队列

一、相关的api

1. 初始化函数

1.1 动态创建工作:INIT_WORK

在实际的驱动开发中, 我们只需要定义工作项(work_struct)即可, 关于工作队列和工作者线程我们基本不用去管。 简单创建工作很简单, 直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:

c
#ifdef CONFIG_LOCKDEP
#define __INIT_WORK(_work, _func, _onstack)				\
	do {								\
		static struct lock_class_key __key;			\
									\
		__init_work((_work), _onstack);				\
		(_work)->data = (atomic_long_t) WORK_DATA_INIT();	\
		lockdep_init_map(&(_work)->lockdep_map, "(work_completion)"#_work, &__key, 0); \
		INIT_LIST_HEAD(&(_work)->entry);			\
		(_work)->func = (_func);				\
	} while (0)
#else
//......
#endif

#define INIT_WORK(_work, _func)						\
	__INIT_WORK((_work), (_func), 0)

INIT_WORK 宏接受两个参数: _work 和 _func, 分别表示要初始化的工作项和工作项的处理函数。示例如下:

c
void __cfg80211_scan_done(struct work_struct *wk)
{
	struct cfg80211_registered_device *rdev;
 
	rdev = container_of(wk, struct cfg80211_registered_device,
			    scan_done_wk);
 
	cfg80211_lock_rdev(rdev);
	___cfg80211_scan_done(rdev, false);
	cfg80211_unlock_rdev(rdev);
}
 
struct cfg80211_registered_device {
 
	struct work_struct scan_done_wk;
	struct work_struct sched_scan_results_wk;
	struct work_struct conn_work;
	struct work_struct event_work;
	struct cfg80211_wowlan *wowlan;
}
struct cfg80211_registered_device *rdev;
rdev = kzalloc(alloc_size, GFP_KERNEL);
 
INIT_WORK(&rdev->scan_done_wk, __cfg80211_scan_done);  // 其执行函数为: __cfg80211_scan_done
INIT_WORK(&rdev->sched_scan_results_wk, __cfg80211_sched_scan_results);

1.2 静态创建工作:DECLARE_WORK

也可以使用 DECLARE_WORK 宏在编译时静态地完成工作的创建和初始化, 宏定义如下:

c
#define __WORK_INITIALIZER(n, f) {					\
	.data = WORK_DATA_STATIC_INIT(),				\
	.entry	= { &(n).entry, &(n).entry },				\
	.func = (f),							\
	__WORK_INIT_LOCKDEP_MAP(#n, &(n))				\
	}

#define DECLARE_WORK(n, f)						\
	struct work_struct n = __WORK_INITIALIZER(n, f)

参数 n 表示定义的工作(work_struct), f 表示工作对应的处理函数。简单示例如下:

c
static void do_poweroff(struct work_struct *dummy)
{
	kernel_power_off();
}
 
static DECLARE_WORK(poweroff_work, do_poweroff);

这段代码创建了一个全局静态变量:static work_struct poweroff_work,且被初始化了,其执行函数为do_poweroff()。

2. 调度/取消调度

2.1 schedule_work()

和 tasklet 一样, 工作也是需要调度才能运行的, 工作的调度函数为 schedule_work()

c
static inline bool schedule_work(struct work_struct *work)
{
	return queue_work(system_wq, work);
}

参数是指向工作项的指针。 这个函数作用是将工作项提交到工作队列中, 并请求调度器在合适的时机执行工作项,该函数会返回一个布尔值, 表示工作项是否成功被提交到工作队列。

这个函数会把 work 提供给系统默认的 work queue: system_wq,它是一个队列:

c
struct workqueue_struct *system_wq __read_mostly;
EXPORT_SYMBOL(system_wq);

谁来执行 work 中的函数?schedule_work() 函数不仅仅是把 work 放入队列,还会把kworker 线程唤醒。此线程抢到时间运行时,它就会从队列中取出 work,执行里面的函数。

Tips:把work放入工作队列,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);

参数是指向工作项的指针。 这个函数的作用是取消该工作项的调度。 如果工作项已经在工作队列中, 它将被从队列中移除。 如果工作项已经在工作队列中, 它将被从队列中移除, 并等待工作项执行完成。 函数返回一个布尔值, 表示工作项是否成功取消。

二、参考示例

c
/* 定义工作(work) */
struct work_struct testwork;
/* work 处理函数 */
void testwork_func_t(struct work_struct *work);
{
	/* work 具体处理内容 */
}
/* 中断处理函数 */
irqreturn_t test_handler(int irq, void *dev_id)
{
	//......
	/* 调度 work */
	schedule_work(&testwork);
	//......
}
/* 驱动入口函数 */
static int __init xxxx_init(void)
{
	//......
    /* 初始化 work */
    INIT_WORK(&testwork, testwork_func_t);
    /* 注册中断处理函数 */
    request_irq(xxx_irq, test_handler, 0, "xxx", &xxx_dev);
    //......
}

三、共享工作队列demo

1. demo源码

源码可以看这里:13_interrupt/07_nodts_workqueue_share

2. 开发板测试

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

shell
insmod sdriver_demo.ko
image-20250323124243669

然后按下按键再释放,就会触发按键中断,按键中断中会提交一个工作项到工作队列,在工作队列的处理函数中,延时了1s:

image-20250323124431349

下面的两次是在工作队列的处理函数未执行完的时候又按了一次。大概也都是间隔了1s。

参考资料:

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

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