搜索
首页运维linux运维linux内核有中断函数吗
linux内核有中断函数吗Nov 11, 2022 pm 02:40 PM
linux

linux内核有中断函数。在Linux内核中要想使用某个中断是需要申请的,而request_irq()函数用于申请中断,中断使用完成以后就要通过free_irq()函数释放掉相应的中断;还有enable_irq()和disable_irq(),它们用于使能和禁止指定的中断。

linux内核有中断函数吗

本教程操作环境:linux7.3系统、Dell G3电脑。

1.Linux中断

1.1 Linux中断API函数

request_irq函数

在 Linux 内核中要想使用某个中断是需要申请的,request_irq 函数用于申请中断,request_irq函数可能会导致睡眠,因此不能在中断上下文或者其他禁止睡眠的代码段中使用 request_irq 函数。request_irq 函数会激活(使能)中断,所以不需要我们手动去使能中断,request_irq 函数原型如下:
linux内核有中断函数吗
irq:要申请中断的中断号。
handler:中断处理函数,当中断发生以后就会执行此中断处理函数。
flags:中断标志,可以在文件 include/linux/interrupt.h 里面查看所有的中断标志
linux内核有中断函数吗
name:中断名字,设置以后可以在/proc/interrupts 文件中看到对应的中断名字。
dev:如果将 flags 设置为 IRQF_SHARED 的话,dev 用来区分不同的中断,一般情况下将dev 设置为设备结构体,dev 会传递给中断处理函数 irq_handler_t 的第二个参数。
返回值:0 中断申请成功,其他负值 中断申请失败,如果返回-EBUSY 的话表示中断已经被申请了。

free_irq

使用中断的时候需要通过 request_irq 函数申请,使用完成以后就要通过 free_irq 函数释放掉相应的中断。如果中断不是共享的,那么 free_irq 会删除中断处理函数并且禁止中断。free_irq函数原型如下所示:
linux内核有中断函数吗
函数参数和返回值含义如下:
irq:要释放的中断。
dev:如果中断设置为共享(IRQF_SHARED)的话,此参数用来区分具体的中断。共享中断只有在释放最后中断处理函数的时候才会被禁止掉。
返回值:无。

中断处理函数

使用 request_irq 函数申请中断的时候需要设置中断处理函数,中断处理函数格式如下所示:
linux内核有中断函数吗
linux内核有中断函数吗

中断使能和禁止函数

常用的中断使用和禁止函数如下所示:
linux内核有中断函数吗
enable_irq 和 disable_irq 用于使能和禁止指定的中断,irq 就是要禁止的中断号。disable_irq函数要等到当前正在执行的中断处理函数执行完才返回,因此使用者需要保证不会产生新的中断,并且确保所有已经开始执行的中断处理程序已经全部退出。在这种情况下,可以使用另外一个中断禁止函数:
linux内核有中断函数吗
disable_irq_nosync 函数调用以后立即返回,不会等待当前中断处理程序执行完毕。上面三个函数都是使能或者禁止某一个中断,有时候我们需要关闭当前处理器的整个中断系统,也就是在学习 STM32 的时候常说的关闭全局中断,这个时候可以使用如下两个函数:
linux内核有中断函数吗
local_irq_enable 用于使能当前处理器中断系统,local_irq_disable 用于禁止当前处理器中断系统。假如 A 任务调用 local_irq_disable 关闭全局中断 10S,当关闭了 2S 的时候 B 任务开始运行,B 任务也调用 local_irq_disable 关闭全局中断 3S,3 秒以后 B 任务调用 local_irq_enable 函数将全局中断打开了。此时才过去 2 3=5 秒的时间,然后全局中断就被打开了,此时 A 任务要关闭 10S 全局中断的愿望就破灭了,然后 A 任务就“生气了”,结果很严重,可能系统都要被A 任务整崩溃。为了解决这个问题,B 任务不能直接简单粗暴的通过 local_irq_enable 函数来打开全局中断,而是将中断状态恢复到以前的状态,要考虑到别的任务的感受,此时就要用到下面两个函数:
linux内核有中断函数吗

1.2 上半部和下半部

