该文章翻译自:http://www.tune2wizard.com/kernel-programming-workqueue/
该文章的源代码测试环境是:Ubuntu-16.04,linux内核版本:4.2.1
这篇文章主要讲的是工作队列(workqueue)相关的知识。首先我们讲一些workqueue的基础知识,然后会通过一个实际的例子讲解一下workqueue的应用。跟tasklet比较相似,都用在中断下半部分的处理。workqueue运行在内核线程的上下文,而tasklet运行在中断上下文,故workqueue允许睡眠的,而tasklet是不允许睡眠的;也由此可知,workqueue较tasklet有较大的延迟,对于那些有较高时间性能要求的不推荐使用workqueue。
一、workqueue的工作原理
首先,将工作处理程序(处理函数)和工作项(work)关联起来;第二步,需要一个内核线程处理提交到工作队列上的函数;第三步,将工作项(work)提交到工作队列。
二、编码流程
工作队列的结构体
struct workqueue_struct;
1、将工作处理程序(处理函数)和工作项(work)关联起来
#include <linux/workqueue.h>
DECLARE_WORK (struct work_struct name, void (*func)(void *));
或者用动态的方式来完成这项工作
INIT_WORK(struct work_struct *work, void (*function)(void *)
2、创建一个特定的线程,执行我们提交的工作(work)
i)专用内核线程
struct workqueue_struct *create_workqueue(const char *name);
ii)如果负载不是太高,我们可以使用普通的内核线程
struct workqueue_struct *create_singlethread_workqueue(const char *name);
3、提交工作项(work)到工作队列(workqueue)
下面的这个API可以将工作线(work)提交到我们第2步创建的工作队列,函数执行成功将会返回0.
int queue_work(struct workqueue_struct *queue, struct work_struct *work);
如果使用的是共享队列,使用下面的API可以很快的调用执行指定的工作项(work)
int schedule_work(struct work_struct *work);
4、清理工作队列使用下面的API:
void flush_scheduled_work(void);
5、一旦工作队列被创建,在cleanup()中使用下面的API销毁创建的工作队列
void destroy_workqueue (struct workqueue_struct * wq);
6、kmalloc() – 与用户控件的malloc()函数很相似. 但是kmalloc()从内核空间分配一定数量字节的空间,并且kmalloc分配的区域在物理上是连续的,下面API
#include <linux/slab.h>
#include <linux/gfp.h>
void * kmalloc(size_t size, int flags);
flags定义在 <linux/gfp.h>,它们分别是:
1. GFP_KERNEL – 当时用该标志时,如果申请不到内存,进程将会睡眠;内核为了使得该函数成功执行,会专门释放一些内存,所以该标志不适合使用在tasklet,中断等
2. GFP_ATOMIC – 该标志可以在中断执行路径中使用,该标志不会睡眠
3. GFP_USER – 为用户空间的页分配内存,可能会发生睡眠
4. GFP_HIGHUSER – 为用户空间分配高端内存
当使用了kmalloc函数分配了内存,我们可以使用下面的API释放
void kfree(const void *ptr);
container_of()函数的使用
parentPtr = container_of( container_field_pointer, container_type, container_field );
该函数可以通过结构体中某个成员的地址来获得结构体的地址,我们举个例子在下面的代码解释一下,通过结构体成员&data来获得data所在结构体的地址:
#include <linux/kernel.h>
struct containerExample_t
{
int data;
char name[100];
};
containerExample_t *containerPtr = container_of(&data,struct containerExample_t,data);
三、代码部分:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
MODULE_LICENSE("GPL");
/** 申明含有工作项(struct work_struct)的结构体 **/
typedef struct
{
struct work_struct my_work;
int x;
}my_work_t;
/** my_work_t 实例 **/
my_work_t *work;
/** 工作队列的处理函数 **/
static void ework_handler(struct work_struct *pwork);
//static void ework_handler2(struct work_struct *pwork);
/** Declare the workqueue struct **/
static struct workqueue_struct *eWq = 0;
/** 静态的将工作项与workqueue关联起来 */
/** eWorkqueue - 工作队列的名字, ework_handler - 工作队列的处理函数 **/
//static DECLARE_WORK(eWorkqueue, ework_handler2);
/*
static void ework_handler2(struct work_struct *pwork)
{
printk("The ework_handler.2..called\r\n");
}
*/
static void ework_handler(struct work_struct *pwork)
{
my_work_t *temp;
printk("The ework_handler...called\r\n");
/** pwork 是 my_work 的指针 **/
temp = container_of(pwork,my_work_t,my_work);
printk("The value of x is %d\r\n",temp->x);
}
static int eworkqueue_init(void)
{
printk("Hello eWorkqueue !!! Welcome!!\r\n");
printk("Create work struct object!!\r\n");
work = (my_work_t *) kmalloc(sizeof(my_work_t), GFP_KERNEL);
work->x = 1010;
/** Init the work struct with the work handler **/
INIT_WORK( &(work->my_work), ework_handler );
if (!eWq)
{
printk("ewq..Single Thread created\r\n");
eWq = create_singlethread_workqueue("eWorkqueue");
}
if (eWq)
{
printk("Push!! my work into the eWq--Queue Work..\r\n");
queue_work(eWq, &(work->my_work) );
}
/** 如果我们想使用默认的内核线程,可以不用创建eWq,使用下面的函数即可**/
/* schedule_work(); */
return 0;
}
static void eworkqueue_exit(void)
{
if (eWq)
destroy_workqueue(eWq);
kfree(work);
printk("GoodBye..WorkQueue");
}
module_init(eworkqueue_init);
module_exit(eworkqueue_exit);
四、Makefile文件
obj-m := Workqueue.o
PWD := $(shell pwd)
KERNEL := /lib/modules/$(shell uname -r)/build
all:
make -C $(KERNEL) M=$(PWD) modules
clean:
rm -rf *.o *~ core .*.cmd *.mod.c *.order *.symvers *.ko ./tmp_version
注:可能会遇到的错误:执行insmod之后会报invalid parameters类似的错误;通过命令dmesg | tail 查看后台输出的消息,如果提示workqueue:module is already loaded,那么我们可以把文件不要命名成workqueue.c就可以去除错误了。
总结:我们做的工作主要是,将要执行的函数ework_handler()绑定到工作项work->my_work,接着将工作项绑定到指定的工作队列