简单使用:
(1)定义:
struct delayed_work test_delaywork;
(2)初始化
INIT_DELAYED_WORK(&test_delaywork , test_delaywork_func);
(3)实现回调函数
static void test_delaywork_func(struct work_struct *work)
{
printk("delaywork running\n");
}
(4)调用delaywork
schedule_delayed_work(&test_delaywork , msecs_to_jiffies(200));
(5)取消delaywork
cancel_delayed_work_sync(&test_delaywork );
INIT_DELAYED_WORK() 函数剖析
INIT_DELAYED_WORK()是一个宏,我们给它传递了两个参数.&hub->leds和led_work.对设备驱动熟悉的人不会觉得INIT_DELAYED_WORK()很陌生,其实鸦片战争那会儿就有这个宏了,只不过从2.6.20的内核开始这个宏做了改变,原来这个宏是三个参数,后来改成了两个参数,所以经常在网上看见一些同志抱怨说最近某个模块编译失败了,说什么make的时候遇见这么一个错误:
error: macro “INIT_DELAYED_WORK” passed 3 arguments, but takes just 2
当然更为普遍的看到下面这个错误:
error: macro “INIT_WORK” passed 3 arguments, but takes just 2
于是就让我们来仔细看看INIT_WORK和INIT_DELAYED_WORK.其实前者是后者的一个特例,它们涉及到的就是传说中的工作队列.这两个宏都定义于include/Linux/workqueue.h中:
#define INIT_WORK(_work, _func) /
do { /
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); /
INIT_LIST_HEAD(&(_work)->entry); /
PREPARE_WORK((_work), (_func)); /
} while (0)
#define INIT_DELAYED_WORK(_work, _func) /
do { /
INIT_WORK(&(_work)->work, (_func)); /
init_timer(&(_work)->timer); /
} while (0)
有时候特怀念谭浩强那本书里的那些例子程序,因为那些程序都特简单,不像现在看到的这些,动不动就是些复杂的函数复杂的数据结构复杂的宏,严重挫伤了我这样的有志青年的自信心.就比如眼下这几个宏吧,宏里边还是宏,一个套一个,不是说看不懂,因为要看懂也不难,一层一层展开,只不过确实没必要非得都看懂,现在这样一种朦胧美也许更美,有那功夫把这些都展开我还不如去认认真真学习三个代表呢.总之,关于工作队列,就这么说吧,linux内核实现了一个内核线程,直观一点,ps命令看一下您的进程,
localhost:/usr/src/linux-2.6.22.1/drivers/usb/core # ps -el
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 1 0 0 76 0 - 195 - ? 00:00:02 init
1 S 0 2 1 0 -40 - - 0 migrat ? 00:00:00 migration/0
1 S 0 3 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/0
1 S 0 4 1 0 -40 - - 0 migrat ? 00:00:00 migration/1
1 S 0 5 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/1
1 S 0 6 1 0 -40 - - 0 migrat ? 00:00:00 migration/2
1 S 0 7 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/2
1 S 0 8 1 0 -40 - - 0 migrat ? 00:00:00 migration/3
1 S 0 9 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/3
1 S 0 10 1 0 -40 - - 0 migrat ? 00:00:00 migration/4
1 S 0 11 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/4
1 S 0 12 1 0 -40 - - 0 migrat ? 00:00:00 migration/5
1 S 0 13 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/5
1 S 0 14 1 0 -40 - - 0 migrat ? 00:00:00 migration/6
1 S 0 15 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/6
1 S 0 16 1 0 -40 - - 0 migrat ? 00:00:00 migration/7
1 S 0 17 1 0 94 19 - 0 ksofti ? 00:00:00 ksoftirqd/7
5 S 0 18 1 0 70 -5 - 0 worker ? 00:00:00 events/0
1 S 0 19 1 0 70 -5 - 0 worker ? 00:00:00 events/1
5 S 0 20 1 0 70 -5 - 0 worker ? 00:00:00 events/2
5 S 0 21 1 0 70 -5 - 0 worker ? 00:00:00 events/3
5 S 0 22 1 0 70 -5 - 0 worker ? 00:00:00 events/4
1 S 0 23 1 0 70 -5 - 0 worker ? 00:00:00 events/5
5 S 0 24 1 0 70 -5 - 0 worker ? 00:00:00 events/6
5 S 0 25 1 0 70 -5 - 0 worker ? 00:00:00 events/7
瞅见最后这几行了吗,events/0到events/7,0啊7啊这些都是处理器的编号,每个处理器对应其中的一个线程.要是您的计算机只有一个处理器,那么您只能看到一个这样的线程,events/0,您要是双处理器那您就会看到多出一个events/1的线程.哥们儿这里Dell PowerEdge 2950的机器,8个处理器,所以就是events/0到events/7了.
那么究竟这些events代表什么意思呢?或者说它们具体干嘛用的?这些events被叫做工作者线程,或者说worker threads,更确切的说,这些应该是缺省的工作者线程.而与工作者线程相关的一个概念就是工作队列,或者叫work queue.工作队列的作用就是把工作推后,交由一个内核线程去执行,更直接的说就是如果您写了一个函数,而您现在不想马上执行它,您想在将来某个时刻去执行它,那您用工作队列准没错.您大概会想到中断也是这样,提供一个中断服务函数,在发生中断的时候去执行,没错,和中断相比,工作队列最大的好处就是可以调度可以睡眠,灵活性更好.
就比如这里,如果我们将来某个时刻希望能够调用led_work()这么一个我们自己写的函数,那么我们所要做的就是利用工作队列.如何利用呢?第一步就是使用INIT_WORK()或者INIT_DELAYED_WORK()来初始化这么一个工作,或者叫任务,初始化了之后,将来如果咱们希望调用这个led_work()函数,那么咱们只要用一句schedule_work()或者schedule_delayed_work()就可以了,特别的,咱们这里使用的是INIT_DELAYED_WORK(),那么之后我们就会调用schedule_delayed_work(),这俩是一对.它表示,您希望经过一段延时然后再执行某个函数,所以,咱们今后会见到schedule_delayed_work()这个函数的,而它所需要的参数,一个就是咱们这里的&hub->leds,另一个就是具体自己需要的延时.&hub->leds是什么呢?struct usb_hub中的成员,struct delayed_work leds,专门用于延时工作的,再看struct delayed_work,这个结构体定义于include/linux/workqueue.h:
struct delayed_work {
struct work_struct work;
struct timer_list timer;
};
其实就是一个struct work_struct和一个timer_list,前者是为了往工作队列里加入自己的工作,后者是为了能够实现延时执行,咱们把话说得更明白一点,您看那些events线程,它们对应一个结构体,struct workqueue_struct,也就是说它们维护着一个队列,完了您要是想利用工作队列这么一个机制呢,您可以自己创建一个队列,也可以直接使用events对应的这个队列,对于大多数情况来说,都是选择了events对应的这个队列,也就是说大家都共用这么一个队列,怎么用呢?先初始化,比如调用INIT_DELAYED_WORK(),这么一初始化吧,实际上就是为一个struct work_struct结构体绑定一个函数,就比如咱们这里的两个参数,&hub->leds和led_work()的关系,就最终让hub_leds这个struct work_struct结构体和函数led_work()相绑定了起来,您问怎么绑定的?您瞧,struct work_struct也是定义于include/linux/workqueue.h:
struct work_struct {
atomic_long_t data;
#define WORK_STRUCT_PENDING 0 /* T if work item pending execution */
#define WORK_STRUCT_FLAG_MASK (3UL)
#define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
struct list_head entry;
work_func_t func;
};
瞅见最后这个成员func了吗,初始化的目的就是让func指向led_work(),这就是绑定,所以以后咱们调用schedule_delayed_work()的时候,咱们只要传递struct work_struct的结构体参数即可,不用再每次都把led_work()这个函数名也给传递一次,一旦绑定,人家就知道了,对于led_work(),那她就嫁鸡随鸡,嫁狗随狗,嫁混蛋随混蛋了.您大概还有一个疑问,为什么只要这里初始化好了,到时候调用schedule_delayed_work()就可以了呢?事实上,events这么一个线程吧,它其实和hub的内核线程一样,有事情就处理,没事情就睡眠,也是一个死循环,而schedule_delayed_work()的作用就是唤醒这个线程,确切的说,是先把自己的这个struct work_struct插入workqueue_struct这个队列里,然后唤醒昏睡中的events.然后events就会去处理,您要是有延时,那么它就给您安排延时以后执行,您要是没有延时,或者您设了延时为0,那好,那就赶紧给您执行.咱这里不是讲了两个宏吗,一个INIT_WORK(),一个INIT_DELAYED_WORK(),后者就是专门用于可以有延时的,而前者就是没有延时的,这里咱们调用的是INIT_DELAYED_WORK(),不过您别美,过一会您会看见INIT_WORK()也被使用了,因为咱们hub驱动中还有另一个地方也想利用工作队列这么一个机制,而它不需要延时,所以就使用INIT_WORK()进行初始化,然后在需要调用相关函数的时候调用schedule_work()即可.此乃后话,暂且不表.
基本上这一节咱们就是介绍了Linux内核中工作队列机制提供的接口,两对函数INIT_DELAYED_WORK()对schedule_delayed_work(),INIT_WORK()对schedule_work().
关于工作队列机制,咱们还会用到另外两个函数,它们是cancel_delayed_work(struct delayed_work *work)和flush_scheduled_work().其中cancel_delayed_work()的意思不言自明,对一个延迟执行的工作来说,这个函数的作用是在这个工作还未执行的时候就把它给取消掉.而flush_scheduled_work()的作用,是为了防止有竞争条件的出现,虽说哥们儿也不是很清楚如何防止竞争,可是好歹大二那年学过一门专业课,数字电子线路,尽管没学到什么有用的东西,怎么说也还是记住了两个专业名词,竞争与冒险.您要是对竞争条件不是很明白,那也不要紧,反正基本上每次cancel_delayed_work之后您都得调用flush_scheduled_work()这个函数,特别是对于内核模块,如果一个模块使用了工作队列机制,并且利用了events这个缺省队列,那么在卸载这个模块之前,您必须得调用这个函数,这叫做刷新一个工作队列,也就是说,函数会一直等待,直到队列中所有对象都被执行以后才返回.当然,在等待的过程中,这个函数可以进入睡眠.反正刷新完了之后,这个函数会被唤醒,然后它就返回了.关于这里这个竞争,可以这样理解,events对应的这个队列,人家本来是按部就班的执行,一个一个来,您要是突然把您的模块给卸载了,或者说你把你的那个工作从工作队列里取出来了,那events作为队列管理者,它可能根本就不知道,比如说它先想好了,下午3点执行队列里的第N个成员,可是您突然把第N-1个成员给取走了,那您说这是不是得出错?所以,为了防止您这种唯恐天下不乱的人做出冒天下之大不韪的事情来,提供了一个函数,flush_scheduled_work(),给您调用,以消除所谓的竞争条件,其实说竞争太专业了点,说白了就是防止混乱吧.
应用场景
在中断中处理太多的操作是非常危险的,对中断的及时响应有很大的影响,在linux中我们经常会用到INIT_DELAYED_WORK,来处理中断需要响应的操作。
函数功能
工作队列(work queue)是Linux内核中将操作延期执行的一种机制。INIT_DELAYED_WORK()是一个宏
函数位置
\kernel\include\linux\workqueue.h
函数分析
函数原型
#define INIT_DELAYED_WORK(_work, _func) \
__INIT_DELAYED_WORK(_work, _func, 0)
#define __INIT_DELAYED_WORK(_work, _func, _tflags) \
do { \
INIT_WORK(&(_work)->work, (_func)); \
__setup_timer(&(_work)->timer, delayed_work_timer_fn, \
(unsigned long)(_work), \
(_tflags) | TIMER_IRQSAFE); \
} while (0)
#ifdef CONFIG_LOCKDEP
#define __INIT_WORK(_work, _func, _onstack) \
do { \
static struct lock_class_key __key; \
\
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
lockdep_init_map(&(_work)->lockdep_map, #_work, &__key, 0); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#else
#define __INIT_WORK(_work, _func, _onstack) \
do { \
__init_work((_work), _onstack); \
(_work)->data = (atomic_long_t) WORK_DATA_INIT(); \
INIT_LIST_HEAD(&(_work)->entry); \
(_work)->func = (_func); \
} while (0)
#endif
函数分析
demo
#define READ_VDELAY 3000 //30S
struct xxx_device{
struct delayed_work xxx_delay_work;
};
static void read_work({struct work_struct *work)
{
struct xxx_device *bq = container_of(work,
struct xxx_device, read_work.work);
schedule_delayed_work(&bq->read_work,
READ_VDELAY );
}
probe
{
struct xxx_device *bq;
...
INIT_DELAYED_WORK(&bq->xxx_delay_work, read_work);
...
}
版权声明:本文为CSDN博主「5念since」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_31339221/article/details/105057978
前面讲到工作队列是一种可以将工作推后执行的一种机制,当我们希望某个工作以一个固定的周期去执行的时候可以使用延迟的工作队列来实现,通过分析延迟工作队列函数我们发现,它本质上也是通过注册内核定时器来保证这个延迟的时间的,
因此我们也可以使用内核定时器+工作队列的方式来代替延迟的工作队列。不过这种基于内核定时器实现的固定周期执行任务,因为内核定时器本身的精度关系,最高只能支持ms级别的精准度。如果需要更高更精确的延迟执行任务,延迟的工作队列可能不合适。
接下来简单说明一下延迟工作队列的创建,本质上和工作队列没有什么区别。
1.定义一个延迟的工作队列和任务对象 static struct workqueue_struct *ms_workqueue = NULL; static struct delayed_work ms_queue_work;
2.定义周期性执行的函数 void work_func(struct work_struct *work) { /*周期执行的操作,比如读取某些外设数据等*/ }
3.初始化工作队列 static int __init xxx_init(void) { my_workqueue= create_workqueue("1ms"); /*创建工作队列workqueue_struct,该函数会为cpu创建内核线程*/ INIT_DELAYED_WORK(&ms_queue_work,work_func); /*初始化延迟的工作work_struct,指定工作函数*/ queue_delayed_work(ms_workqueue, &ms_queue_work, 1); /*将工作加入到工作队列中,最终唤醒内核线程(比较常见的使用场景是在中断上半部去唤醒内核线程),第三个参数1是表示以1ms的周期去执行*/ return 0; } 4.注销工作队列 cancel_rearming_delayed_workqueue(ms_workqueue, &ms_queue_work);
destroy_workqueue(ms_workqueue);
一、INIT_DELAYED_WORK中断实例
初始化:
static int stk3311_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
......
INIT_DELAYED_WORK(&work,stk3311_update); //stk3311_update是中断函数
......
queue_delayed_work(wq, &work, 0);//开启
}
开启定时:
queue_delayed_work(wq, &work, 0);//经过一段延时后再执行某一函数
关闭定时:
cancel_delayed_work_sync(&work);
static int stk3311_remove(struct i2c_client *client)
{
struct stk3311_data *data = i2c_get_clientdata(client);
pm_runtime_disable(&client->dev);
pm_runtime_set_suspended(&client->dev);
cancel_delayed_work_sync(&work);//
free_irq(client->irq, data);
input_unregister_device(data->idev);
return 0;
}
中断函数:
static void stk3311_update(void)
{
int ret = 0;
struct stk3311_data *data = i2c_get_clientdata(global_client);
pr_info("stk3311_update!!!algorithm_suspend_flag=%d \n", algorithm_suspend_flag);
stk3311_algorithm_ps_data_value(data);
if (algorithm_suspend_flag == false)
queue_delayed_work(wq, &work, 1 * HZ);
}
二、Linux内核中工作队列机制提供的接口,两对函数:
INIT_DELAYED_WORK()对schedule_delayed_work()
INIT_WORK()对schedule_work().
三、关于工作队列机制,咱们还会用到另外两个函数:
它们是cancel_delayed_work(struct delayed_work *work)和flush_scheduled_work().
其中cancel_delayed_work()的意思不言自明,对一个延迟执行的工作来说,这个函数的作用是在这个工作还未执行的时候就把它给取消掉.
而flush_scheduled_work()的作用,是为了防止有竞争条件的出现。
delayed_work的工作比较简单:
1:初始化:
INIT_DELAYED_WORK(_work, _func);
2:启动:
schedule_delayed_work(struct delayed_work *dwork,
unsigned long delay);
以某TP驱动为例,示意图如下:
直接上代码:
1:关键结构体:
1 | /* * The externally visible workqueue. It relays the issued work items to * the appropriate worker_pool through its pool_workqueues. */ struct workqueue_struct { struct list_head pwqs; /* WR: all pwqs of this wq */ struct list_head list; /* PR: list of all workqueues */ struct mutex mutex; /* protects this wq */ int work_color; /* WQ: current work color */ int flush_color; /* WQ: current flush color */ atomic_t nr_pwqs_to_flush; /* flush in progress */ struct wq_flusher *first_flusher; /* WQ: first flusher */ struct list_head flusher_queue; /* WQ: flush waiters */ struct list_head flusher_overflow; /* WQ: flush overflow list */ struct list_head maydays; /* MD: pwqs requesting rescue */ struct worker *rescuer; /* I: rescue worker */ int nr_drainers; /* WQ: drain in progress */ int saved_max_active; /* WQ: saved pwq max_active */ struct workqueue_attrs *unbound_attrs; /* PW: only for unbound wqs */ struct pool_workqueue *dfl_pwq; /* PW: only for unbound wqs */ #ifdef CONFIG_SYSFS struct wq_device *wq_dev; /* I: for sysfs interface */ #endif #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif char name[WQ_NAME_LEN]; /* I: workqueue name */ /* * Destruction of workqueue_struct is sched-RCU protected to allow * walking the workqueues list without grabbing wq_pool_mutex. * This is used to dump all workqueues from sysrq. */ struct rcu_head rcu; /* hot fields used during command issue, aligned to cacheline */ unsigned int flags ____cacheline_aligned; /* WQ: WQ_* flags */ struct pool_workqueue __percpu *cpu_pwqs; /* I: per-cpu pwqs */ struct pool_workqueue __rcu *numa_pwq_tbl[]; /* PWR: unbound pwqs indexed by node */ }; typedef void (*work_func_t)(struct work_struct *work); struct work_struct { /* atomic_long_t data; */ unsigned long data; struct list_head entry; work_func_t func; #ifdef CONFIG_LOCKDEP /* no define in 4-19 */ struct lockdep_map lockdep_map; #endif }; struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ struct hlist_node entry; unsigned long expires; void (*function)(struct timer_list *); u32 flags; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif }; struct delayed_work { struct work_struct work; struct timer_list timer; /* target workqueue and CPU ->timer uses to queue ->work */ struct workqueue_struct *wq; int cpu; }; |
2: init DELAYED_WORK
1 | #define __INIT_WORK(_work, _func, _onstack) \ |
3:start
1 | /**
|