查看: 2016|回复: 0

[软件] 【转】深度解析linux设备驱动中断程序

[复制链接]
  • TA的每日心情
    慵懒
    2013-1-22 10:13
  • 签到天数: 3 天

    连续签到: 1 天

    [LV.2]偶尔看看I

    发表于 2017-8-9 10:34:30 | 显示全部楼层 |阅读模式
    分享到:
    1.ARM裸机中断程序流程分析
    1.1.中断统一入口
    1.2.事先注册中断处理程序
    1.3.根据中断源的编号处理程序
    2.linux系统中断处理流程分析2.1中断流程概述
    Linux统irq.svc中断入口获得中断源的编号,根据中断号找到irq_desc结构,在irq_desc结构中找到action结构,执行用户注册中断处理函数
    驱动程序支持中断应该做什么?
    驱动程序实现中断处理程序,注册到中断号所对应irq_desc中
    linux设备驱动程序中包含中断处理程序中断注册,中断处理函数实现,注销处理
    2.2linux系统中断处理实现
    2.2.1中断注册使用request_irq函数用于中断注册
    int request_irq(unsigned int irq,void (handler)(int, void, structpt_regs *),unsigned long flags,const char *devname,void *dev_id)
    返回0表示成功,或者返回一个错误码
    参数:
    unsigned int irq
    中断号。
    void (handler)(int,void )
    中断处理函数。
    unsigned long flags
    与中断管理有关的各种选项。
    const char * devname
    设备名
    void *dev_id
    共享中断时使用
    在flags参数中,可以选择一些与中断管理有关的选项,如:
    IRQF_DISABLED(SA_INTERRUPT)
    如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程
    序。
    IRQF_SHARED(SA_SHIRQ)
    该位表明该中断号是多个设备共享的。
    也可选择为中断触发标志:IRQF_TRIGGER_FALLING(下降沿)
    快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)
    在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
    2.2.2中断处理程序
    中断处理程序的特别之处是在中断上下文中运行的,它的行为受到某些限制:1.不能使用可能引起阻塞的函数,2.不能使用肯呢过引起调度的函数
    中断处理程序流程
    1.检查设备是否产生了中断
    2.清除中断产生标志
    3.相应的硬件操作
    2.2.3.注销中断
    void free_irq(unsigned int irq,void *dev_id)
    3.按键中断处理框架代码实现
    1. #include<linux/module.h>
    2. #include<linux/init.h>
    3. #include<linux/miscdevice.h>
    4. #include<linux/fs.h>
    5. #include<linux/interrupt.h>
    6. #include<linux/irq.h>
    7. #include<linux/io.h>
    8. #include<linux/slab.h>
    9. #include<linux/vmalloc.h>
    10. MODULE_LICENSE("GPL");

    11. #define GPX1CON 0x11000C20
    12. //home按键的物理基地址
    13. unsigned int *gpx1con;
    14. irqreturn_t key_int(int irq,void *dev_id)
    15. {
    16.         //检测是否发生了按键中断



    17.         //清除按键中断


    18.         //打印键值
    19.           printk("press the key\n");
    20.          return 0;
    21. }
    22. void keyinit()
    23. {
    24.     gpx1con = ioremap(GPX1CON,4);//物理地址映射为虚拟地址
    25.     writel(readl(gpx1con)&~(0xf<<4)|(0xf<<4),gpx1con);//将GPIO功能设置为输入
    26. }
    27. int key_open(struct inode *node,struct file *filp)
    28. {
    29.     return 0;   
    30. }
    31. struct file_operations key_fops =
    32. {
    33.     .open = key_open,   
    34. };
    35. struct miscdevice key_miscdev = {

    36.     .minor = 200,
    37.     .name = "mykey",
    38.     .fops = &key_fops,
    39. };
    40. static int key_init()
    41. {

    42.     misc_register(&key_miscdev);//注册杂项设备
    43.     keyinit();按键的硬件初始化
    44.     request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);//注册中断
    45.     return 0;
    46. }

    47. static void key_exit()
    48. {
    49.     misc_deregister(&key_miscdev);//注销杂项设备
    50.     free_irq(IRQ_EINT(9),0);//释放中断
    51. }

    52. module_init(key_init);
    53. module_exit(key_exit);
    复制代码
    4.中断分层4.1.中断嵌套4.2.中断分层方式4.3.使用工作队列实现分层4.1中断嵌套
    linux如何处理中断嵌套?
    一种类型的中断发生的时候,又产生了其他的中断,其他的中断可以是同类型的,也可以是不同类型的。
    不同的系统处理方式不同
    慢速中断:在进行中断处理的时候,中断的总开关不关闭,允许其他类型中断产生
    情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统暂停串口中断处理程序,转去执行网卡处理程序,执行完毕,又去执行串口中断处理程序。
    情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。
    快速中断:在进行中断处理的时候,中断的总开关关闭,不允许其他类型中断产生
    情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统继续串口中断处理程序,忽略网卡处理程序。
    情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。
    4.2中断分层:
    4.2.1linux如何处理中断丢失的问题?
    方法:将中断处理程序时间减短,减小中断丢失的概率
    如何将中断处理程序的时间尽量减短?
    中断处理程序一般做两部分工作,一部分是和硬件相关的,另外一类部分和硬件不密切相关。于是把中断处理程序分成两部分,上半部分做与硬件相关的工作,因为硬件相关工作必须在中断处理程序上下文中进行。下半部分与硬件不相关的抛给内核处理。
    上半部:当中断发生时,它进行相应地硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。
    下半部:在系统空闲的时候对上半部“登记”的中断进行后续处理。
    4.2.2中断分层方式
    中断分层的方式有三种
    1软中断,2tasklet,3工作队列现在最常用的方式为工作队列
    工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列。
    创建一个工作队列,3核的CPU,每个CPU上都会挂再一个链表,链表上连的就是工作,将下半部的工作添加到链表中买,内核为每一个链表创建一个线程,当内核空闲时,就处理下半部工作。
    4.3工作队列的使用
    Linux内核使用workqueue_struct来描述一个工作队列
    struct workqueue_struct {
    struct cpu_workqueue_struct *cpu_wq;
    struct list_head list;
    const char name; /*workqueue name/
    int singlethread;
    int freezeable; /* Freeze threads during suspend */
    int rt;
    };
    Linux内核使用struct work_struct来描述一个工作项:
    struct work_struct {
    atomic_long_t data;
    struct list_head entry;
    work_func_t func;
    };
    typedef void (*work_func_t)(struct work_struct *work);
    工作队列使用流程:
    step1. 创建工作队列
    create_workqueue
    step2. 创建工作
    INIT_WORK
    step3. 提交工作
    queue_work
    4.4工作队列代码实现
    1. #include<linux/init.h>
    2. #include<linux/module.h>
    3. #include<linux/slab.h>
    4. MODULE_LICENSE("GPL");
    5. struct workqueue_struct *my_wq;
    6. struct work_struct *work1;
    7. struct work_struct *work2;
    8. static void work1_fun(struct work_struct *work)
    9. {

    10.     printk("this is work1\n");
    11. }
    12. static void work2_fun(struct work_struct *work)
    13. {
    14.     printk("this is work2\n");

    15. }
    16. MODULE_LICENSE("GPL");

    17. int init_queue(void)
    18. {
    19.     //创建工作队列
    20.     //my_wq = create_workqueue("mywq");

    21.     //创建工作
    22.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    23.     INIT_WORK(work1,work1_fun);
    24.     //挂载工作
    25.     //queue_work(my_wq,work1);
    26.     schedule_work(work1);
    27.     //创建工作
    28.     work2 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    29.     INIT_WORK(work2,work2_fun);
    30.     //挂载工作
    31.     //queue_work(my_wq,work2);
    32.     schedule_work(work2);
    33. }

    34. void clean_queue(void)
    35. {

    36. }
    37. module_init(init_queue);
    38. module_exit(clean_queue);
    复制代码
    4.5内核定时器
    linux内核使用struct timer_list来描述定时器:
    1. struct timer_list {
    2. /*
    3. * All fields that change during normal runtime grouped to the
    4. * same cacheline
    5. */
    6. struct list_head entry;
    7. unsigned long expires;
    8. struct tvec_base *base;

    9. void (*function)(unsigned long);
    10. unsigned long data;
    11. };
    复制代码
    定时器使用流程
    1.定义定时器变量struct timer_list
    2初始化定时器
    2.1init_timer的初始化
    2.2设置超时函数
    3.add_timer注册定时器
    4.mod_timer启动定时器
    按键驱动的分层实现
    1. #include<linux/module.h>
    2. #include<linux/init.h>
    3. #include<linux/miscdevice.h>
    4. #include<linux/fs.h>
    5. #include<linux/interrupt.h>
    6. #include<linux/io.h>
    7. #include<linux/irq.h>
    8. #include<linux/slab.h>
    9. #include<linux/vmalloc.h>
    10. #include<linux/uaccess.h>
    11. MODULE_LICENSE("GPL");
    12. #define GPX1CON 0x11000C20
    13. #define GPX1DAT 0x11000C24
    14. unsigned int *gpx1con;
    15. unsigned int *gpx1dat;
    16. unsigned int key_num;

    17. struct work_struct *work1;

    18. struct work_struct *work1;
    19. struct timer_list key_timer;//定义定时器变量struct timer_list
    20. void work1_fun(struct work_struct *work)
    21. {
    22.     mod_timer(&key_timer,jiffies+HZ/10);//mod_timer启动定时器
    23.     //  printk("press the key\n");
    24. }
    25. void key_timer_func(unsigned long data)//超时函数
    26. {   unsigned int key_val;
    27.     key_val = readl(gpx1dat)&(0x1<<1);
    28.     if(key_val==0)
    29.     {
    30.         key_num = 1;
    31.     }
    32.         //printk("\nhome key down\n");

    33.     key_val = readl(gpx1dat)&(0x1<<2);
    34.     if(key_val==0)
    35.     {
    36.         key_num = 2;
    37.     }      
    38. //printk("\n back key down\n");

    39. }
    40. irqreturn_t key_int(int irq,void *dev_id)
    41. {
    42.         //检测是否发生了按键中断
    43.         //清除按键中断
    44.         //提交下半部分   
    45.         schedule_work(work1);
    46.         //printk("press the key\n");

    47.         return 0;
    48. }
    49. void keyinit()
    50. {
    51.     gpx1con = ioremap(GPX1CON,4);

    52.     writel(readl(gpx1con)&~(0xff<<4)|(0xff<<4),gpx1con);
    53.     gpx1dat = ioremap(GPX1DAT,4);


    54. }
    55. ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
    56. {
    57.     printk("in kernel :key num is %d\n",key_num);   
    58.     copy_to_user(buf, &key_num, 4);

    59.     return 4;
    60. }
    61. int key_open(struct inode *node,struct file *filp)
    62. {
    63.     return 0;   
    64. }

    65. struct file_operations key_fops =
    66. {
    67.     .open = key_open,
    68.     .read = key_read,   
    69. };
    70. struct miscdevice key_miscdev = {

    71.     .minor = 200,
    72.     .name = "mykey",
    73.     .fops = &key_fops,
    74. };
    75. static int key_init()
    76. {

    77.     misc_register(&key_miscdev);
    78.      keyinit();

    79.     request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

    80.     request_irq(IRQ_EINT(10),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

    81.     //创建工作
    82.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    83.     INIT_WORK(work1,work1_fun);

    84.     init_timer(&key_timer);//init_timer的初始化
    85.     key_timer.function = key_timer_func;//设置超时函数   
    86.     add_timer(&key_timer);//add_timer注册定时器
    87.     return 0;
    88. }

    89. static void key_exit()
    90. {
    91.     misc_deregister(&key_miscdev);
    92.     free_irq(IRQ_EINT(9),0);
    93.     free_irq(IRQ_EINT(10),0);
    94. }

    95. module_init(key_init);
    96. module_exit(key_exit);  
    复制代码
    5.阻塞型驱动设计5.1.阻塞的必要性
    当应用程序调用read读取数据时,设备没有数据提供,但以后可能会有,或者进程正在试图去写,但设备暂时没有准备好去接收数据。当以上情况发生时。驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求得到满足。内核等待队列像是候车室
    1.读数据2.睡眠3.唤醒
    5.2.内核等待队列
    进程在哪里睡眠,就是内核等待队列
    5.2.1、定义等待队列
    wait_queue_head_t my_queue
    5.2.2、初始化等待队列
    init_waitqueue_head(&my_queue)
    5.2.3、定义+初始化等待队列
    DECLARE_WAIT_QUEUE_HEAD(my_queue)
    5.2.4、进入等待队列,睡眠
    wait_event(queue,condition)
    当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
    wait_event_interruptible(queue,condition)当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
    5.2.5从等待队列中唤醒进程
    wake_up(wait_queue_t *q)
    从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。wake_up_interruptible(wait_queue_t *q)从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程
    5.3.阻塞优化驱动
    1. #include<linux/module.h>
    2. #include<linux/init.h>
    3. #include<linux/miscdevice.h>
    4. #include<linux/fs.h>
    5. #include<linux/interrupt.h>
    6. #include<linux/io.h>
    7. #include<linux/irq.h>
    8. #include<linux/slab.h>
    9. #include<linux/vmalloc.h>
    10. #include<asm/uaccess.h>
    11. #include <linux/sched.h>
    12. MODULE_LICENSE("GPL");
    13. #define GPX1CON 0x11000C20
    14. #define GPX1DAT 0x11000C24
    15. unsigned int *gpx1con;
    16. unsigned int *gpx1dat;
    17. unsigned int key_num=0;
    18. wait_queue_head_t key_queue;//定义等待队列
    19. struct work_struct *work1;

    20. struct timer_list key_timer;
    21. void work1_fun(struct work_struct *work)
    22. {
    23.     mod_timer(&key_timer,jiffies+HZ/10);
    24.     //  printk("press the key\n");
    25. }
    26. void key_timer_func(unsigned long data)
    27. {   unsigned int key_val;
    28.     key_val = readl(gpx1dat)&(0x1<<1);
    29.     if(key_val==0)
    30.     {
    31.         key_num = 1;
    32.     }
    33.         //printk("\nhome key down\n");
    34.     key_val = readl(gpx1dat)&(0x1<<2);
    35.     if(key_val==0)
    36.     {
    37.         key_num = 2;
    38.     }      
    39.     wake_up(&key_queue);//按键按下则唤醒进程
    40. //printk("\n back key down\n");
    41. }
    42. irqreturn_t key_int(int irq,void *dev_id)
    43. {
    44.         //检测是否发生了按键中断



    45.         //清除按键中断


    46.         //提交下半部分   
    47.         schedule_work(work1);
    48.         //printk("press the key\n");

    49.         return 0;
    50. }
    51. void keyinit()
    52. {
    53.     gpx1con = ioremap(GPX1CON,4);

    54.     writel(readl(gpx1con)&~(0xff<<4)|(0xff<<4),gpx1con);
    55.     gpx1dat = ioremap(GPX1DAT,4);


    56. }
    57. ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
    58. {   
    59.     wait_event(key_queue,key_num);//进入等待队列,key_num是进入等待队列的条件
    60.     printk("in kernel :key num is %d\n",key_num);   
    61.     copy_to_user(buf, &key_num, 4);
    62.     key_num = 0;
    63.     return 4;
    64. }
    65. int key_open(struct inode *node,struct file *filp)
    66. {
    67.     return 0;   
    68. }
    69. struct file_operations key_fops =
    70. {
    71.     .open = key_open,
    72.     .read = key_read,   
    73. };
    74. struct miscdevice key_miscdev = {

    75.     .minor = 200,
    76.     .name = "mykey",
    77.     .fops = &key_fops,
    78. };
    79. static int mykey_init()
    80. {

    81.     misc_register(&key_miscdev);
    82.      keyinit();

    83.     request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

    84.     request_irq(IRQ_EINT(10),key_int,IRQF_TRIGGER_FALLING,"mykey",0);

    85.     //创建工作
    86.     work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
    87.     INIT_WORK(work1,work1_fun);
    88.     //初始化定时器
    89.     init_timer(&key_timer);
    90.     //添加超时函数
    91.     key_timer.function = key_timer_func;
    92.     //向内核注册
    93.     add_timer(&key_timer);
    94.     //初始化等待队列
    95.     init_waitqueue_head(&key_queue);
    96.     return 0;
    97. }

    98. static void mykey_exit()
    99. {
    100.     misc_deregister(&key_miscdev);
    101.     free_irq(IRQ_EINT(9),0);
    102.     free_irq(IRQ_EINT(10),0);
    103. }

    104. module_init(mykey_init);
    105. module_exit(mykey_exit);  
    复制代码
    应用程序调用
    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include <errno.h>

    4. int main(int argc,char **argv)
    5. {
    6.     int fd;
    7.     int key_num;
    8.     //1.打开设备
    9.     fd = open("/dev/mykey",0);
    10.     if(fd<0)
    11.         printf("open device fail\n");

    12.     //读取设备
    13.     read(fd,&key_num,4);
    14.     printf("key is %d\n",key_num);


    15.     //关闭设备
    16.     close(fd);
    17.     return 0;
    18. }
    复制代码
    当应用程序调用驱动程序读取按键值,此时按键没有按下,测试驱动程序使其应用程序进入等待状态,当有按键按下,则唤醒程序读取键值。


    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条



    手机版|小黑屋|与非网

    GMT+8, 2025-1-12 13:38 , Processed in 0.123231 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.