Linux设备驱动程序 之 工作队列

工作队列可以把工作推后,交给一个内核线程去执行–这个下半部分总是会在进程上下文中执行;通过工作队列执行的代码占尽进程上下文的优势;最重要的是工作队列允许重新调度甚至睡眠;

在工作队列和软中断/tasklet中做出选择很容易;如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择软中断或者tasklet;

如果需要用一个可以重新调度的实体来执行下半部处理,那么应该使用工作队列;它是唯一能在进程上下文中运行的下半部机制,也只有它才可以睡眠;这意味着如果需要获取大量的内存,或者在需要获取信号量时,或者需要执行阻塞式IO操作时,它会非常有用;如果不需要一个内核线程来推后执行工作,那么考虑使用tasklet;

创建推后的工作

可以通过DECLARE_WORK在编译时静态的创建work_struct结构实例并初始化:

1 #define DECLARE_WORK(n, f)                        \
2     struct work_struct n = __WORK_INITIALIZER(n, f)
3 
4 #define DECLARE_DELAYED_WORK(n, f)                    \
5     struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, 0)
6 
7 #define DECLARE_DEFERRABLE_WORK(n, f)                    \
8     struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f, TIMER_DEFERRABLE)

或者在运行时动态的创建一个work_struct实例,然后使用下面函数进行初始化:

 1 #define INIT_WORK(_work, _func)                        \
 2     __INIT_WORK((_work), (_func), 0)
 3 
 4 #define INIT_WORK_ONSTACK(_work, _func)                    \
 5     __INIT_WORK((_work), (_func), 1)
 6 
 7 #define INIT_DELAYED_WORK(_work, _func)                    \
 8     __INIT_DELAYED_WORK(_work, _func, 0)
 9 
10 #define INIT_DELAYED_WORK_ONSTACK(_work, _func)                \
11     __INIT_DELAYED_WORK_ONSTACK(_work, _func, 0)
工作队列处理函数

工作队列处理函数的原型是:

1 typedef void (*work_func_t)(struct work_struct *work);

这个函数会由一个工作者线程执行,因此,函数会运行在进程上下文中;默认情况下,允许响应中断,并不持有任何锁;如果需要,函数可以睡眠;

需要注意的是,尽管操作处理函数运行在进程上下文,但它不能访问用户空间,因为内核线程在用户空间没有相关的内存映射;通常在发送系统调用时,内核会代表用户空间进程运行,也只有此时它才会映射用户空间的内存;

调度工作(缺省工作队列)

如果要把给定工作的处理交给缺省的工作线程,则需要调用:

1 bool schedule_work(struct work_struct *work)

work马上会被调度,一旦其所在的处理器上的工作线程被唤醒,它就会被执行;

如果不希望马上被执行,而是希望它经过一段时间的延迟后才执行,则需要下面函数,可以在指定时间之后执行:

1 bool schedule_delayed_work(struct delayed_work *dwork,unsigned long delay)

这时,dwork会在delay指定的始终节拍用完之后才会执行;

刷新操作

排入队列的工作会在工作者线程下一次被唤醒时执行;有时,在继续下一步工作之前,必须保证一些操作已经执行完毕了;这一点对模块来说就很重要,在卸载之前,它就有可能需要调用下面的函数;而在内核的其他部分,为了防止竞争条件的出现,也可能需要确保不再有待处理的工作;

为了,内核准备了用于刷新指定工作队列的函数:

1 bool flush_work(struct work_struct *work);
2 
3 bool flush_delayed_work(struct delayed_work *dwork);

函数会一直等待,直到队列中的所有对象都被执行以后才返回;在等待所有待处理的工作执行的时候,该函数会进入休眠状态,所以只能在进程上下文中使用;

内核也提供了取消执行工作的函数:

1 bool cancel_work(struct work_struct *work);
2 bool cancel_work_sync(struct work_struct *work);
3 
4 bool cancel_delayed_work(struct delayed_work *dwork);
5 bool cancel_delayed_work_sync(struct delayed_work *dwork);
创建新的工作队列

如果缺省队列不能满足需求,则应该创建一个新的工作队列和与之对应的工作者线程;由于这么做会在每个处理器上都创建一个工作者线程,所以只有在你明确了必须要靠自己一套线程来提高性能的情况下,再创建自己的队列;

创建一个新的任务队列和与之相关的工作者线程,需要使用下面函数,name参数用于该内核线程的命名;

1 #define create_workqueue(name)                        \
2     alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))
3 #define create_freezable_workqueue(name)                \
4     alloc_workqueue("%s", __WQ_LEGACY | WQ_FREEZABLE | WQ_UNBOUND |    \
5             WQ_MEM_RECLAIM, 1, (name))
6 #define create_singlethread_workqueue(name)                \
7     alloc_ordered_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, name)

这样就会在每个处理器上创建工作者线程,并准备好开始处理工作之前的准备工作;

 

创建一个工作的时候无须考虑工作队列的类型,在创建之后,可以调用下面的函数;这些函数与schedule_work()相似,唯一的区别是它们针对给定的工作队列而不是缺省的队列进行操作;

1 bool queue_work(struct workqueue_struct *wq,
2                   struct work_struct *work)
3 
4 bool queue_delayed_work(struct workqueue_struct *wq,
5                       struct delayed_work *dwork,
6                       unsigned long delay)

 

刷新指定的工作队列可以使用下面函数:

1 void flush_workqueue(struct workqueue_struct *wq);

 

结束对工作队列的使用后,可以使用下面函数释放资源:

1 void destroy_workqueue(struct workqueue_struct *wq);

 

posted @ 2019-10-29 21:50  AlexAlex  阅读(1239)  评论(0编辑  收藏  举报