• Linux中断编程


    Linux中断编程

      中断:是指CPU在运行过程中,出现了某种异常事件,需要CPU先暂停当前工作,转而去处理新产生的异常事件,处理完后再返回暂停的事件继续往下执行。就例如我们正在使用手机进行微信视频聊天,这时突然有人打电话过来,这时手机的处理方式是手机来来电铃声响起,通知用户电话来了。
      中断,就是来处理未来时间内可能会发生的事件, 中断事件也称为异常事件。有了中断处理,则可大大提高CPU处理效率。
      在单片机中,我们也常用中断方式来处理一些紧急事件,帮我们实现快速响应一些实时性的事件。因此我们在编写中断服务函数时都是代码尽可能简洁、一定不能处理死循环、若需要处理的事情比较多则应在中断中设定标志位,然后将逻辑代码放到主函数中去实现。
      在Linux内核中,我们一般会将中断分为顶半部分底半部分。顶半部分主要是处理耗时短的代码(像单片机中设置标志位),启动底半部分代码;底半部分主要是处理耗时比较长的代码,完成中断响应后的事件处理。

    1. Linux下外部中断

      要使用外部中断,则需要完成中断三要素的配置:中断号(irq)、中断服务函数、中断触发方式(电平触发、边沿触发)。

    1.1 相关接口函数

    • 获取中断号gpio_to_irq

      在Linux内核中提供了方便函数获取引脚中断号。

    int gpio_to_irq(unsigned gpio)
    函数功能: 获取中断号
    返回值: 成功返回对应GPIO的中断号irq

    • 注册中断request_irq

    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,const char *name, void *dev)
    函数功能: 注册中断
    形 参: irq --中断号,gpio_to_irq函数返回值。
        handler --中断服务函数。
         服务函数原型:typedef irqreturn_t (*irq_handler_t)(int, void *);
        flags --中断触发方式。
         #define IRQF_TRIGGER_RISING 0x00000001 //上升沿
         #define IRQF_TRIGGER_FALLING 0x00000002 //下升沿
         #define IRQF_TRIGGER_HIGH 0x00000004//高电平
        #define IRQF_TRIGGER_LOW 0x00000008//低电平

        #define IRQF_SHARED 0x00000080 //共享中断
       name --中断注册标志。
       dev --传给中断服务函数的参数。
    返回值: 成功返回0,失败返回其它值。

    • 中断服务函数

    typedef irqreturn_t (*irq_handler_t)(int, void *);
    函数功能: 中断服务函数
    形 参: 第一个参数为中断号;第二个参数为注册函数传入的参数dev
    返回值:
       enum irqreturn {
        IRQ_NONE = (0 << 0), //如果不是本中断的则返回这个值,只在共享中断中使用
        IRQ_HANDLED = (1 << 0), //正确执行中断程序返回这个值,常用
        IRQ_WAKE_THREAD = (1 << 1), //表示去唤醒中断处理者的线程
      };

      注意: irqreturn_t (*irq_handler_t)(int, void *);函数中不能出现带休眠的函数,如msleep函数;该函数必须要返回值。

    • 注销free_irq

    free_irq(unsigned int irq, void *dev_id)
    函数功能: 注销中断
    形 参: irq --中断号,gpio_to_irq函数返回值。
       dev --传给中断服务函数的参数。需和注册时保持一致

    2. 工作队列

      中断处理函数分为中断顶半部分和中断底半部分。顶半部分代码实现即为中断服务函数,而底半部分代码则可由工作队列完成。

    2.1 工作队列简介

      在操作系统中,如果我们需要进行一项工作处理,往往需要创建一个任务来加入内核的调度队列。一个任务对应一个处理函数,如果要进行不同的事务处理,则需要创建多个不同的任务。任务作为CPU调度的基本单元,任务数量越大,则调度成本越高。工作队列(workqueue)机制简化了基础的任务创建和处理机制,一个workqueue对应一个实体task任务处理,工作队列中可以挂载多个工作实体,每一个工作都能对应不同的工作处理函数。即用户只需要创建一个workqueue,则可以完成多个挂接不同处理函数的工作队列。
       工作队列还具有将工作推后执行机制,工作队列可以把工作推后,交由一个内核线程去执行,也就是说,这个下半部分可以在进程上下文中执行。最重要的就是工作队列允许被重新调度甚至是睡眠。
      workqueue的处理依赖于task任务。一个workqueue队列会创建关联其对应的task任务,一个workqueue会挂载多个工作进行处理,每个工作都有工作处理函数。当workqueue得到调度,即其关联的task得到运行,在每次task的调度期间,都会从工作队列中按照先后顺序取出一个work来进行处理。workqueue模块在初始化时,会创建一个系统默认的工作队列,用户可根据需要将work添加到该队列中去执行。

    2.2 工作相关函数接口

    • 工作结构体struct work_struct
    #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
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      在工作结构体体中,我们需要关心的成员是工作处理函数:work_func_t func,简单来说即一个工作会对应有一个处理函数。工作处理函数原型如下:

    #include 
    typedef void (*work_func_t)(struct work_struct *work);
    
    • 1
    • 2
    • 初始化工作INIT_WORK

    #define INIT_WORK(_work, _func)
    函数功能: 初始化工作,以宏的方式实现
    形 参: _work --工作结构体体指针
       _func --工作处理函数

    • 工作调度schedule_work

    int schedule_work(struct work_struct *work)

    2.3工作队列使用步骤

    1. 定义工作结构体struct work_struct,初始化工作INIT_WORK;
    2. 编写工作处理函数void (*work_func_t)(struct work_struct *work);
    3. 在合适的地方调调度工作(一般在中断顶半部分);

    2.4工作队列使用示例

      下面以按键为例,实现中断方式按键检测,通过工作队列处理底半部分代码,杂项设备框架实现设备注册。
    在这里插入图片描述
    在这里插入图片描述

    • K1 – GPX3_2
    • K2 --GPX3_3
    • K3 --GPX3_4
    • K4 --GPX3_5
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #include 
    #include 
    #include 
    struct work_struct key_work;/*工作结构体*/
    struct _KEY
    {
    	unsigned int gpio;/*按键引脚*/
    	char name[20];/*按键名*/
    	int irq;/*中断号*/
    	int key_num;/*按键编号*/
    };
    static struct _KEY KEY_GPIO_PIN[]=
    {
    	{EXYNOS4_GPX3(2),"key1",0,1},
    	{EXYNOS4_GPX3(3),"key2",0,2},
    	{EXYNOS4_GPX3(4),"key3",0,3},	
    	{EXYNOS4_GPX3(5),"key4",0,4},	
    };
    static struct _KEY *key_p;
    static unsigned int key_val;
    /*工作服务函数*/
    void key_work_func(struct work_struct *work)
    {
    	msleep(30);/*按键消抖*/
    	if(gpio_get_value(key_p->gpio)==0)
    	{
    		printk(" key%d 按下\n",key_p->key_num);
    	}
    	else 
    	{
    		printk(" key%d 松开\n",key_p->key_num);
    	}
    	key_val=key_p->key_num;
    }
    /*中断服务函数*/
    static irqreturn_t key_irq_handler(int irq, void *dev)
    {
    	key_p=(struct _KEY *)dev;
    	schedule_work(&key_work);/*调度工作*/
    	return IRQ_HANDLED;/*中断正常处理*/
    }
    static int key_open(struct inode *inode, struct file *file)
    {
    	printk("设备打开成功\n");
    	return 0;
    }
    static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *seek)
    {
    	int res=copy_to_user(buf,&key_val, 4);
    	key_val=0;
    	return 4-res;
    }
    static int key_release(struct inode *inode, struct file *file)
    {
    	printk("设备关闭成功\n");
    	return 0;
    }
    /*文件操作集合*/
    static struct file_operations key_fops=
    {
    	.owner= THIS_MODULE, /*当前模块文件操作集合所有者*/
    	.open=key_open,/*open函数接口*/
    	.read=key_read,/*read函数接口*/
    	.release=key_release,/*close函数接口*/
    };
    /*
    字符设备注册:主设备+次设备号
    主设备  --用来区分类(杂项设备、输入设备)
    次设备号  --对应哪个设备
    杂项设备的主设备号固定为:10
    */
    static struct miscdevice key_miscdev = {
    	.minor	= MISC_DYNAMIC_MINOR,/*次设备号255由内核分配*/
    	.name	= "tiny4412_key",/*设备节点名字,会在/dev下生成*/
    	.fops	= &key_fops,/**/
    };
    
    static int __init tiny4412_key_module_init(void)
    {
    	int i=0;
    	int res;
        printk("hello,驱动注册成功\n");
    	/*初始化工作*/
    	INIT_WORK(&key_work,key_work_func);
    	/*注册中断*/
    	for(i=0;i<sizeof(KEY_GPIO_PIN)/sizeof(KEY_GPIO_PIN[0]);i++)
    	{
    		KEY_GPIO_PIN[i].irq=gpio_to_irq(KEY_GPIO_PIN[i].gpio);/*获取中断号*/
    		res=request_irq(KEY_GPIO_PIN[i].irq,key_irq_handler,IRQF_TRIGGER_RISING|IRQF_TRIGGER_FALLING,
    												KEY_GPIO_PIN[i].name,&KEY_GPIO_PIN[i]);
    		if(res)
    		{
    			printk("%d中断注册失败\n",i);
    			return -1;
    		}
    	}
    	/*注册杂项设备*/
    	misc_register(&key_miscdev);
        return 0;
    }
    
    static void __exit tiny4412_key_module_cleanup(void)
    {
    	int i=0;
    	/*注销中断*/
    	for(i=0;i<sizeof(KEY_GPIO_PIN)/sizeof(KEY_GPIO_PIN[0]);i++)
    	{
    		free_irq(KEY_GPIO_PIN[i].irq,&KEY_GPIO_PIN[i]);
    	}
    	/*注销杂项设备*/
    	misc_deregister(&key_miscdev);
    	printk("Good-bye, 驱动注销成功\n");
    }
    
    module_init(tiny4412_key_module_init);/*驱动入口函数:注册驱动时调用*/
    module_exit(tiny4412_key_module_cleanup);/*驱动出口函数:注销驱动时调用*/
    
    MODULE_LICENSE("GPL");/*驱动许可证*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129

    在这里插入图片描述

  • 相关阅读:
    AR空间音频能力,打造沉浸式声音体验
    如何运营好技术相关的自媒体?
    六大科研工具推荐,外文文献阅读管理全都搞定!
    一文整理深度学习【CT原理、成像及CT重建知识汇总】
    qgis 将县区的数据转成市区的边界数据
    面试中经常问到的几个问题,快来看看能答对几道吧
    力扣刷题-二叉树-完全二叉树的节点个数
    linux pinctrl子系统
    html5期末大作业:基于html+css+javascript+jquery+bootstarp响应式图书电商HTML模板网上书店(25页)
    动态规划43(Leetcode91解码方法)
  • 原文地址:https://blog.csdn.net/weixin_44453694/article/details/126812705