在有些资料中也将上半部和下半部称为顶半部和底半部,都是一个意思。我们在使用request_irq 申请中断的时候注册的中断服务函数属于中断处理的上半部,只要中断触发,那么中断处理函数就会执行。我们都知道中断处理函数一定要快点执行完毕,越短越好,但是现实往往是残酷的,有些中断处理过程就是比较费时间,我们必须要对其进行处理,缩小中断处理函数的执行时间。比如电容触摸屏通过中断通知 SOC 有触摸事件发生,SOC 响应中断,然后通过 IIC 接口读取触摸坐标值并将其上报给系统。但是我们都知道 IIC 的速度最高也只有400Kbit/S,所以在中断中通过 IIC 读取数据就会浪费时间。我们可以将通过 IIC 读取触摸数据的操作暂后执行,中断处理函数仅仅相应中断,然后清除中断标志位即可。这个时候中断处理过程就分为了两部分:
上半部:上半部就是中断处理函数,那些处理过程比较快,不会占用很长时间的处理就可以放在上半部完成。
下半部:如果中断处理过程比较耗时,那么就将这些比较耗时的代码提出来,交给下半部去执行,这样中断处理函数就会快进快出。
因此,Linux 内核将中断分为上半部和下半部的主要目的就是实现中断处理函数的快进快出,那些对时间敏感、执行速度快的操作可以放到中断处理函数中,也就是上半部。剩下的所有工作都可以放到下半部去执行,比如在上半部将数据拷贝到内存中,关于数据的具体处理就可以放到下半部去执行。至于哪些代码属于上半部,哪些代码属于下半部并没有明确的规定,一切根据实际使用情况去判断,这个就很考验驱动编写人员的功底了。这里有一些可以借鉴的参考点:
①、如果要处理的内容不希望被其他中断打断,那么可以放到上半部。
②、如果要处理的任务对时间敏感,可以放到上半部。
③、如果要处理的任务与硬件有关,可以放到上半部
④、除了上述三点以外的其他任务,优先考虑放到下半部。
下半部机制:

软中断

一开始 Linux 内核提供了“bottom half”机制来实现下半部,简称“BH”。后面引入了软中断和 tasklet 来替代“BH”机制,完全可以使用软中断和 tasklet 来替代 BH,从 2.5 版本的 Linux内核开始 BH 已经被抛弃了。Linux 内核使用结构体 softirq_action 表示软中断, softirq_action结构体定义在文件 include/linux/interrupt.h 中,内容如下:
linux内核有中断函数吗
在 kernel/softirq.c 文件中一共定义了 10 个软中断,如下所示:
linux内核有中断函数吗
NR_SOFTIRQS 是枚举类型,定义在文件 include/linux/interrupt.h 中,定义如下:
linux内核有中断函数吗
可以看出,一共有 10 个软中断,因此 NR_SOFTIRQS 为 10,因此数组softirq_vec 有 10 个元素。softirq_action 结构体中的 action 成员变量就是软中断的服务函数,数组 softirq_vec 是个全局数组,因此所有的 CPU(对于 SMP 系统而言)都可以访问到,每个 CPU 都有自己的触发和控制机制,并且只执行自己所触发的软中断。但是各个 CPU 所执行的软中断服务函数确是相同的,都是数组 softirq_vec 中定义的 action 函数。要使用软中断,必须先使用 open_softirq 函数注册对应的软中断处理函数,open_softirq 函数原型如下:
linux内核有中断函数吗
nr:要开启的软中断,在示例代码 51.1.2.3 中选择一个。
action:软中断对应的处理函数。
返回值:没有返回值。
注册好软中断以后需要通过 raise_softirq 函数触发,raise_softirq 函数原型如下:
linux内核有中断函数吗
软中断必须在编译的时候静态注册!Linux 内核使用 softirq_init 函数初始化软中断,softirq_init 函数定义在 kernel/softirq.c 文件里面,函数内容如下:
linux内核有中断函数吗

tasklet

