LV075-共享工作队列
一、相关的api
1. 初始化函数
1.1 动态创建工作:INIT_WORK
在实际的驱动开发中, 我们只需要定义工作项(work_struct)即可, 关于工作队列和工作者线程我们基本不用去管。 简单创建工作很简单, 直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作, INIT_WORK 宏定义如下:
#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, 分别表示要初始化的工作项和工作项的处理函数。示例如下:
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 宏在编译时静态地完成工作的创建和初始化, 宏定义如下:
#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 表示工作对应的处理函数。简单示例如下:
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():
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}参数是指向工作项的指针。 这个函数作用是将工作项提交到工作队列中, 并请求调度器在合适的时机执行工作项,该函数会返回一个布尔值, 表示工作项是否成功被提交到工作队列。
这个函数会把 work 提供给系统默认的 work queue: system_wq,它是一个队列:
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() 函数:
bool cancel_work_sync(struct work_struct *work)
{
return __cancel_work_timer(work, false);
}
EXPORT_SYMBOL_GPL(cancel_work_sync);参数是指向工作项的指针。 这个函数的作用是取消该工作项的调度。 如果工作项已经在工作队列中, 它将被从队列中移除。 如果工作项已经在工作队列中, 它将被从队列中移除, 并等待工作项执行完成。 函数返回一个布尔值, 表示工作项是否成功取消。
二、参考示例
/* 定义工作(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. 开发板测试
拷贝驱动到开发板中,加载驱动:
insmod sdriver_demo.ko
然后按下按键再释放,就会触发按键中断,按键中断中会提交一个工作项到工作队列,在工作队列的处理函数中,延时了1s:

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