TA的每日心情 | 奋斗 2016-4-14 10:16 |
---|
签到天数: 9 天 连续签到: 1 天 [LV.3]偶尔看看II
|
内核工作队列概述
工作队列(workqueue)是另外一种将工作推后执行的形式,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行,最重要的就是工作队列允许被重新调度甚至睡眠。
linux 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工作队列中,加入后工作者线程就会被唤醒,在适当的时机就会执行工作函数。
工作队列数据结构
work_struct
我们把推后执行的任务叫工作(work),描述它的数据结构为work_struct
路径:workqueue.h /work/lichee/linux-3.4/include/linux/workqueue.h
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_struct
这个结构是用来描述内核队列的数据结构。定义在workqueue.c
具体定义如下:
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函数
cpu_workqueue_struct
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添加到指定的工作队列上
linux内核共享工作队列
共享工作队列介绍
内核为了方便驱动开发者使用工作队列,给我们创建好一个工作队列,只要使用schedule_work 添加的工作节点都是添加到内核共享工作队列中,使用方法只需要开发者实现一个 work_struct结构,然后把它添加到共享工作中去。
内核共享队列API
静态定义工作结构DECLARE_WORK
#define DECLARE_WORK(n, f) \
struct work_struct n = __WORK_INITIALIZER(n, f)
功能:定义一个名字为n的work_struct结构变量,并且初始化它,工作是f。
参数:n要定义的work_struct结构变量名,f工作函数,要延后执行的代码
动态初始化工作结构INIT_WORK
#define INIT_WORK(_work, _func) \
do { \
__INIT_WORK((_work), (_func), 0); \
} while (0)
功能:运行期间动态初始化work_struct结构
参数:_work要定义的work_struct结构变量地址,_func工作函数,要延后执行的代码
调度工作schedule_work
声明路径在workqueue.h workqueue.h /work/lichee/linux-3.4/include/linux/workqueue.h
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步
- #include<linux/module.h>
- #include<linux/init.h>
- //添加头文件
- #include<linux/workqueue.h>
- //实现一个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");
复制代码
参考博文:https://blog.csdn.net/z961968549/article/details/78758527
|
|