tasklet 是利用软中断来实现的另外一种下半部机制,在软中断和 tasklet 之间,建议大家使用 tasklet。Linux 内核使用结构体
linux内核有中断函数吗
第 489 行的 func 函数就是 tasklet 要执行的处理函数,用户定义函数内容,相当于中断处理函数。如果要使用 tasklet,必须先定义一个 tasklet,然后使用 tasklet_init 函数初始化 tasklet,taskled_init 函数原型如下:
linux内核有中断函数吗
linux内核有中断函数吗
函数参数和返回值含义如下:
t:要初始化的 tasklet
func:tasklet 的处理函数。
data:要传递给 func 函数的参数
返回值:没有返回值。
也可以使用宏DECLARE_TASKLET来一次性完成tasklet的定义和初始化DECLARE_TASKLET 定义在 include/linux/interrupt.h 文件中,定义如下:
linux内核有中断函数吗
其中 name 为要定义的 tasklet 名字,这个名字就是一个 tasklet_struct 类型的时候变量,func就是 tasklet 的处理函数,data 是传递给 func 函数的参数。
在上半部,也就是中断处理函数中调用 tasklet_schedule 函数就能使 tasklet 在合适的时间运行,tasklet_schedule 函数原型如下:
linux内核有中断函数吗
关于 tasklet 的参考使用示例如下所示:
linux内核有中断函数吗
linux内核有中断函数吗

工作队列

工作队列是另外一种下半部执行方式,工作队列在进程上下文执行,工作队列将要推后的工作交给一个内核线程去执行,因为工作队列工作在进程上下文,因此工作队列允许睡眠或重新调度。因此如果你要推后的工作可以睡眠那么就可以选择工作队列,否则的话就只能选择软中断或 tasklet。
Linux 内核使用 work_struct 结构体表示一个工作,内容如下(省略掉条件编译):
linux内核有中断函数吗
这些工作组织成工作队列,工作队列使用 workqueue_struct 结构体表示,内容如下(省略掉条件编译):
linux内核有中断函数吗
Linux 内核使用工作者线程(worker thread)来处理工作队列中的各个工作,Linux 内核使用worker 结构体表示工作者线程,worker 结构体内容如下:
linux内核有中断函数吗
从示例代码 51.1.2.10 可以看出,每个 worker 都有一个工作队列,工作者线程处理自己工作队列中的所有工作。在实际的驱动开发中,我们只需要定义工作(work_struct)即可,关于工作队列和工作者线程我们基本不用去管。简单创建工作很简单,直接定义一个 work_struct 结构体变量即可,然后使用 INIT_WORK 宏来初始化工作,INIT_WORK 宏定义如下:
linux内核有中断函数吗
linux内核有中断函数吗
linux内核有中断函数吗

1.3 设备树中断信息节点

如果使用设备树的话就需要在设备树中设置好中断属性信息,Linux 内核通过读取设备树中的中断属性信息来配置中断。对于中断控制器而言,设备树绑定信息参考文档Documentation/devicetree/bindings/arm/gic.txt。打开 imx6ull.dtsi 文件,其中的 intc 节点就是I.MX6ULL 的中断控制器节点,节点内容如下所示:
linux内核有中断函数吗
第 2 行,compatible 属性值为“arm,cortex-a7-gic”在 Linux 内核源码中搜索“arm,cortex-a7-gic”即可找到 GIC 中断控制器驱动文件。
第 3 行,#interrupt-cells 和#address-cells、#size-cells 一样。表示此中断控制器下设备的 cells大小,对于设备而言,会使用 interrupts 属性描述中断信息,#interrupt-cells 描述了 interrupts 属性的 cells 大小,也就是一条信息有几个 cells。每个 cells 都是 32 位整形值,对于 ARM 处理的GIC 来说,一共有 3 个 cells,这三个 cells 的含义如下:
第一个 cells:中断类型,0 表示 SPI 中断,1 表示 PPI 中断。
第二个 cells:中断号,对于 SPI 中断来说中断号的范围为 0~987,对于 PPI 中断来说中断号的范围为 0~15。
第三个 cells:标志,bit[3:0]表示中断触发类型,为 1 的时候表示上升沿触发,为 2 的时候表示下降沿触发,为 4 的时候表示高电平触发,为 8 的时候表示低电平触发。bit[15:8]为 PPI 中断的 CPU 掩码。
第 4 行,interrupt-controller 节点为空,表示当前节点是中断控制器。
对于 gpio 来说,gpio 节点也可以作为中断控制器,比如 imx6ull.dtsi 文件中的 gpio5 节点内容如下所示:
linux内核有中断函数吗
第 4 行,interrupts 描述中断源信息,对于 gpio5 来说一共有两条信息,中断类型都是 SPI,触发电平都是 IRQ_TYPE_LEVEL_HIGH。不同之处在于中断源,一个是 74,一个是 75,打开可以打开《IMX6ULL 参考手册》的“Chapter 3 Interrupts and DMA Events”章节,找到表 3-1,有如图 50.1.3.1 所示的内容:
linux内核有中断函数吗
从图 50.1.3.1 可以看出,GPIO5 一共用了 2 个中断号,一个是 74,一个是75。其中 74 对 应 GPIO5_IO00~GPIO5_IO15 这低 16 个 IO,75 对应GPIO5_IO16~GPIOI5_IO31 这高 16 位 IO。 第 8 行,interrupt-controller 表明了 gpio5 节点也是个中断控制器,用于控制 gpio5 所有 IO
的中断。
第 9 行,将#interrupt-cells 修改为 2。
打开 imx6ull-alientek-emmc.dts 文件,找到如下所示内容:
linux内核有中断函数吗
linux内核有中断函数吗
linux内核有中断函数吗

