工作队列(workqueue
)是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行,最重要的就是工作队列允许被重新调度甚至睡眠。
linux系统启动期间会创建名为kworker/u:x
(x
是0
开始的整数,表示CPU
编号)工作者内核线程,该线程创建之后处于sleep
状态。从调度器的角度来解释,内核线程就是可以调度的进程;从代码表现形式看,本质是一个函数。
work_struct
,workqueue_struct
,struct cpu_workqueue_struct
三者之间关系如下:
CPU
创建一个cpu_workqueue_struct
结构,同时还会有一个内核工作的线程,这个线程创建好后处于睡眠状态,等待用户加入工作,来唤醒线程去调度工作结构体 work_struct
中的工作函数。work_struct
是通过链表连接在cpu_workqueue_struct
上,后面其他work_struct
连接在前一个后面,组成一个队列struct_work
结构中的工作函数,执行完成后就会把 work_struct
从链表上删除。work_struct
->
cpu_workqueue_struct
,然后把工作节点work_struct
加入到workqueue_struct
工作队列中,加入后工作者线程就会被唤醒,在适当的时机就会执行工作函数。不同版本内核中,对数据结构定义是不同的,以下是linux3.5
源码版本摘出来的数据结构
我们把推后执行的任务叫工作(work
),描述它的数据结构为work_struct
路径:workqueue.h linux-3.5\include\linux
struct work_struct {
atomic_long_t data;
struct list_head entry; //链表指针 把每个工作连接在一个链表上组成一个双向链表
work_func_t func; //函数指针 指向工作函数
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
补充work_func_t
结构
typedef void (*work_func_t)(void *work);
补充list_head
结构
struct list_head {
struct list_head *next, *prev;
};
我们编程只需要关注func成员它是工作函数指针就是用户需要延后执行的代码
这个结构是用来描述内核队列的数据结构。定义在workqueue.c linux-3.5\kernel
中,在workqueue.h linux-3.5\include\linux
中声明
具体定义如下:
struct workqueue_struct {
unsigned int flags; /* W: WQ_* flags */
union {
struct cpu_workqueue_struct __percpu *pcpu;
struct cpu_workqueue_struct *single;
unsigned long v;
} cpu_wq; /* I: cwq's */
struct list_head list; /* W: list of all workqueues */
struct mutex flush_mutex; /* protects wq flushing */
int work_color; /* F: current work color */
int flush_color; /* F: current flush color */
atomic_t nr_cwqs_to_flush; /* flush in progress */
struct wq_flusher *first_flusher; /* F: first flusher */
struct list_head flusher_queue; /* F: flush waiters */
struct list_head flusher_overflow; /* F: flush overflow list */
mayday_mask_t mayday_mask; /* cpus requesting rescue */
struct worker *rescuer; /* I: rescue worker */
int nr_drainers; /* W: drain in progress */
int saved_max_active; /* W: saved cwq max_active */
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
char name[]; /* I: workqueue name */
};
注意:这个结构表示一个工作队列,一般情况下驱动开发者不需要接触太多这个结构成员,关于队列操作,内核都提供了相应的API
函数
struct cpu_workqueue_struct {
struct global_cwq *gcwq; /* I: the associated gcwq */
struct workqueue_struct *wq; /* I: the owning workqueue */
int work_color; /* L: current color */
int flush_color; /* L: flushing color */
int nr_in_flight[WORK_NR_COLORS];
/* L: nr of in_flight works */
int nr_active; /* L: nr of active works */
int max_active; /* L: max active works */
struct list_head delayed_works; /* L: delayed works */
};
内核通过delayed_works
成员把第一个 work_struct
连接起来,后面work_struct
通过本身的entry
成员把自己连接在链表上。
内核工作队列分成共享工作队列和自定义工作队列两种
系统在启动时候自动创建一个工作队列驱动开发者如果想使用这个队列,则不需要自己创建工作队列,只需要把自己的work
添加到这个工作队列上即可。
使用schedule_work
这个函数可以把work_struct
添加到工作队列中
由于共享工作队列是大家共同使用的,如果上面的工作函数有存在睡眠的情况,阻塞了,则会影响到后面挂接上去的工作执行时间,当你的动作需要尽快执行,不想受其它工作函数的影响,则自己创建一个工作队列,然后把自己的工作添加到这个自定义工作队列上去。
使用自定义工作队列分为两步:
creat_workqueue(name)
创建一个名为name
的工作队列queue_work
函数把一个工作结构work_struc
添加到指定的工作队列上内核为了方便驱动开发者使用工作队列,给我们创建好一个工作队列,只要使用schedule_work
添加的工作节点都是添加到内核共享工作队列中,使用方法只需要开发者实现一个 work_struct
结构,然后把它添加到共享工作中去。
路径workqueue.h \linux-3.5\include\linux
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
功能:定义一个名字为n
的work_struct
结构变量,并且初始化它,工作是f
。
参数:n
要定义的work_struct
结构变量名,f
工作函数,要延后执行的代码
路径workqueue.h \linux-3.5\include\linux
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
功能:运行期间动态初始化work_struct
结构
参数:_work
要定义的work_struct
结构变量地址,_func
工作函数,要延后执行的代码
声明路径在workqueue.h \linux-3.5\include\linux
int queue_work(struct workqueue_struct *wq, struct work_struct *work)
{
int ret;
ret = queue_work_on(get_cpu(), wq, work);
put_cpu();
return ret;
}
功能:把一个work_struct
添加道共享工作队列中,成为一个工作节点。
参数:work
要定义的work_struct
结构变量名地址
返回值:0
表示已经挂接到共享工作队列上还未执行。非0
其他情况内核未做说明
函数返回值通常不需要驱动开发者关注
2/3
步void (*work_func_t)(void *work);
void mywork_func(struct work_struct *work)
{
······//工作内容
}
工作函数的参数是工作结构变量的首地址
- 定义一个工作结构
第一种:静态定义
DECLARE_WORK(mywork,mywork_func); //定义并且初始化
第二种:动态定义
struct work_struct mywork;
INIT_WORK(&mywork,mywork_func);
schedule_work(&mywork);
#include
#include
//添加头文件
#include
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
printk("%s is call!! work:%p\r\n",__FUNCTION__,work);
}
//定义一个struct work_struct结构变量,并且进行初始化
DECLARE_WORK(mywork,mywork_func); //定义并且初始化
static int __init mywork_init(void)
{
//一安装模块就进行调度
schedule_work(&mywork);
printk("%s is call!!",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += workqueue.o
[root@ZC/zhangchao]#insmod workqueue.ko
[ 3526.610000] mywork_init is call!!
[ 3526.615000] mywork_func is call!! work:bf0040c0
验证代码
#include
#include
//添加头文件
#include
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
printk("%s is call!! work:%p\r\n",__FUNCTION__,work);
}
//定义一个struct work_struct结构变量,并且进行初始化
DECLARE_WORK(mywork,mywork_func); //定义并且初始化
static int __init mywork_init(void)
{
//一安装模块就进行调度
schedule_work(&mywork);
printk("&mywork:%p\r\n",&mywork);
printk("%s is call!!\r\n",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod workqueue.ko
[ 4208.410000] &mywork:bf0080c4
[ 4208.410000] mywork_init is call!!mywork_func is call!! work:bf0080c4
运行结果表明:工作函数的参数是工作结构变量的首地址
为什么将工作结构体指针传入工作函数内部呢?用一个全局变量直接赋值不更方便么?原来在实际驱动开发中,经常将工作结构封装进我们自定义的数据结构中,这个数据结构还有其他成员,将数据结构地址传入,相比全局变量应用更加方便,同时减少了全局变量的使用,在一定意义上方便了驱动开发人员编写代码。
#include
#include
//添加头文件
#include
//定义一个自定义数据结构 , 将工作结构体包含其中
struct my_data {
struct work_struct mywork;
int x;
int y;
int z;
};
struct my_data mydata;
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
struct my_data *p=(struct my_data *)work;
printk("p->mywork is %p\r\n",&p->mywork);
printk("x:%d y:%d z:%d\r\n",mydata.x,mydata.y,mydata.z);
printk("p->x:%d,p->y:%d,p->z:%d\r\n",p->x,p->y,p->z);
}
//定义一个struct work_struct结构变量,并且进行初始化
//DECLARE_WORK(mywork,mywork_func); //定义并且初始化
static int __init mywork_init(void)
{
//初始化相关变量
mydata.x=123;
mydata.y=456;
mydata.z=789;
//工作结构初始化
INIT_WORK(&mydata.mywork,mywork_func);
//一安装模块就进行调度
schedule_work(&mydata.mywork);
printk("mywork:%p\r\n",&mydata.mywork);
printk("%s is call!!\r\n",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod workqueue.ko
[ 615.650000] p->mywork is bf004280
[ 615.650000] x:123 y:456 z:789
[ 615.650000] p->x:123,p->y:456,p->z:789
[ 615.650000] mywork:bf004280
[ 615.650000] mywork_init is call!!
我们能观察到,通过全局变量打印的结果与通过指针传入打印的结果是相同的,如果采用指针传入的方式可以避免全局变量的使用,方便实用。由于传入的work工作结构恰好是结构体第一个成员,传入工作结构地址也就是传入整个结构的首地址,如果工作结构不是第一个成员那要怎样处理呢?
#include
#include
//添加头文件
#include
//定义一个自定义数据结构 , 将工作结构体包含其中 在第二位
struct my_data {
int x;
struct work_struct mywork;
int y;
int z;
};
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
struct my_data *p=(struct my_data *)((unsigned int)work-4);
//打印工作数据结构地址
printk("p->mywork is %p\r\n",&p->mywork);
printk("p->x:%d,p->y:%d,p->z:%d\r\n",p->x,p->y,p->z);
}
//定义一个struct work_struct结构变量,并且进行初始化
//DECLARE_WORK(mywork,mywork_func); //定义并且初始化
static int __init mywork_init(void)
{
//不使用全局变量 在函数其中定义
struct my_data mydata;
//初始化相关变量
mydata.x=123;
mydata.y=456;
mydata.z=789;
//工作结构初始化
INIT_WORK(&mydata.mywork,mywork_func);
//一安装模块就进行调度
schedule_work(&mydata.mywork);
//打印结构体首地址
printk("mydata.x:%p\r\n",&mydata.x);
printk("%s is call!!\r\n",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod workqueue.ko
[ 1437.705000] p->mywork is ed063ea0
[ 1437.705000] p->x:123,p->y:456,p->z:789
[ 1437.705000] mydata.x:ed063e9c
[ 1437.705000] mywork_init is call!!
运行结果表明虽然传入的工作结构并不是数据结构的第一个成员,但是在工作函数中还原地址时,只需要将前面成员所占用地址减去就能得到数据结构的首地址,打印的结果也不会出现错误。但是!!这是在数据成员不复杂的情况下可以计算得出数据结构的首地址,如果数据结构成员很复杂、很多,计算起来就会很麻烦而且容易出错,因此,计算的办法不太好,怎么做?内核提供了一个计算宏可以帮我们算出成员所在数据结构的首地址
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) * __mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); })
#endif
功能:算出成员所在数据结构的首地址
参数:
ptr:已知结构的变量成员地址(&mydata.mywork
)
type:已知结构所在结构的变量类型(struct my_data
)
member:结构体成员名(mywork
)
#include
#include
//添加头文件
#include
//定义一个自定义数据结构 , 将工作结构体包含其中 在第二位
struct my_data {
int x;
struct work_struct mywork;
int y;
int z;
};
struct my_data mydata;
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
struct my_data *p=(struct my_data *)container_of(work,struct my_data,mywork);
//打印工作数据结构地址
printk("p->mywork is %p\r\n",&p->mywork);
printk("p->x:%d,p->y:%d,p->z:%d\r\n",p->x,p->y,p->z);
}
//定义一个struct work_struct结构变量,并且进行初始化
//DECLARE_WORK(mywork,mywork_func); //定义并且初始化
static int __init mywork_init(void)
{
//初始化相关变量
mydata.x=123;
mydata.y=456;
mydata.z=789;
//工作结构初始化
INIT_WORK(&mydata.mywork,mywork_func);
//一安装模块就进行调度
schedule_work(&mydata.mywork);
//打印结构体首地址
printk("mydata.x:%p\r\n",&mydata.x);
printk("%s is call!!\r\n",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod workqueue.ko
[ 3151.620000] mydata.x:bf010258
[ 3151.620000] mywork_init is call!!
[ 3151.625000] p->mywork is bf01025c
[ 3151.625000] p->x:123,p->y:456,p->z:789
[root@ZC/zhangchao]#
结果表明这个宏能起到计算首地址效果
根据前面的框架图可以知道,需要我们自己先创建一个工作队列,再往工作队列中添加工作结构。
CPU
创建一个cpu_workqueue_struct
结构 workqueue.h linux-3.5\include\linux
#define create_workqueue(name) \
alloc_workqueue((name), WQ_MEM_RECLAIM, 1)
补充alloc_workqueue:
#ifdef CONFIG_LOCKDEP
#define alloc_workqueue(fmt, flags, max_active, args...) \
({ \
static struct lock_class_key __key; \
const char *__lock_name; \
\
if (__builtin_constant_p(fmt)) \
__lock_name = (fmt); \
else \
__lock_name = #fmt; \
\
__alloc_workqueue_key((fmt), (flags), (max_active), \
&__key, __lock_name, ##args); \
})
#else
#define alloc_workqueue(fmt, flags, max_active, args...) \
__alloc_workqueue_key((fmt), (flags), (max_active), \
NULL, NULL, ##args)
#endif
作用:创建一个名为name的工作队列
参数:工作队列的名字是一个字符串
返回值:
成功:创建的工作队列指针struct workqueue_struct *
失败:返回NULL
create_singlethread_workqueue(name)
作用和上面的相同区别在于,如果只创建一个cpu_workqueue_struct
结构,其余属性也相同
queue_work
int queue_work(struct workqueue_struct *wq, struct work_struct *work);
作用:往wq
工作队列中添加work
工作节点
参数:wq
自己定义的工作队列结构变量地址,work
自己要添加的工作队列节点
返回值:
其实系统工作队列调用的也是这个函数,只不过系统工作队列只有一个在函数内部封装起来,不用我们自己去填写
int schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
void destroy_workqueue(struct workqueue_struct *wq)
wq
自定一工作队列的指针#include
#include
//添加头文件
#include
//定义一个自定义数据结构 , 将工作结构体包含其中 在第二位
struct my_data {
int x;
struct work_struct mywork;
int y;
int z;
};
struct my_data mydata;
//创建一个自定义工作队列指针 用来承接创建等待队列成功的地址
struct workqueue_struct *mywq;
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
struct my_data *p=(struct my_data *)container_of(work,struct my_data,mywork);
//打印工作数据结构地址
printk("p->mywork is %p\r\n",&p->mywork);
printk("p->x:%d,p->y:%d,p->z:%d\r\n",p->x,p->y,p->z);
}
//定义一个struct work_struct结构变量,并且进行初始化
//DECLARE_WORK(mywork,mywork_func); //定义并且初始化
static int __init mywork_init(void)
{
//初始化相关变量
mydata.x=123;
mydata.y=456;
mydata.z=789;
//创建自定义工作队列
mywq=create_workqueue("mywq");
if( mywq ==NULL)
{
printk("create_workqueue error\r\n");
return -1;
}
printk("create_workqueue ok\r\n");
//工作结构初始化
INIT_WORK(&mydata.mywork,mywork_func);
//一安装模块就进行调度
//schedule_work(&mydata.mywork);
queue_work(mywq,&mydata.mywork);
//打印结构体首地址
printk("mydata.x:%p\r\n",&mydata.x);
printk("%s is call!!\r\n",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
destroy_workqueue(mywq);
}
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod workqueue.ko
[ 287.095000] create_workqueue ok
[ 287.095000] mydata.x:bf0002ac
[ 287.095000] mywork_init is call!!
[ 287.095000] p->mywork is bf0002b0
[ 287.095000] p->x:123,p->y:456,p->z:789
与前面说的共享队列,还是自定义工作队列不同,延时队列是在调度时,需要等待指定时间才会调用工作函数。
路径:workqueue.h linux-3.5\include\linux
内核使用一个delayed_work
结构来描述一个要延期执行的工作,其定义如下:
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
从结构可以知道:延时工作队列就是把普通工作队列结构和一个内核定时器结合在一起实现延长指定时间的才调度。
workqueue.h linux-3.5\include\linux
#define DECLARE_DELAYED_WORK(n, f) \
struct delayed_work n = __DELAYED_WORK_INITIALIZER(n, f)
功能:定义一个名字为n
的delayed_work
结构变量,并且初始化它,工作函数是f
参数:
n:要定义的delayed_work
结构变量名
f:工作函数,就是要延期执行的代码
#define INIT_DELAYED_WORK(_work, _func) \
do { \
INIT_WORK(&(_work)->work, (_func)); \
init_timer(&(_work)->timer); \
} while (0)
功能:运行期间动态初始化一个delayed_struct
结构
参数:
_work:初始化的要定义的delayed_struct
结构
_func:工作函数,要延期执行的代码
int schedule_delayed_work(struct delayed_work *work, unsigned long delay);
delayed_struct
添加到延时共享工作队列中,成为一个工作节点。 delayed_struct
结构地址 jiffies
0
表示已经挂接到共享工作队列上还未执行。非0
其他情况内核未做说明 int queue_delayed_work(struct workqueue_struct *wq,
struct delayed_work *work, unsigned long delay);
功能:把一个delayed_struct
添加到自定义延时工作队列中,成为一个工作节点。
参数:
wq:自定义的延时工作队列结构地址
work:定义的delayed_struct
结构地址
delay:延时时间,时间单位时钟节拍jiffies
返回值:0
表示已经挂接到共享工作队列上还未执行。非0
#include
#include
//添加头文件
#include
//实现一个work_func工作函数
void mywork_func(struct work_struct *work)
{
printk("12345678\r\n");
}
//分配结构体
//struct delayed_work mydelayed_work; 动态初始化
DECLARE_DELAYED_WORK(mydelayed_work, mywork_func);
static int __init mywork_init(void)
{
//一安装模块就进行调度
schedule_delayed_work(&mydelayed_work,2*HZ);
printk("%s is call!!\r\n",__FUNCTION__);
return 0;
}
static void __exit mywork_exit(void)
{
printk("mywork is exit!\r\n");
}
struct delayed_work;
module_init(mywork_init);
module_exit(mywork_exit);
MODULE_LICENSE("GPL");
[root@ZC/zhangchao]#insmod workqueue.ko
[ 5573.260000] mywork_init is call!!
[root@ZC/zhangchao]#[ 5575.265000] 12345678
其余功能与上面的工作队列差不多可自行测试