论linux内核工作队列

来源:互联网 发布:淘宝打假赚钱怎么赚钱 编辑:程序博客网 时间:2024/04/30 15:28

说到工作队列,相信接触linux内核驱动的同学肯定看到过,但是为什么要引入工作队列呢?工作队列什么时候引入的?
它的作用是什么?我们应该怎么使用它等等问题,一定困惑了不少刚接触驱动的新人,当然也困惑了我很长一段时间
今天就我个人的学习经历以及查找网上资源进行一个小结,本文仅代表个人愚见,如果不足之处还请指正和交流沟通,下面就进入正文!
工作队列的引入是为了把工作推后执行,工作推后执行之前已经有了软中断和tasklet这两种下半部机制为什么还要进入工作队列,
其实tasklet就是软中断的一个补充添加了一定的机制,软中断不能被自己打断,能被硬中断打断,可以并发运行于多个cpu上,所以软中断必须设计为可重入函数
那么其数据结构就需要自旋锁保护防止被修改为不期望的数据。基于软中断必须使用可重入函数,带来编程的复杂度,
于是就出现了tasklet补充这个不足,其实软中断类型默认11中,而tasklet就是其中的HI_SOFTIRQ和TASKLET_SOFTIRQ两种相当于
是实现也是软中断机制一种特定的tasklet只能在一个cpu上运行,不同的tasklet可以运行在不同的cpu上;软中断是静态分配的,在内核编译好后就不能改变
tasklet就灵活许多。

其实相比于软中断和tasklet其优势在于交给一个内核线程去执行,也就是说这个下半部允许在进程上下文中执行,尤其是可以重新调度甚至是睡眠
那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列。
如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体执行你的下半部处理,
也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。
这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。
如果不需要用一个内核线程来推后执行工作,那么就考虑使用 tasklet。

前面描述了关于工作队列的引入带来的优势和作用,下面就来描述一下工作队列的用法
工作队列是在内核2.6版本引入的机制,在2.6.20之后做了些许改动,主要是数据结构上和函数API上的改动,具体看下面
在2.6.20之前的数据结构
struct work_struct {
unsigned long pending;
struct list_head entry;
void (func)(void );
void *data;
void *wq_data;
struct timer_list timer;
};
结构成员说明
pending是用来记录工作是否已经挂在队列上;
entry是循环链表结构;
func作为函数指针,由用户实现;
data用来存储用户的私人数据,此数据即是func的参数;
wq_data一般用来指向工作者线程(工作者线程参考下文);
timer是推后执行的定时器。
work_struct的这些变量里,func和data是用户使用的,其他是内部变量,我们可以不用太关心。
API:
1、INIT_WORK(_work, _func, _data);
2、int schedule_work(struct work_struct *work);
3、int schedule_delayed_work(struct work_struct *work, unsigned long delay);
4、void flush_scheduled_work(void);
5、int cancel_delayed_work(struct work_struct *work);
1、初始化指定工作,目的是把用户指定的函数_func及_func需要的参数_data赋给work_struct的func及data变量。
2、对工作进行调度,即把给定工作的处理函数提交给缺省的工作队列和工作者线程。工作者线程本质上是一个普通的内核线程,在默认情况下,每个CPU均有一个类型为“events”的工作者线程,当调用schedule_work时,这个工作者线程会被唤醒去执行工作链表上的所有工作。
3、延迟执行工作,与schedule_work类似。
4、刷新缺省工作队列。此函数会一直等待,直到队列中的所有工作都被执行。
5、flush_scheduled_work并不取消任何延迟执行的工作,因此,如果要取消延迟工作,应该调用cancel_delayed_work。
以上均是采用缺省工作者线程来实现工作队列,其优点是简单易用,缺点是如果缺省工作队列负载太重,执行效率会很低,这就需要我们创建自己的工作者线程和工作队列。