1.4 获取中断号

编写驱动的时候需要用到中断号,我们用到中断号,中断信息已经写到了设备树里面,因此可以通过 irq_of_parse_and_map 函数从 interupts 属性中提取到对应的设备号,函数原型如下:
linux内核有中断函数吗
函数参数和返回值含义如下:
dev:设备节点。
index:索引号,interrupts 属性可能包含多条中断信息,通过 index 指定要获取的信息。
返回值:中断号。
如果使用 GPIO 的话,可以使用 gpio_to_irq 函数来获取 gpio 对应的中断号,函数原型如下:
linux内核有中断函数吗

2.驱动代码

#include <linux/types.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ide.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/errno.h> 
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>

#define IMX6UIRQ_CNT            1               /* 设备号个数 */
#define IMX6UIRQ_NAME           "irqDev"        /* 名字 */
#define KEY0VALUE               0X01            /* KEY0 按键值 */
#define INVAKEY                 0XFF            /* 无效的按键值 */
#define KEY_NUM                 1               /* 按键数量 */

/* 可能会有好多按键,通过结构体数组来描述按键 */
/* 中断 IO 描述结构体 */
struct irq_keydesc {
    int gpio;                               /* gpio */
    int irqnum;                             /* 中断号 */
    unsigned char value;                    /* 按键对应的键值 */
    char name[10];                          /* 名字 */
    irqreturn_t (*handler)(int, void *);    /* 中断服务函数 */
};

/* irq设备结构体 */
struct imx6uirq_dev {
    dev_t               devid;          /* 设备号 */
    struct cdev         cdev;           /* 字符设备 */
    struct class        *class;         /* 类 */
    struct device       *device;        /* 设备 */
    int                 major;          /* 注设备号 */
    int                 minor;          /* 次设备号 */
    struct device_node  *nd;            /* 设备节点 */

    atomic_t            keyvalue;       /* 有效的按键键值 */
    atomic_t            releasekey;     /* 标记是否完成一次完成的按键*/
    struct timer_list   timer;          /* 定义一个定时器*/
    struct irq_keydesc  irqkeydesc[KEY_NUM]; /* 按键描述数组 */
    unsigned char       curkeynum;      /* 当前的按键号 */
};

struct imx6uirq_dev  irqDev; /* 定义LED结构体 */

