做网站推广托管费用,邢台企业建站,怎么查询网站建设时间,义乌上溪镇i.MX8MM处理器采用了先进的14LPCFinFET工艺#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53#xff0c;单核Cortex-M4#xff0c;多达五个内核 #xff0c;主频高达1.8GHz#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…i.MX8MM处理器采用了先进的14LPCFinFET工艺提供更快的速度和更高的电源效率;四核Cortex-A53单核Cortex-M4多达五个内核 主频高达1.8GHz2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码H.264、H.265、VP8、VP9视频硬解码并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩物联网工业控制医疗智能交通等可用于任何通用工业和物联网应用、 【公众号】迅为电子
【粉丝群】258811263加群获取驱动文档例程 第五十七章 Linux中断实验
本章导读
在 Linux 下的驱动实验中中断是频繁使用的功能Linux 内核提供了完善的中断框架我们只需要使
用内核提供的函数便可以方便的使用中断功能。本章我们就来学习一下如何在 Linux 中使用中断。
57.1章节讲解了Linux中断基础理论知识
57.2章节在iTOP-IMX8MM开发板上以按键中断为例进行实验。
本章内容对应视频讲解链接在线观看
中断基础概念 → https://www.bilibili.com/video/BV1Vy4y1B7ta?p34
设备树中的中断节点以及相关函数 → https://www.bilibili.com/video/BV1Vy4y1B7ta?p35
按键中断实验 → https://www.bilibili.com/video/BV1Vy4y1B7ta?p36
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\014-Linux中断实验”路径下。
57.1 Linux中断简介
57.1.1 Linux中断介绍
中断是指 CPU 在执行程序的过程中出现了某些突发事件急待处理CPU 必须暂停当前程序的执行
转去处理突发事件处理完毕后又返回原程序被中断的位置继续执行。由于中断的存在极大的提高了 CPU的运行效率但是设备的中断会打断内核进程中的正常调度和运行系统对更高吞吐率的追求势必要求中断服务程序尽量短小精悍。
举例来说我现在正在厨房做饭突然电话响了然后我关火去接电话接完电话在回去开火继续做饭这个过程就是中断的一个过程。在这个看似简单的过程中却涉及到了中断的几个过程我们一起来看一下
电话铃声响了中断请求我要去接电话中断响应我关掉火保护现场我接电话的过程中断处理接完电话回到厨房开火恢复现场继续做饭中断返回如果我不接电话中断屏蔽
为保证系统实时性中断服务程序必须足够简短但实际应用中某些时候发生中断时必须处理大量的
事物这时候如果都在中断服务程序中完成则会严重降低中断的实时性基于这个原因linux 系统提出了一个概念把中断服务程序分为两部分顶半部-底半部 。
顶半部中断上文完成尽可能少的比较急的功能它往往只是简单的读取寄存器的中断状态并清除中断标志后就进行“中断标记”也就是把底半部处理程序挂到设备的底半部执行队列中的工作。 顶半部的特点就是响应速度快。
底半部中断下文处理中断的剩余大部分任务可以被新的中断打断。
57.1.2 中断相关函数
linux 中断有专门的中断子系统其实现原理很复杂但是驱动开发者不需要知道其实现的具体细节
只需要知道如何应用该子系统提供的 API 函数来编写中断相关驱动代码即可。
1 获取中断号相关函数
编写驱动的时候需要用到中断号每一个中断都有中断号我们用到中断号中断信息已经写到了设备树里面因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号函数原型如下表所示 函数 unsigned int irq_of_parse_and_map(struct device_node *dev,int index) dev 设备节点 index 索引号interrupts 属性可能包含多条中断信息通过 index 指定要获取的信息。 返回值 中断号 功能 通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号
如下表所示
如果使用 GPIO 的话可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号函数原型如下表所 函数 int gpio_to_irq(unsigned int gpio) gpio 要获取的 GPIO 编号 返回值 GPIO 对应的中断号 功能 获取GPIO对应的中断号
2 申请中断函数
同GPIO一样在Linux内核里面如果我们要使用某个中断也是需要申请的申请中断我们使用的函数是 request_irq。函数原型如下表所示 函数 int request_irq( unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev) irq 要申请中断的中断号 handler 中断处理函数当中断发生以后就会执行此中断处理函数。 flags 中断标志 name 中断名字设置以后可以在开发板/proc/interrupts 文件中看到对应的中断名字 dev 如果将 flags 设置为 IRQF_SHARED 的话 dev 用来区分不同的中断一般情况下将 dev 设置为设备结构体dev 会传递给中断处理函数 irq_handler_t 的第二个参数。 返回值 中断申请成功返回0其他负值则中断申请失败如果返回-EBUSY 的话表示中断已经被申请了。
中断标识可以在文件 include/linux/interrupt.h 里面查看所有的中断标志这里我们介绍几个常用的中断标志如下图所示 标志 功能 IRQF_SHARED 多个设备共享一个中断线共享的所有中断都必须指定此标志。如果使用共享中断的话request_irq 函数的 dev 参数就是唯一区分他们的标志。 IRQF_ONESHOT 单次中断中断执行一次就结束。 IRQF_TRIGGER_NONE 无触发。 IRQF_TRIGGER_RISING 上升沿触发。 IRQF_TRIGGER_FALLING 下降沿触发。 IRQF_TRIGGER_HIGH 高电平触发。 IRQF_TRIGGER_LOW 低电平触发。
3 、free_irq 函数
中断使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的那么 free_irq 会
删除中断处理函数并且禁止中断。free_irq 函数原型如下所示 函数 void free_irq(unsigned int irq,void *dev) irq 要释放的中断 dev 如果中断设置为共享(IRQF_SHARED)的话此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。 返回值 无 功能 释放掉相应的中断
4、中断处理函数
使用 request_irq 函数申请中断的时候需要设置中断处理函数中断处理函数函数如下表所示 函数 irqreturn_t (*irq_handler_t) (int, void *) 第一个参数 要中断处理函数要相应的中断号 第二个参数 是一个指向 void 的指针也就是个通用指针需要与 request_irq 函数的 dev 参数保持一致。用于区分共享中断的不同设备dev 也可以指向设备数据结构。 返回值 中断处理函数的返回值为 irqreturn_t 类型
irqreturn_t 类型定义如下所示 enum irqreturn { IRQ_NONE (0 0), IRQ_HANDLED (1 0), IRQ_WAKE_THREAD (1 1), }; typedef enum irqreturn irqreturn_t; 可以看出 irqreturn_t 是个枚举类型一共有三种返回值。一般中断服务函数返回值使用如下形式
return IRQ_RETVAL(IRQ_HANDLED)
5、中断使能和禁止函数 常用的中断使用和禁止函数如下所示 void enable_irq(unsigned int irq) void disable_irq(unsigned int irq) enable_irq 和 disable_irq 用于使能和禁止指定的中断irq 就是要禁止的中断号。disable_irq 函数要
等到当前正在执行的中断处理函数执行完才返回因此使用者需要保证不会产生新的中断并且确保所有
已经开始执行的中断处理程序已经全部退出。在这种情况下可以使用另外一个中断禁止函数 void disable_irq_nosync(unsigned int irq) disable_irq_nosync 函数调用以后立即返回不会等待当前中断处理程序执行完毕。
57.1.3 中断上文和中断下文
中断的存在可以极大的提高CPU的运行效率但是中断会打断内核进程中的正常调度和运行所以为保证系统实时性中断服务程序必须足够简短但实际应用中某些时候发生中断时必须处理大量的事物这时候如果都在中断服务程序中完成则会严重降低中断的实时性基于这个原因linux 系统提出了一个概念把中断服务程序分为两部分中断上文和中断下文。
有些资料中也将顶半部和底半部称为上半部和下半部都是一个意思。Linux 内核将中断分为顶半部和
底半部的主要目的就是实现中断处理函数的快进快出那些对时间敏感、执行速度快的操作可以放到中断
处理函数中也就是顶半部。剩下的所有工作都可以放到底半部去执行至于哪些代码要在顶半部完成哪些代码要在底半部完成并没有严格的要求要根据实际情况来判断下面有一些参考点
① 如果要处理的内容不希望被其他中断打断那么可以放到上半部。
② 如果要处理的任务对时间敏感可以放到上半部。
③ 如果要处理的任务与硬件有关可以放到上半部
④ 除了上述三点以外的其他任务优先考虑放到下半部。
中断上文完成尽可能少却比较急的任务中断上文的特点就是响应速度快。中断下文处理中断剩余的大量比较耗时间的任务而且可以被新的中断打断。
举例来说我现在正在厨房做饭突然电话响了然后我关火去接电话快递员打电话让我下楼去拿快递接完电话叫我女朋友去下楼拿快递然后我在回去开火继续做饭这个过程就是中断上下文。
分析例子快递员打电话让我下去拿快递这个事情很紧急所以要快速处理这个就是要在中断上文中完成。但是下楼拿快递这个过程非常耗时间所以叫女朋友去拿快递这个就是中断下文。下楼拿快递很耗时间如果我不叫女朋友去帮我拿而是自己拿等我拿完饭回来我锅里的菜是不是就凉了呀同理如果你在中断里面做很耗时间的时间系统就会崩溃。如果女朋友在去拿快递的过程中突然口渴了要去超市买水所以中断下半部分是可以被中断打断的。
总之中断上文越快越好中断下文可以做比较耗时间的事情但是不能死循环。Linux中断可以嵌套吗以前是可以现在不可以。
57.1.4 设备树中的中断节点
如果一个设备需要用到中断功能开发人员就需要在设备树中配置好中断属性信息因为设备树是用来描述硬件信息的然后Linux内核通过设备树配置的中断属性来配置中断功能。对于中断控制器而言打开/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/itop8mm-evk.dtsii文件其中的 gic节点就是 IMX8MM的中断控制器节点节点内容如下所示
gic: interrupt-controller38800000 {compatible arm,gic-v3;reg 0x0 0x38800000 0 0x10000, /* GIC Dist */0x0 0x38880000 0 0xC0000; /* GICR (RD_base SGI_base) */#interrupt-cells 3;interrupt-controller;interrupts GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH;interrupt-parent gic;};
简单说明
IMX8MM使用中断控制器是 gic-v3。
gic-v3 是 ARM Generic Interrupt Controller, version 3 的缩写是一款 ARM 出品的通用中断控制器。
AArch64 SMP 内核通常与 GICv3 搭配使用GICv3 提供了专用外设中断PPI共享外设中断SPI软件生成的中断SGI和特定于区域的外设中断LPI。
#interrupt-cells 3 表明 Interrupt client devices 需要用 3个单元才能确定引用的中断例如 interrupts GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH;。interrupt-controller 节点为空表示当前节点是中断控制器。
对于 gpio 来说gpio 节点也可以作为中断控制器比如 imx6ull.dtsi 文件中的 gpio5 节点内容如下所示
1 gpio5 : gpio 020ac000{
2 compatible fsl,imx6ul-gpio, fsl,imx35-gpio;
3 reg 0x020ac000 0x4000;
4 interrupts GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH, GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH;
5 gpio-controller;
6 #gpio-cells 2;
7 interrupt-controller;
8 #interrupt-cells 2;
9 };
第 4 行interrupts 描述中断源信息对于 gpio5 来说一共有两条信息中断类型都是 SPI触发电
平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源一个是 74一个是 75可以打开参考手册的“Chapter 3 Interrupts and DMA Events”章节找到表 3-1有如图所示的内容 从上图可以看出GPIO5 一共用了 2 个中断号一个是 74一个是 75。其中 74 对应 GPIO5_IO00~GPIO5_IO15 这低 16 个 IO75 对应 GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO。
第 7 行interrupt-controller 表明了 gpio5 节点也是个中断控制器用于控制 gpio5 所有 IO的中断。
第 8 行将#interrupt-cells 修改为 2。
简单总结下与中断有关的设备树属性信息
#interrupt-cells指定中断源的信息 cells 个数。 interrupt-controller表示当前节点为中断控制器 interrupts指定中断号触发方式等。 interrupt-parent指定父中断也就是中断控制器
中断实际上是非常复杂的但是作为开发人员我们只需要关心怎么在设备树中指定中断怎么在代码中获得中断就可以。其他的事情比如设备树中的中断控制器这些都是由原厂的BSP工程师帮我们写好了我们不需要来修改他。除非将来你有机会去原厂工作否则我们不会从头开始写一个设备树文件的分工是非常明确的。我们需要关注的点是怎么在设备树里面描述一个外设的中断节点我们来看一个例子。
ft5x06_ts38 {compatible edt,edt-ft5x06;reg 0x38;pinctrl-names defaults;pinctrl-0 pinctrl_ft5x06_int;interrupt-parent gpio1;interrupts 15 2;status okay;
}
pinctrl_ft5x06_int: ft5x06_int {fsl,pins /*MX8MM_IOMUXC_GPIO1_IO09_GPIO1_IO9 0x159*/MX8MM_IOMUXC_GPIO1_IO15_GPIO1_IO15 0x159MX8MM_IOMUXC_SAI5_RXD2_GPIO3_IO23 0x41;};
在这个例子中我们先使用pinctrl和gpio子系统把这个引脚设置为了gpio功能因为我们在使用中断的时候需要把引脚设置成输入。然后使用interrupt-parent和interrupts属性来描述中断。interrupt-parent的属性值是gpio1也就是他的要使用gpio1这个中断控制器为什么是gpio1呢因为我们的引脚使用的是gpio1里面的io15所以我们使用的是gpio1这个中断控制器。interrupts属性设置的是中断源为什么里面是两个cells呢因为我们在gpio1这个中断控制器里面#interrupt-cells的值为2如下图所示 例子中的第一个 cells 的 15表示 GPIO1 组的 15号 IO。2表示下降沿有效。
IRQ_TYPE_EDGE_BOTH 定义在文件 include/linux/irq.h 中定义如下 所以我们在设备树里面配置中断的时候只需要两个步骤即可第一个步骤是把管脚设置为gpio功能。第二个步骤是使用interrupt-parent和interrupts属性来描述中断。 57.2 实验程序编写
我们以iMX8MM开发板为例写一个按键中断按一下按键就让在终端上打印一句话这个程序是非常简单的因为他没有涉及到中断下文的编写只有中断上文。 57.2.1 修改设备树文件
我们修改设备树文件itop8mm-evk.dtsi路径在源码目录/home/topeet/linux/linux-imx/arch/arm64/boot/dts/freescale/目录下。我们要使用开发板底板上的音量键进行试验首先将原来的节点注释掉如下图所示 然后在根节点下添加以下内容。 注释掉以前修改的内容如下图所示 然后编译源码编译源码请参考IMX8MM开发板使用手册。然后重新烧写Linux镜像接下来我们来检查你编译的设备树文件有没有被加载到系统里面也就是说查看你添加的节点有没有如下图所示 从上图可以看到我们添加的节点我们查看下节点“test”如下图所示 从上图可以看出“compatible”属性值是keys。
57.2.2 编写驱动代码
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\014-Linux中断实验\001”路径下。
我们在Ubuntu的/home/topeet/imx8mm/14/001目录下新建driver.c将上次编译driver.c的Makefile文件和build.sh拷贝到新建的driver.c同级目录下修改Makefile为
obj-m driver.o
KDIR:/home/topeet/linux/linux-imx
PWD?$(shell pwd)
all:make -C $(KDIR) M$(PWD) modules ARCHarm64
clean:make -C $(KDIR) M$(PWD) clean
完整的驱动代码如下面所示
/** Author:topeet* Description: 使用irq_of_parse_and_map函数来获取中断号*/
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_irq.h#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;/*** description: 中断处理函数test_key* param {int} irq 要申请的中断号* param {void} *args * return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk(test_key \n);return IRQ_RETVAL(IRQ_HANDLED);
}
/***************************************************************************************** brief led_probe : 与设备信息层设备树匹配成功后自动执行此函数* param inode : 文件索引* param file : 文件* return 成功返回 0 ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret 0;// 打印匹配成功进入probe函数printk(led_probe\n);test_device_node of_find_node_by_path(/test);if (test_device_node NULL){//查找节点失败则打印信息printk(of_find_node_by_path is error \n);return -1;}gpio_nu of_get_named_gpio(test_device_node, gpios, 0);if (gpio_nu 0){printk(of_get_namd_gpio is error \n);return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号//irq gpio_to_irq(gpio_nu);irq irq_of_parse_and_map(test_device_node, 0);printk(irq is %d \n, irq);/*申请中断irq:中断号名字 test_key中断处理函数IRQF_TRIGGER_RISING中断标志意为上升沿触发test_key中断的名字*/ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);if (ret 0){printk(request_irq is error \n);return -1;}return 0;
}int led_remove(struct platform_device *pdev)
{printk(led_remove\n);return 0;
}
const struct platform_device_id led_idtable {.name keys,
};
const struct of_device_id of_match_table_test[] {{.compatible keys},{},
};
struct platform_driver led_driver {//3. 在led_driver结构体中完成了led_probe和led_remove.probe led_probe,.remove led_remove,.driver {.owner THIS_MODULE,.name led_test,.of_match_table of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高优先与.id_table进行匹配.id_table led_idtable};/*** description: 模块初始化函数* param {*}* return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret 0;//2.在init函数里面注册了platform_driverret platform_driver_register(led_driver);if (ret 0){printk(platform_driver_register error \n);}printk(platform_driver_register ok \n);return 0;
}/*** description: 模块卸载函数* param {*}* return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(led_driver);printk(goodbye! \n);
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE(GPL);57.2.3 运行测试
编译驱动代码为驱动模块编译成功如下图所示
我们进入共享文件夹加载驱动模块如下图所示 那么我们怎么查看是否申请中断成功了呢?我们可以进入到开发板的/proc/interrupts目录查看如下图所示 cat /proc/interrupts | grep test_key 我们按一下底板上的音量VOL可以看到控制台打印信息如下图所示 我们可以看中断发生了几次我们可以进入到开发板的/proc/irq/196/spurious 目录下如下图所示 cat /proc/irq/196/spurious 从上图可知中断发生了15次我们可以数一下test_key打印的次数是十五次说明发生一次中断会打印一次test_key。
57.2.4 优化方案
上面的例子我们是使用函数gpio_to_irq来获取中断号的接下来我们通过在设备树文件里面使用属性“interrupt-parent”和“interrupts”来获取中断号我们修改设备树文件如下图所示 重新编译源码然后烧写设备树镜像启动开发板后如下图所示 我们检查一下有没有我们添加的节点如下图所示出现添加的设备节点test。 接下来我们改一下驱动driver.c,完整代码如下所示
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\014-Linux中断实验\002”路径下。
/** Author:topeet* Description: 使用gpio_to_irq函数来获取中断号*/
#include linux/init.h
#include linux/module.h
#include linux/platform_device.h
#include linux/of.h
#include linux/of_address.h
#include linux/of_irq.h#include linux/gpio.h
#include linux/of_gpio.h
#include linux/interrupt.h
//定义结构体表示我们的节点
struct device_node *test_device_node;
struct property *test_node_property;
//要申请的中断号
int irq;
// GPIO 编号
int gpio_nu;/*** description: 中断处理函数test_key* param {int} irq 要申请的中断号* param {void} *args * return {*}IRQ_HANDLED*/
irqreturn_t test_key(int irq, void *args)
{printk(test_key \n);return IRQ_HANDLED;
}
/***************************************************************************************** brief led_probe : 与设备信息层设备树匹配成功后自动执行此函数* param inode : 文件索引* param file : 文件* return 成功返回 0 ****************************************************************************************/
int led_probe(struct platform_device *pdev)
{int ret 0;// 打印匹配成功进入probe函数printk(led_probe\n);test_device_node of_find_node_by_path(/test);if (test_device_node NULL){//查找节点失败则打印信息printk(of_find_node_by_path is error \n);return -1;}gpio_nu of_get_named_gpio(test_device_node, gpios, 0);if (gpio_nu 0){printk(of_get_namd_gpio is error \n);return -1;}//设置GPIO为输入模式gpio_direction_input(gpio_nu);//获取GPIO对应的中断号// irq gpio_to_irq(gpio_nu);irq irq_of_parse_and_map(test_device_node,0);printk(irq is %d \n, irq);/*申请中断irq:中断号名字 test_key中断处理函数IRQF_TRIGGER_RISING中断标志意为上升沿触发test_key中断的名字*/ret request_irq(irq, test_key, IRQF_TRIGGER_RISING, test_key, NULL);if (ret 0){printk(request_irq is error \n);return -1;}return 0;
}int led_remove(struct platform_device *pdev)
{printk(led_remove\n);return 0;
}
const struct platform_device_id led_idtable {.name keys,
};
const struct of_device_id of_match_table_test[] {{.compatible keys},{},
};
struct platform_driver led_driver {//3. 在led_driver结构体中完成了led_probe和led_remove.probe led_probe,.remove led_remove,.driver {.owner THIS_MODULE,.name led_test,.of_match_table of_match_table_test},//4 .id_table的优先级要比driver.name的优先级要高优先与.id_table进行匹配.id_table led_idtable};/*** description: 模块初始化函数* param {*}* return {*}*/
static int led_driver_init(void)
{//1.我们看驱动文件要从init函数开始看int ret 0;//2.在init函数里面注册了platform_driverret platform_driver_register(led_driver);if (ret 0){printk(platform_driver_register error \n);}printk(platform_driver_register ok \n);return 0;
}/*** description: 模块卸载函数* param {*}* return {*}*/
static void led_driver_exit(void)
{free_irq(irq, NULL);platform_driver_unregister(led_driver);printk(gooodbye! \n);
}
module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_LICENSE(GPL); 编译驱动代码为驱动模块如下图所示 编译成功加载驱动可以成功获得irq号如下图所示 我们按一下底板上的音量VOL可以看到控制台打印信息如下图所示 我们可以看中断发生了几次我们可以进入到开发板的/proc/irq/196/spurious 目录下如下图所示 cat /proc/irq/196/spurious 从上图可知中断发生了9次我们可以数一下test_key打印的次数是9次说明发生一次中断会打印一次test_key。