本文档的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()创建返回。