单片机
返回首页

手把手教你写Linux设备驱动---中断(三)--workqueue实现(基于友善之臂4412开发板)

2024-11-08 来源:cnblogs

上节,我们讲到如何来实现tasklet小任务机制


http://blog.csdn.NET/morixinguan/article/details/69666935


这节,我们来实现一下中断下半部的工作队列:


在写这个demo之前,我们要了解一下工作队列的相关数据结构还有API。


需要包含的头文件:


#include


基本的数据结构:


//工作队列结构  

struct work_struct {  

    atomic_long_t data;  

    //链表处理  

    struct list_head entry;  

    //工作处理函数  

    work_func_t func;  

#ifdef CONFIG_LOCKDEP  

    struct lockdep_map lockdep_map;  

#endif  

};  

当然,如果需要等待一定时间后再执行工作队列,可以用下面这个结构体申请一个内核定时器:


//指定时间让工作队列执行  

struct delayed_work {  

    //初始化  

    struct work_struct work;  

    //内核定时器  

    struct timer_list timer;  

};  

一般,不要轻易的去使用工作队列,因为每当创建一条工作队列,内核就会为这条工作队列创建一条内核线程。


工作队列位于进程上下文,与软中断,tasklet有所区别,工作队列里允许延时,睡眠操作,而软中断,tasklet位于中断上下文,不允许睡眠,延时操作。


参考我转发的这位博主写的工作队列和tasklet的区别:


http://blog.csdn.net/morixinguan/article/details/69666642


工作队列(work queue)是另外一种将工作推后执行的形式,它和前面讨论的tasklet有所不同。工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的就是工作队列允许被重新调度甚至是睡眠。

      那么,什么情况下使用工作队列,什么情况下使用tasklet。如果推后执行的任务需要睡眠,那么就选择工作队列;如果推后执行的任务不需要睡眠,那么就选择tasklet。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的I/O操作时,它都会非常有用。如果不需要用一个内核线程来推后执行工作,那么就考虑使用tasklet。


接下来我们看看需要使用到哪些API:


 


创建一个队列就会有一个内核线程,一般不要轻易创建队列  

位于进程上下文--->可以睡眠  

定义:  

    struct work_struct work;  

  

初始化:  

    INIT_WORK(struct work_struct *work, void (*func)(struct work_struct *work));  

  

定义并初始化:  

    DECLARE_WORK(name, void (*func)(struct work_struct *work));  

  

===========================================================  

  

调度:  

    int schedule_work(struct work_struct *work);  

    返回1成功, 0已经添加在队列上  

  

延迟调度:  

    int schedule_delayed_work(struct work_struct *work, unsigned long delay);  

  

===========================================================  

  

创建新队列和新工作者线程:  

    struct workqueue_struct *create_workqueue(const char *name);  

  

调度指定队列:  

    int queue_work(struct workqueue_struct *wq, struct work_struct *work);  

  

延迟调度指定队列:  

    int queue_delayed_work(struct workqueue_struct *wq,   

            struct work_struct *work, unsigned long delay);  

销毁队列:  

    void destroy_workqueue(struct workqueue_struct *wq);  

接下来,我们来实现这个demo:


#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   

#include   /*timer*/  

#include   /*jiffies*/  

#include   

#include   

#include   

struct tasklet_struct task_t ;   

struct workqueue_struct *mywork ;  

//定义一个工作队列结构体  

struct work_struct work;  

static void task_fuc(unsigned long data)  

{  

    if(in_interrupt()){  

             printk('%s in interrupt handle!n',__FUNCTION__);  

        }  

}  

//工作队列处理函数  

static void mywork_fuc(struct work_struct *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);  

    //调度工作  

    schedule_work(&work);  

    if(in_interrupt()){  

         printk('%s in interrupt handle!n',__FUNCTION__);  

    }  

    printk('key_irq:%dn',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);  

    printk('irq_key initn');  

    irq_num1 = gpio_to_irq(EXYNOS4_GPX3(2));  

    err = request_irq(irq_num1,irq_fuction,IRQF_TRIGGER_FALLING,'tiny4412_key1',(void *)'key1');  

    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 exitn');  

    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');  

将程序编译完,将zImage下到板子上:

我们可以看到,当我们按下按键的时候,进入外部中断服务函数,此时task_fuc先被调用,然后调用到mywork_fuc,并打印了mywork_fuc里面的信息,从这里我们用程序验证了,工作队列是位于进程上下文,而不是中断上下文,和tasklet是有所区别的。


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

最新器件
精选电路图
  • 基于IC555的可变PWM振荡器电路

  • 优化电路板布局的简单方法

  • 如何使用LED驱动器LM3915制作振动计

  • 分享一个电网倾角计电路

  • 电谐波图形均衡器示意图

  • 一种构建12V和230V双直流电源的简单方法

    相关电子头条文章