/* @description : 中断服务函数,开启定时器,延时 10ms,
* 定时器用于按键消抖。
* 两个参数是中断处理函数的必须写法
* @param - irq : 中断号
* @param - dev_id : 设备结构。
* @return : 中断执行结果
*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;

    /* 采用定时器削抖,如果再定时器时间内还是这个值,说明是真的按下了,在定时器中断中处理 */
    /* 这里设置为0是简易处理,因为只有一个按键 */
    /* 有其他按键要再建一个中断处理函数,并把curkeynum改成相应的按键值 */
    /* 注意不能所有按键用一个中断函数,第一是一起按的时候会出错 */
    /* 第二,无法用curkeynum判断使用的是第几个按键 */
    dev->curkeynum = 0;
    /* 传递给定时器的参数,注意要强转,在中断处理函数里面再转回来 */
    dev->timer.data = (volatile long)dev_id;
    /* mod_timer会启动定时器,第二个参数是要修改的超时时间 */
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    return IRQ_RETVAL(IRQ_HANDLED);
}

 /* @description : 定时器服务函数,用于按键消抖,定时器到了以后
* 再次读取按键值,如果按键还是处于按下状态就表示按键有效。
* @param – arg : 设备结构变量
* @return : 无
*/
void timer_function(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; 

    /* 因为只有一个按键,这里是0 */
    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num]; 
    value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */

    if(value == 0){ /* 按下按键 */
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else{ /* 按键松开 */
        /* 这种情况是按下再松开的松开,使用keyValue加上releaseKey */
        /* 没按下的话, releasekey一直为0*/
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ 
    } 
}

/*
* @description : 按键 IO 初始化
* @param : 无
* @return : 无
*/
static int keyio_init(void)
{
    unsigned char i = 0;

    int ret = 0;

    /* 1.获取key节点 */
    irqDev.nd = of_find_node_by_path("/key");
    if (irqDev.nd== NULL){
        printk("key node not find!\r\n");
        return -EINVAL;
    }
    /* 对每个按键都提取 GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        irqDev.irqkeydesc[i].gpio = of_get_named_gpio(irqDev.nd, "key-gpios", i);
        if (irqDev.irqkeydesc[i].gpio < 0) {
            printk("can&#39;t get key%d\r\n", i);
        }
    }

    /* 初始化 key 所使用的 IO,并且设置成中断模式 */
    for (i = 0; i < KEY_NUM; i++) {
        /* 先对每一个IO命名 */
        /* 先对命名清0 */
        memset(irqDev.irqkeydesc[i].name, 0, sizeof(irqDev.irqkeydesc[i].name)); 
        /* 给IO命名 */
        sprintf(irqDev.irqkeydesc[i].name, "KEY%d", i); 
        /* 请求GPIO */
        gpio_request(irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].name);
        
        /* 设置GPIO为输入 */
        gpio_direction_input(irqDev.irqkeydesc[i].gpio); 

        /* 获取中断号,以下为两个方法,都可以获取到 */
        /* 从interrupts属性里面获取 */
        /* 注意i是根据设备树里面设置了多少个就是多少个,都会获取到 */
        /* 下面的方法是通用的获取中断号的函数 */
        irqDev.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqDev.nd, i);
#if 0
        /* 此方法是gpio获取中断号的方法 */
        irqDev.irqkeydesc[i].irqnum = gpio_to_irq(irqDev.irqkeydesc[i].gpio);
#endif
        printk("key%d:gpio=%d, irqnum=%d\r\n", i, irqDev.irqkeydesc[i].gpio,
                                                  irqDev.irqkeydesc[i].irqnum);
    }

    /* 2. 按键中断初始化 */
    /* 设置中断处理函数和按键初始值 */
    /* 因为只有一个key0.,所以这里也没用循环 */
    irqDev.irqkeydesc[0].handler = key0_handler;
    irqDev.irqkeydesc[0].value = KEY0VALUE;
     /* 申请中断 */
    for (i = 0; i < KEY_NUM; i++) {
        /* request_irq参数
         * 中断号,中断函数,中断触发类型,中断名字,传递给中断处理函数的参数(第二个),这里传的结构体
         * */
        ret = request_irq(irqDev.irqkeydesc[i].irqnum, irqDev.irqkeydesc[i].handler,
                        IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                        irqDev.irqkeydesc[i].name, &irqDev);
        if(ret < 0){
            printk("irq %d request failed!\r\n", irqDev.irqkeydesc[i].irqnum);
            return -EFAULT;
        }
    }

    /* 3. 创建定时器 */
    init_timer(&irqDev.timer);
    irqDev.timer.function = timer_function;
    /* 注意下面不能让定时器运行,因为要按下按键之后再运行 */
    /* 启动定时器通过mod_timer启动,通常在初始化阶段的定时器用的是add_timer */
    return 0;
}


static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &irqDev;
    return 0;
}

