如何使用Linux工作队列workqueue


本文档的Copyleft归rosetta所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性。

参考资料:《Linux设备驱动程序》第3版 LDD3e, LKD3e, 《 Linux per-CPU实现分析 》,linux-2.6.27,irq_balance

要使用workqueue当然逃不了per-CPU,per-CPU顾名思义,每个CPU,很多地方直接翻译为“每CPU"。关于per-CPU的接口操作就是对于所有CPU的操作,即每个CPU都有一个数据副本或者线程等,如果这个接口传入指定CPU编号,那么就是对某个指定的CPU操作。具体的per-CPU内核实现请看之前转载的文章《Linux per-CPU实现分析 》见
       
1,创建一个per-CPU
        *编译期间静态创建一个per-CPU      
        DEFINE_PER_CPU(type, name) 
        创建一个名为name,数据类型为type的per-CPU,比如static DEFINE_PER_CPU(struct sk_buff_head, bs_cpu_queues),此时每个CPU都有一个名叫bs_cpu_queues,数据结构为sk_buff_head的变量副本。每个副本都是在自己的CPU上工作。
     
      * 动态创建per-CPU,以下代码是内核create_workqueue实现的片断
        struct workqueue_struct *__create_workqueue(const char *name,
                        int singlethread)
        {
            int cpu, destroy = 0;
            struct workqueue_struct *wq;
            struct task_struct *p;
       
            wq = kzalloc(sizeof(*wq), GFP_KERNEL);
            if (!wq)
                return NULL;
       
            wq->cpu_wq = alloc_percpu(struct cpu_workqueue_struct);
            if (!wq->cpu_wq) {
                kfree(wq);
                return NULL;
                 }
                ……
        }

2,使用工作队列
        *编译时静态创建,因为暂时用不到也就没去看具体实现过程和用法。
        #define DECLARE_WORK(name, void(*func)(void *), void *data);
        其使用的默认处理函数为work_handler(void *data)。
        使用schedule_work(&work)进行工作调度。
        *动态创建
        INIT_WORK(struct work_struct *work,void(*func)(void *), void *data);
        比如如下代码
        static DEFINE_PER_CPU(struct sk_buff_head, bs_cpu_queues);
        int netif_receive_skb(struct sk_buff *skb)//接收到网卡数据
        {
                ……
                ……
                static inline int bs_dispatch(struct sk_buff *skb)//分发网卡数据
                {
                         struct sk_buff_head *q;
                        q = &per_cpu(bs_cpu_queues, cpu);//从CPU为cpu处取一个sk_buff_head数据结构
                        //把数据skb插入到双向循环链表q中。这样子这个q就是待处理的数据了。

                        bs_works = &per_cpu(bs_works, cpu);//从CPU为cpu处取一个work_struct结构
                        if (!bs_works->func) {//假如当前工作处理函数指针为空
                                 INIT_WORK(bs_works, bs_func, q);//创建工作队列,工作队列函数指为bs_func,处理的数据为q。
                                 queue_work(per_cpu_ptr(keventd_wq->cpu_wq, cpu), bs_works);//在CPU编号为cpu的默认队列keventd_wq中插入一个bs_works工作任务。具体看下面。                         
                        }
                 }
                ……
                ……
        } 

3,创建新的工作队列已经在《Linux工作队列workqueue实现分析》讲过了,是通过create_workqueue实现,这里不再重复。

4,工作调度
        *默认工作队列处理函数
        void work_handler(void *data)//在好几个版本的内核里没有发现这个函数或者宏定义,只找到了work_handlers,它其实是个函数指针数组,具体实现没仔细看,大概就是为初始化一些函数,再看时机进行调度。
        使用schedule_work()对默认的event队列进程调度。
        *调度新创建的工作队列
        int queue_work(struct workqueue_struct *wq, struct work_struct *work)
        从其参数可知,它是对某种类型的任务进行工作调度,即这种类型的每个CPU中的工作者线程的每个体work_struct都要被调度。
        由于上面使用的是per_cpu_ptr(keventd_wq->cpu_wq, cpu),其返回的是CPU为cpu的keventd_wq->cpu_wq workqueue_struct结构,即默认的工作队列,所以上面其实可以使用schedule_work(&work)进行调度。使用queue_work一般是自己指定自行创建的工作队列wq,这个工作队列由 create_workqueue()创建返回。

相关内容