API:
struct workqueue_struct *create_workqueue(const char *name);
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *wq, struct work_struct *work, unsigned long delay);
void flush_workqueue(struct workqueue_struct *wq);
void destroy_workqueue(struct workqueue_struct *wq);
1、创建新的工作队列和相应的工作者线程,name用于该内核线程的命名。
2、类似于schedule_work,区别在于queue_work把给定工作提交给创建的工作队列wq而不是缺省队列。
3、延迟执行工作。
4、刷新指定工作队列。
5、释放创建的工作队列。

2.6.20之后数据结构
typedef void (*work_func_t)(struct work_struct *work);
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
相比于之前的版本数据结构entry没有变,func和data变了,而其它变量没有了
data的类型是atomic_long_t,可以看出是原子类型,与之前的用法有所改变,现在是之前版本pending和wq_data的复合体;
func的参数是一个指向work_struct的指针,指向的数据就是定义func的work_struct。
所以可能就有疑问:用户数据如何传输给函数和如何做到延时执行?之前版本是void *data传参和timer表示延时时间

关于延时重新定义了一个结构体
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
这么做的目的是只有在需要延时的工作才会有到timer,之前的显然有点浪费,而且普通情况下timer并没有意义。
API
初始化队列动态创建
INIT_WORK(struct work_struct *work, work_func_t func);//定义正常执行的工作项
INIT_DELAYED_WORK(struct delayed_work *work, work_func_t func);//定义延后执行的工作项

静态创建
DECLARE_WORK(name,function); //定义正常执行的工作项
DECLARE_DELAYED_WORK(name,function);//定义延后执行的工作项

创建工作队列
struct workqueue_struct *create_singlethread_workqueue(const char *name);
struct workqueue_struct *create_workqueue(const char *name);
相比于create_singlethread_workqueue,create_workqueue同样会分配一个wq的工作队列,但是不同之处在于,对于多cpu系统而言,
对每一个cpu,都会为之创建一个per_CPU的cwq结构,对应每一个cwq,都会生成一个新的work_thread进程,但是当用queue_work向cwq上
提交work节点时,是哪个cpu调用该函数,便向该cpu对应的cwq上的worklist上增加work节点
int schedule_work(struct work_struct *work);
work马上会被调用,一旦其所在的处理器工作线程被唤醒就会被执行
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
work指向的工作在delay指定的节拍之后才会被执行
struct workqueue_struct *create_workqueue(const char *name);
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
把工作交给指定的工作队列wq,而不是缺省的工作队列
int queue_delayed_work(struct workqueue_struct *wq, struct delayed_work *work, unsigned long delay);
调度延迟工作队列
void flush_scheduled_work(void);//刷新缺省的工作队列
void flush_workqueue(struct workqueue_struct *wq);刷新指定工作队列
int cancel_delayed_work(struct delayed_work *work);删除工作
void destroy_workqueue(struct workqueue_struct *wq);释放创建的工作队列

demo示例1
static init_events(struct work_struct *work);
struct workqueue_struct *wq;
静态创建工作
static DECLARE_WORK(init_work, init_events);
在内核模块驱动执行prob函数中
创建工作队列
wq = create_singlethread_workqueue(“init_work”);
if (wq == NULL) {
printk(“create wq fail!\n”);
return -ENOMEM;
}

然后可以使用进行调度队列
queue_work(wq, &init_work);

demo示例2
动态创建工作
struct workqueue_struct *wq;
struct work_struct init_work;

在内核模块驱动执行prob函数中
INIT_WORK(&init_work, init_events);
创建工作队列
wq = create_singlethread_workqueue(“init_work”);
if (wq == NULL) {
printk(“create wq fail!\n”);
return -ENOMEM;
}

queue_work(wq, &init_work);

示例3
static struct delayed_work work;
static struct workqueue_struct * workqueue = NULL;
创建延时工作达到轮询的效果
INIT_DELAYED_WORK(&work, init_events);
workqueue = create_workqueue(“poll_check”);
调度延迟工作队列(轮询调度)
queue_delayed_work(gtp_esd_check_workqueue, &work,delay);
cancel_delayed_work_sync(&work);等待直到删除(不能用于中断上下文)
destroy_workqueue(wq);删除创建的工作队列

1 0