static int imx6uirq_release(struct inode *inode, struct file *filp)
{
    //struct imx6uirq_dev  *dev = (struct imx6uirq_dev  *)filp->private_data;
    return 0;
}

 /*
* @description : 从设备读取数据
* @param – filp : 要打开的设备文件(文件描述符)
* @param – buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param – offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;     /* 按键值 */
    unsigned char releasekey = 0;   /* 标记是否一次完成 */
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) { /* 有按键按下 */
        if (keyvalue & 0x80) {
            keyvalue &= ~0x80; /* 因为中断中或了一个0x80,这里面去掉0x80 */
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    } else { /* 没有按下 */
        goto data_error;
    }
    return 0;

data_error:
    return -EINVAL;
}

/* 字符设备操作集 */
static const struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .release = imx6uirq_release,
    .read = imx6uirq_read
};

/* 模块入口函数 */
static int __init imx6uirq_init(void)
{
    /* 定义一些所需变量 */
    int ret = 0;

    /* 1. 注册字符设备驱动 */
    irqDev.major = 0;
    if(irqDev.major) {
        irqDev.devid = MKDEV(irqDev.major, 0);
        ret = register_chrdev_region(irqDev.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME );  
    } else {
        alloc_chrdev_region(&irqDev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME );
        irqDev.major = MAJOR(irqDev.devid);
        irqDev.minor = MINOR(irqDev.devid);
    }
    if(ret < 0){
        goto fail_devid;
    }
    printk("Make devid success! \r\n");
    printk("major = %d, minor = %d \r\n", irqDev.major, irqDev.minor);

    /* 2. 初始化cdev */
    irqDev.cdev.owner = THIS_MODULE;
    cdev_init(&irqDev.cdev, &imx6uirq_fops);
    ret = cdev_add(&irqDev.cdev, irqDev.devid, IMX6UIRQ_CNT);
    if (ret < 0){
        goto fail_cdev;
    } else {
        printk("Cdev add sucess! \r\n");
    }

    /* 3. 自动创建设备节点 */
    irqDev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME );
    if(IS_ERR(irqDev.class)) {
        ret = PTR_ERR(irqDev.class);
        goto fail_class;
    } else {
        printk("Class create sucess! \r\n");
    }

    irqDev.device = device_create(irqDev.class, NULL, irqDev.devid, NULL, IMX6UIRQ_NAME );
    if(IS_ERR(irqDev.device)) {
        ret = PTR_ERR(irqDev.device);
        goto fail_device;
    } else {
        printk("Device create sucess! \r\n");
    }

    /* 4.初始化按键 */
    atomic_set(&irqDev.keyvalue, INVAKEY);
    atomic_set(&irqDev.releasekey, 0);
    keyio_init();

    printk("irqDev init! \r\n");
    return 0;

/* 错误处理 */
fail_device:
    class_destroy(irqDev.class);
fail_class:
    cdev_del(&irqDev.cdev);
fail_cdev:
    unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
fail_devid:
    return ret;
}

/* 模块出口函数 */
static void __exit imx6uirq_exit(void)
{
    unsigned int i = 0;
    /* 删除定时器 */
    del_timer_sync(&irqDev.timer); 

    /* 释放中断 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(irqDev.irqkeydesc[i].irqnum, &irqDev);
    }

    /* 1. 释放设备号 */
    cdev_del(&irqDev.cdev);
    /* 2. 注销设备号 */
    unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT);
    /* 3. 摧毁设备 */
    device_destroy(irqDev.class, irqDev.devid);
    /* 4.摧毁类 */
    class_destroy(irqDev.class);


    printk("irqDev exit! \r\n");
}

/* 模块入口和出口注册 */
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Shao Zheming");

3.应用代码

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

/* 
 * argc: 应用程序参数个数
 * argv[]: 参数是什么,具体的参数,说明参数是字符串的形式
 * .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯
 * .chrdevbaseApp /dev/led 0 关灯
 * .chrdevbaseApp /dev/led 1 开灯
 * */


int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }
    
    int fd, ret;
    char *filename;
    unsigned char data;

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("file %s open failed! \r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, &data, sizeof(data));
        if (ret < 0) { /* 数据读取错误或者无效 */
        
        } else { /* 数据读取正确 */
            if (data) /* 读取到数据 */
                printf("key value = %#X\r\n", data);
        }
    }

    close(fd);
    return 0;
}

