1、工作队列
schedule_work | 把任务提交到内核默认提供的工作队列[events/0]中执行 |
---|---|
schedule_delayed_work | 把任务提交到内核默认提供的工作队列[events/0]中,(延时一定的时间)执行 |
queue_work | 把任务提交到自定义创建的队列[my workqueue/0]中执行 |
queue_delayed_work | 把任务提交到自定义创建的队列[my workqueue/0]中,(延时一定的时间)执行 |
1.1 queue_work(自定义的工作队列)
delayed_workqueue
的使用步骤如下:
(1)具体的work:
1)定义workqueue要做的delayed工作:
struct delayed_work mdelayed_work;
2)初始化workqueue:INIT_DELAYED_WORK(mworkqueue, mdelayed_work);
(2)存放work 的队列:
1)定义workqueue:
struct workqueue_struct *mworkqueue;
2)创建线程queue并加以名字:mworkqueue = create_singlethread_workqueue("myqueue");
(3)在需要执行的地方调用work:
运行queue:
queue_delayed_work(mworkqueue, mdelayed_work, delay_time);
1.1.1 queue_delayed_work(延时)
(1)设备结构体里面定义工作队列:
struct workqueue_struct *sm_usb_wq; //1.自定义工作队列,存放work
struct delayed_work sm_work; //1.定义延时工作,具体的work,结构体对象私有
(2)初始化工作队列:
INIT_DELAYED_WORK(&mdwc->sm_work, dwc3_otg_sm_work); //2.将工作队列和其function关联
sm_usb_wq = alloc_ordered_workqueue("k_sm_usb", WQ_FREEZABLE); //3.创建新队列和新工作者线程
(3)function函数:
(4)调用工作队列:
1.1.2 queue_delayed_work(实现循环)
如果要实现循环任务,可以在delayed_work_func
中将delayed_workqueue
再次添加到queue
中,即再次调用queue_delayed_work
。
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
//1.定义workqueue要做的delayed工作
struct delayed_work mdwq;
//2.定义workqueue
struct workqueue_struct *mwq;
void delay_work_func(struct work_struct *work)
{
int i;
printk(KERN_INFO "%s:%d\n",__FUNCTION__,__LINE__);
for (i = 0; i < 3; i++) {
printk(KERN_ERR "%s:i=%d\n",__FUNCTION__,i);
msleep(3000);
}
//6.实现循环实行任务
queue_delayed_work(mwq, &mdwq, 3000);
}
static int __init delay_work_init(void)
{
int ret;
int i;
//3. 创建线程queue并加以名字
mwq = create_workqueue("my workqueue");
if (!mwq) {
printk(KERN_ERR "Create workqueue failed!\n");
return 1;
}
printk(KERN_INFO "Create workqueue successful!\n");
//4. 初始化workqueue
INIT_DELAYED_WORK(&mdwq, delay_work_func);
//5. 运行queue
ret = queue_delayed_work(mwq, &mdwq, 3000);
printk(KERN_INFO "first ret=%d!\n", ret);
for (i = 0; i < 3; i++) {
printk(KERN_INFO "%s:ret=%d,i=%d\n",__FUNCTION__,ret, i);
msleep(1000);
}
ret = queue_delayed_work(mwq, &mdwq, 0);
printk(KERN_INFO "second ret=%d!\n", ret);
return 0;
}
static void __exit delay_work_exit(void)
{
int ret;
ret = cancel_delayed_work(&mdwq);
flush_workqueue(mwq);
destroy_workqueue(mwq);
printk(KERN_INFO "Exit! ret=%d\n", ret);
}
module_init(delay_work_init);
module_exit(delay_work_exit);
MODULE_LICENSE("GPL");
运行结果:
kernel: Create workqueue successful!
kernel: first ret=1!
kernel: delay_work_init:ret=1,i=0
kernel: delay_work_init:ret=1,i=1
kernel: delay_work_init:ret=1,i=2
kernel: second ret=0!
kernel: Exit! ret=1
从例子可以看出:
(1)当工作队列还在执行该任务,调用queue_delayed_work()
返回1,否则返回0。
(2)主线程mwq将任务添加到工作队列后,使得工作队列在延迟delay后执行函数delay_work_func()
,而mwq线程继续执行。
1.2 schedule_work(内核工作队列)
内核自带有系统的workqueue(system_wq),使用schedule_work 将work 添加到内核工作队列去执行。
static inline bool schedule_work(struct work_struct *work)
{
return queue_work(system_wq, work);
}
使用步骤:
- 定义工作:
struct work_struct xxx_work;
- 初始化工作,定义执行函数:
INIT_WORK(&xxx_wq, xxx_do_work);
- 调度工作:
schedule_work(&xxx_wq);
例子:
//1.定义工作
struct work_struct xxx_wq;
//2.定义工作执行函数,处理中断底半部
void xxx_do_work (struct work_struct *work)
{
...
}
//中断顶半部
irqreturn_t xxx_interrupt (int irq, void *dev_id)
{
...
//4.调度工作
schedule_work(&xxx_wq);
...
return IRQ_HANDLED;
}
//模块加载函数
int xxx_init (void)
{
...
//申请中断
result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL);
...
//3.初始化工作,将工作和执行函数关联
INIT_WORK(&xxx_wq, xxx_do_work);
...
}
//模块卸载函数
void xxx_exit(void)
{
...
//释放中断
free_irq(xxx_irq, xxx_interrupt);
...
}
2、task
2.1 workqueue and task
工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。
(1)如果推后执行的任务需要睡眠,那么就选择workqueue
;如果推后执行的任务不需要睡眠,那么就选择tasklet
。
(2)如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。
(3)工作队列位于进程上下文,工作队列里允许延时,睡眠操作;而软中断、tasklet位于中断上下文,不允许睡眠和延时操作。
2.2 task 使用
struct tasklet_struct task_t ; //定义一个task
struct workqueue_struct *mywork ; //自定义一个workqueue
struct work_struct work; //定义一个work
static void task_fuc(unsigned long data) //task执行函数
{
if(in_interrupt()){
printk("%s in interrupt handle!\n",__FUNCTION__);
}
}
static void mywork_fuc(struct work_struct *work) //work执行函数
{
if(in_interrupt()){
printk("%s in interrupt handle!\n",__FUNCTION__);
}
msleep(2);
printk("%s in process handle!\n",__FUNCTION__);
}
static irqreturn_t irq_fuction(int irq, void *dev_id) //中断执行函数
{
tasklet_schedule(&task_t); //调度task
schedule_work(&work); //调度work
if(in_interrupt()){
printk("%s in interrupt handle!\n",__FUNCTION__);
}
printk("key_irq:%d\n",irq);
return IRQ_HANDLED ;
}
static int __init tiny4412_Key_irq_test_init(void)
{
int err = 0 ;
int irq_num1 ;
int data_t = 100 ;
mywork = create_workqueue("my work"); //创建新队列和新工作者线程
INIT_WORK(&work,mywork_fuc); //初始化工作和执行函数
queue_work(mywork,&work); //调度指定队列
tasklet_init(&task_t,task_fuc,data_t); //初始化task
printk("irq_key init\n");
irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2)); //gpio初始化irq
err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,
"tiny4412_key1",(void *)"key1"); //申请irq
if(err != 0){
free_irq(irq_num1,(void *)"key1");
return -1 ;
}
return 0 ;
}
static void __exit tiny4412_Key_irq_test_exit(void)
{
int irq_num1 ;
printk("irq_key exit\n");
irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));
destroy_workqueue(mywork); //销毁一条工作队列
free_irq(irq_num1,(void *)"key1");
}
module_init(tiny4412_Key_irq_test_init);
module_exit(tiny4412_Key_irq_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("YYX");
MODULE_DESCRIPTION("Exynos4 KEY Driver");
打印log如下:
可以看到,当我们按下按键的时候,进入外部中断服务函数,此时task_fuc先被调用,然后调用到mywork_fuc,并打印了mywork_fuc里面的信息,从这里验证了,工作队列是位于进程上下文,而不是中断上下文。