• linux驱动开发:中断和时间管理


    目录

    中断

    1.基本概念

    2.共享中断

    3.驱动中的中断处理

    4.linux中断处理程序架构(top half + bottom half)

    5.实现底半部的机制

    5.1 软中断

    5.2 tasklet

    5.3工作队列

    时钟

    1.内核延时

    2.内核定时器

    3.延时队列


    中断

    1.基本概念

            所谓中断是指cpu在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停执行当前的程序,转而去处理突发事件,处理完毕后cpu再返回到原来的位置继续执行。根据中断的来源,中断可以分为内部中断与外部中断。根据中断是否可以屏蔽,中断可以分为可屏蔽中断与不可屏蔽中断。根据跳转方法不同,可以分为向量中断与非向量中断。向量中断,不同的中断号对应于不同的中断入口地址,非向量中断多个中断共享一个中断入口地址,需要在中断服务程序中进行区分。

    2.共享中断

    由于中断控制器的管脚有限,所以在某些体系结构上有两个或两个以上的设备被接到了同一根中断线上,这样两个设备中的任何一个 设备产生了中断,都会触发中断,但linux内核不能判断新产生的这个中断究竟属于哪个设备,于是将共用一根中断线的中断处理函数通过不同的struct irqaction结构体包装起来,然后用链表链接起来,内核得到IRQ号后,就遍历链表上的每一个struct irqaction对象,然后调用其handler成员所指向的中断处理函数。在中断处理函数中,驱动开发者应该判断该中断是否是由自己管理的设备产生的,如果是则进行相应的中断处理,如果不是直接返回。

    struct irqaction结构类型定义:

    struct irqaction{

            irq_handler_t      handler;  //指向驱动开发者编写的中断处理函数的指针

            void                      *dev_id;//区分共享中断中的不同设备的ID

            void __percpu    *percpu_dev_id;

            struct irqaction    *next;//将共享同一IRQ号的struct irqaction对象链接在一起的指针

            irq_handler_t       *thread;

            struct task_struct   *thread;

            unsigned int           irq;//irq号

            unsigned int           flags;//以IRQF_开头的一组标志

            unsigned long       thread_flags;

            unsigned long      thread_mask;

            const char             *name;

            struct pro_dir_entry     **dir;

    }___cacheline_internodealigned_in_smp;

    3.驱动中的中断处理

    不难发现,要在驱动中支持中断,则需要构造一个struct irqaction的结构对象,并根据irq号加入到对应的链表中。

    注册一个中断处理函数的函数原型

    int request_irq(

                    unsigned int irq,//设备上所用中断的IRQ号,内核中的,决定构造的对象被插入哪个链表
                    irqreturn_t (*handler)(int, void *, struct pt_regs *),//指向中断处理函数的指针,初始化对象中的handler成员
                    unsigned long flags, //与中断相关的标志,用来初始化对象中的flags成员,一般有上升沿触发,下降沿触发,高低电平触发等
                    const char *dev_name,//该中断在/proc中的名字,用于初始化struct irqaction对象中的name成员
                    void *dev_id);//区别共享中断中的不同设备所对应的struct irqaction对象,用于初始化dev_id成员

    返回值:函数成功返回0,失败返回负值。

    释放中断函数原型
    void free_irq(unsigned int irq, void *dev_id);

    dev_id:共享中断必须要传递一个非NULL的实参,和request_irq中的dev_id保持一致

    中断处理函数应该快速完成,不能消耗太长时间,因为如果执行时间过长,那么其他的中断将被挂起,从而对其他中断造成严重的影响,但中断的处理往往又是复杂的,那么这就要引出我们接下来将提到的中断上半部和下半部!另外,在中断处理函数中一定不能调用调度器,即不能调用可能造成进程切换的函数(因为中断处理程序一旦被切换,将不能再次被调度)下面列举一些可能导致进程切换的函数:

    kfifo_to_user    kfifo_from_user   copy_from_user  copy_to_user  wait_event_xxx

    4.linux中断处理程序架构(top half + bottom half)

            设备的中断会打断内核中进程的正常调度和运行,系统为了效率必然要求中断服务程序进可能短小精悍。但是实际情况是中断需要完成的事情并不简单,需要消耗大量的时间。
             linux内核的中断处理机制,为了解决这一问题,将中断程序设计成顶半部+底半部。顶半部完成较紧急的事情,剩下的事情挂到底半部中去执行,底半部中完成是事情相对复杂,也更加耗时,中断处理的重心也就落到了底半部中。

    5.实现底半部的机制

    5.1 软中断

    软中断是中断下半部机制的一种

    struct softirq_action     //表述软中断的结构
    open_softirq();         //关联
    raise_softirq();         //触发软中断

    虽然软中断可以实现中断下半部,但软中断基本是内核开发者预定义好的,通常用在对性能要求比较高的场合,需要一些编程技巧,不太适合驱动开发者。

    5.2 tasklet

    虽然软中断通常由内核开发者设计,但内核开发者保留了一个软中断给驱动开发者,就是TASKLET_SOFTIRQ,相应的函数原型:

    void tasklet_func(unsigned long);

     
    DECLARE_TASKLET(my_tasklet, tasklet_func, 0);          //定义my_tasklet,并关联处理函数


    tasklet_schedule(&my_tasklet);                         //调度,顶半部结尾处执行(对应的下半部保证至少会执行一次)
     

    看示例代码:

    1. struct resource *irqres;
    2. int my_probe(struct platform_device *pdev);
    3. int my_remove(struct platform_device *pdev);
    4. void my_task(unsigned long args);
    5. DECLARE_TASKLET(mytasklet, my_task, 0); //定义了mytasklet,并关联了处理函数
    6. #if 0
    7. mykey3@1133{
    8. compatible ="fs,mykey3";
    9. interrupt-parent = <&gpx1>;
    10. interrupts = <2 2>;
    11. };
    12. #endif
    13. //定义platform_driver对象
    14. struct of_device_id of_matches[]={
    15. {.compatible="fs,mykey3"}, //修改匹配规则,从设备树
    16. {}, //获取key3相关硬件信息
    17. };
    18. static struct platform_driver mydriver ={
    19. .probe = my_probe,
    20. .remove = my_remove,
    21. .driver = {
    22. .name = "key",
    23. .of_match_table = of_matches, //通过设备树匹配
    24. },
    25. };
    26. irqreturn_t key_irq_handler(int irqnum, void *args)
    27. {
    28. printk("top half \n");
    29. tasklet_schedule(&mytasklet); //调度mytasklet
    30. return IRQ_HANDLED;
    31. }
    32. void my_task(unsigned long args)
    33. {
    34. printk("bottom half\n");
    35. return ;
    36. }
    37. int my_probe(struct platform_device *pdev)
    38. {
    39. int ret;
    40. //通过设备树获取 硬件资源
    41. printk("match\n");
    42. irqres = platform_get_resource(pdev,IORESOURCE_IRQ,0);
    43. if(irqres==NULL){
    44. return -1;
    45. }
    46. printk("irqnum:%#x\n",irqres->start);
    47. //硬件操作
    48. ret = request_irq(irqres->start, key_irq_handler, irqres->flags,"key3",NULL);
    49. if(ret!=0){
    50. return ret;
    51. }
    52. printk("request irq\n");
    53. return 0;
    54. }
    55. int my_remove(struct platform_device *pdev)
    56. {
    57. printk("driver remove\n");
    58. free_irq(irqres->start,NULL);
    59. return 0;
    60. }
    61. static int mod_init(void)
    62. {
    63. return platform_driver_register(&mydriver); //平台驱动注册
    64. }
    65. static void mod_exit(void)
    66. {
    67. platform_driver_unregister(&mydriver); //平台驱动注销
    68. }
    69. module_init(mod_init);
    70. module_exit(mod_exit);
    71. MODULE_LICENSE("GPL");

    5.3工作队列

    前面讲解额下半部机制都有一个限制,软中断和tasklet中不能睡眠,也就是说在中断上下文中执行不能直接或间接地调用调度器,所以为了解决这个问题,内核提出了工作队列这种下半部机制,巩工作队列可以休眠。内核在启动的时候创建一个或多个内核工作线程,当工作线程取出工作队列的每一个工作,然后执行,当队列没有工作时,工作线程休眠。当驱动想要延迟执行某一个工作时,构造一个工作队列节点对象,然后加入到相应的工作队列,并唤醒工作线程,工作线程取出队列上的节点来完成工作,所有工作完成后又休眠。

    工作队列节点的结构类型定义:

    struct work_struct{

            atomic_long_t  data;  //传递给工作函数的参数,可以是一个整型数,但一般为指针

            strruct list_head entry;//构成工作队列的链表节点对象

            work_func_t    func;//工作函数

    ..........................................

    };

    常用的与工作队列相关的函数和宏:

    struct work_struct xxx_wq;

    void xxx_do_work(struct work_struct *work);//工作函数

    DECLARE_WORK(n,f)//静态定义一个工作队列节点,n为节点名字,f为工作函数

    DECLARE_DELAYED_WORK(n,f)//静态定义一个延迟的工作队列节点,n为节点名字,f为工作函数

    INIT_WORK(&xxx_wq, xxx_do_work);    //动态分配的工作队列节点的初始化

    schedule_work(&xxx_wq);                                //调度将工作队列节点加入到内核定义的全局工作队列中,顶半部结尾处执行

    schedule_delayed_work(&xxx_wq,unsigned long delay);                                //调度在delay指定的时间后将工作队列节点延迟加入到内核定义的全局工作队列中

    工作队列特性:

    1.工作队列的工作函数允许在进程上下文,可以调度调度器

    2.如果上一个工作还没有完成,又重新调度下一个工作,那么新的工作将不会被调度。

    时钟

    在硬件的操作中经常会用到延时,比如要保持芯片的复位时间持续多久,芯片复位后至少要延迟多长时间才能去访问芯片,等。为此内核提供了一组延时操作函数。

    1.内核延时

    //1.短延时
    void ndelay(unsigned long nsecs);  //纳秒
    void udelay(unsigned long usecs);  //微秒
    #define mdelay(n)   udelay((n) * 1000)  ;  //毫秒
    //本质都是忙等,是靠白白消耗CPU的时间来获得延时的,一般不推荐使用

    //2.睡着延时
    void msleep(unsigned int msecs);                            //毫秒
    unsigned long msleep_interruptible(unsigned int msecs);  //毫秒,休眠可以被信号打断
    static inline void ssleep(unsigned int seconds);          //秒

    2.内核定时器

    有时候需要在设定的时间到期后自动执行一个操作,这就是定时。经典的定时器都是基于一个硬件定时器的,该定时器周期性产生中断,产生中断的次数可以进行配置,内核源码中以HZ这个宏来代表这个配置值,也就是说这个硬件定时器每秒钟就会产生HZ次中断。该定时器自开机以来产生的中断次数会被记录在jiffies全局变量中。

    struct timer_list {
        struct list_head entry;                //双向链表节点的对象,用于构成双向链表
        unsigned long expires;             //定时值 (是当前jiffies基础上添加的一个延时)
        struct tvec_base *base;
        void (*function)(unsigned long);   //定时器处理函数
        unsigned long data;                //传参,通常为一个指针
        int slack;


    #ifdef CONFIG_TIMER_STATS
        int start_pid;
        void *start_site;
        char start_comm[16];
    #endif


    #ifdef CONFIG_LOCKDEP
        struct lockdep_map lockdep_map;
    #endif
    }

    低分辨率定时器的相关函数如下:

    struct timer_list my_timer;   //定义定时器
    void init_timer(struct timer_list *timer);  //初始化
    void add_timer(struct timer_list *timer);   //注册并启动,将定时器添加到内核中的定时器链表中
    int del_timer(struct timer_list * timer);  //删除,从内核链表中删除该定时器
    int del_timer_sync(struct timer_list *timer); //等待使用完后删除
    int mod_timer(struct timer_list *timer, unsigned long expires); //修改

    看一段代码:

    1. struct resource *irqres;
    2. int my_probe(struct platform_device *pdev);
    3. int my_remove(struct platform_device *pdev);
    4. struct timer_list mytimer; //定义定时器对象
    5. void timer_out(unsigned long args); //定时器处理函数
    6. //定义platform_driver对象
    7. struct of_device_id of_matches[]={
    8. {.compatible="fs,mykey3"}, //修改匹配规则,从设备树
    9. {}, //获取key3相关硬件信息
    10. };
    11. static struct platform_driver mydriver ={
    12. .probe = my_probe,
    13. .remove = my_remove,
    14. .driver = {
    15. .name = "key",
    16. .of_match_table = of_matches, //通过设备树匹配
    17. },
    18. };
    19. void timer_out(unsigned long args)
    20. {
    21. printk("timer out\n");
    22. mod_timer(&mytimer,jiffies+2*HZ); //周期执行,定时2s
    23. }
    24. int my_probe(struct platform_device *pdev)
    25. {
    26. printk("match\n");
    27. init_timer(&mytimer); //初始化定时器
    28. mytimer.function = timer_out;
    29. mytimer.expires = jiffies + 2*HZ ; //延时2s
    30. add_timer(&mytimer); //注册到内核,并启动
    31. return 0;
    32. }
    33. int my_remove(struct platform_device *pdev)
    34. {
    35. printk("driver remove\n");
    36. del_timer_sync(&mytimer);
    37. //删除定时器
    38. return 0;
    39. }
    40. static int mod_init(void)
    41. {
    42. return platform_driver_register(&mydriver); //平台驱动注册
    43. }
    44. static void mod_exit(void)
    45. {
    46. platform_driver_unregister(&mydriver); //平台驱动注销
    47. }
    48. module_init(mod_init);
    49. module_exit(mod_exit);
    50. MODULE_LICENSE("GPL");

    3.延时队列

    对于周期性的任务,linux内核还提供一种封装好延时队列(delayed_work),本质是利用工作队列+定时器实现。

    struct delayed_work {
         struct work_struct work;
         struct timer_list timer; 
         struct workqueue_struct *wq;
         int cpu;
    };

    struct delayed_work delaytask;                   //定义
    void my_delay_work(struct work_struct *work);       //设计处理函数
    INIT_DELAYED_WORK(&delaytask,my_delay_work);      //关联

    static inline bool schedule_delayed_work(struct delayed_work *dwork,                      unsigned long delay); //定时执行

    //取消delayed_work
    bool cancel_delayed_work(struct delayed_work *dwork);
    bool cancel_delayed_work_sync(struct delayed_work *dwork);

    1. struct resource *irqres;
    2. int my_probe(struct platform_device *pdev);
    3. int my_remove(struct platform_device *pdev);
    4. struct delayed_work delaytask;//定义延时队列对象
    5. void my_delay_work(struct work_struct *work);//延时工作函数
    6. //定义platform_driver对象
    7. struct of_device_id of_matches[]={
    8. {.compatible="fs,mykey3"}, //修改匹配规则,从设备树
    9. {}, //获取key3相关硬件信息
    10. };
    11. static struct platform_driver mydriver ={
    12. .probe = my_probe,
    13. .remove = my_remove,
    14. .driver = {
    15. .name = "key",
    16. .of_match_table = of_matches, //通过设备树匹配
    17. },
    18. };
    19. void my_delay_work(struct work_struct *work)
    20. {
    21. printk("my_delay_work\n");
    22. schedule_delayed_work(&delaytask, 2*HZ);
    23. }
    24. int my_probe(struct platform_device *pdev)
    25. {
    26. printk("match\n");
    27. INIT_DELAYED_WORK(&delaytask,my_delay_work);//关联
    28. schedule_delayed_work(&delaytask, 2*HZ); //定时2s执行
    29. return 0;
    30. }
    31. int my_remove(struct platform_device *pdev)
    32. {
    33. printk("driver remove\n");
    34. cancel_delayed_work_sync(&delaytask);
    35. return 0;
    36. }
    37. static int mod_init(void)
    38. {
    39. return platform_driver_register(&mydriver); //平台驱动注册
    40. }
    41. static void mod_exit(void)
    42. {
    43. platform_driver_unregister(&mydriver); //平台驱动注销
    44. }
    45. module_init(mod_init);
    46. module_exit(mod_exit);
    47. MODULE_LICENSE("GPL");

  • 相关阅读:
    五年谷歌ML Infra生涯,我学到最重要的3个教训
    第14章 结构和其他数据形式
    C和指针 第3章 语义“陷阱” 3.5 空指针并非字符串
    iframe实现pdf预览,并使用pdf.js修改内嵌标题,解决乱码问题
    《硬件历险》之Mac抢救出现问题的时间机器硬盘中的数据
    MyBatis 笔记
    AI&机器学习笔试题
    Netty(5)第一行代码Hello World
    算法--差分
    Mysql 高阶语句
  • 原文地址:https://blog.csdn.net/m0_70983574/article/details/126655542