4.使用tasklet处理中断下半部

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "linux/ioctl.h"

/* 
 * argc: 应用程序参数个数
 * argv[]: 参数是什么,具体的参数,说明参数是字符串的形式
 * .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯
 * .chrdevbaseApp /dev/led 0 关灯
 * .chrdevbaseApp /dev/led 1 开灯
 * */


int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        printf("Error Usage!\r\n");
        return -1;
    }
    
    int fd, ret;
    char *filename;
    unsigned char data;

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("file %s open failed! \r\n", filename);
        return -1;
    }

    while (1) {
        ret = read(fd, &data, sizeof(data));
        if (ret < 0) { /* 数据读取错误或者无效 */
        
        } else { /* 数据读取正确 */
            if (data) /* 读取到数据 */
                printf("key value = %#X\r\n", data);
        }
    }

    close(fd);
    return 0;
}

5. 工作队列处理下半部

开发方式同tasklet
注意work是可以推导出设备dev结构体的,所以一般将work放在dev结构体里

相关推荐:《Linux视频教程

以上是linux内核有中断函数吗的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
什么是linux设备节点什么是linux设备节点Apr 18, 2022 pm 08:10 PM

linux设备节点是应用程序和设备驱动程序沟通的一个桥梁;设备节点被创建在“/dev”,是连接内核与用户层的枢纽,相当于硬盘的inode一样的东西,记录了硬件设备的位置和信息。设备节点使用户可以与内核进行硬件的沟通,读写设备以及其他的操作。

Linux中open和fopen的区别有哪些Linux中open和fopen的区别有哪些Apr 29, 2022 pm 06:57 PM

区别:1、open是UNIX系统调用函数,而fopen是ANSIC标准中的C语言库函数;2、open的移植性没fopen好;3、fopen只能操纵普通正规文件,而open可以操作普通文件、网络套接字等;4、open无缓冲,fopen有缓冲。

linux怎么判断pcre是否安装linux怎么判断pcre是否安装May 09, 2022 pm 04:14 PM

在linux中,可以利用“rpm -qa pcre”命令判断pcre是否安装;rpm命令专门用于管理各项套件,使用该命令后,若结果中出现pcre的版本信息,则表示pcre已经安装,若没有出现版本信息,则表示没有安装pcre。

linux中eof是什么linux中eof是什么May 07, 2022 pm 04:26 PM

在linux中,eof是自定义终止符,是“END Of File”的缩写;因为是自定义的终止符,所以eof就不是固定的,可以随意的设置别名,linux中按“ctrl+d”就代表eof,eof一般会配合cat命令用于多行文本输出,指文件末尾。

linux中什么叫端口映射linux中什么叫端口映射May 09, 2022 pm 01:49 PM

端口映射又称端口转发,是指将外部主机的IP地址的端口映射到Intranet中的一台计算机,当用户访问外网IP的这个端口时,服务器自动将请求映射到对应局域网内部的机器上;可以通过使用动态或固定的公共网络IP路由ADSL宽带路由器来实现。

linux怎么查询mac地址linux怎么查询mac地址Apr 24, 2022 pm 08:01 PM

linux查询mac地址的方法:1、打开系统,在桌面中点击鼠标右键,选择“打开终端”;2、在终端中,执行“ifconfig”命令,查看输出结果,在输出信息第四行中紧跟“ether”单词后的字符串就是mac地址。

手机远程linux工具有哪些手机远程linux工具有哪些Apr 29, 2022 pm 05:30 PM

手机远程linux工具有:1、JuiceSSH,是一款功能强大的安卓SSH客户端应用,可直接对linux服务进行管理;2、Termius,可以利用手机来连接Linux服务器;3、Termux,一个强大的远程终端工具;4、向日葵远程控制等等。

linux中lsb是什么意思linux中lsb是什么意思May 07, 2022 pm 05:08 PM

linux中,lsb是linux标准基础的意思,是“Linux Standards Base”的缩写,是linux标准化领域中的标准;lsb制定了应用程序与运行环境之间的二进制接口,保证了linux发行版与linux应用程序之间的良好结合。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

SublimeText3 英文版

SublimeText3 英文版

推荐:为Win版本,支持代码提示!

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。