1. 驱动中断
中断是指 CPU 在执行程序的过程中,出现了某些突发事件急待处理,CPU 必须暂停当前程序的执行,转去处理突发事件,处理完毕后又返回原程序被中断的位置继续执行。中断可以分为:内部中断和外部中断,内部中断来源于 CPU 内部,外部中断来源于 CPU 外部。
/proc/interrupts 作用:处理异步事件,随机事件,非顺序逻辑情况
2.相关函数接口

1.request_irq------注册中断处理程序
函数原型:
c
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);
参数说明:
- irq:要申请的中断号。
- handler:中断处理函数指针,中断触发时被调用。
- flags :中断标志,如
IRQF_SHARED表示共享中断。 - name :中断名称,显示在
/proc/interrupts中。 - dev:传递给中断处理函数的设备标识,共享中断时用于区分。
使用实例:
c
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
printk(KERN_INFO "IRQ %d triggered\n", irq);
return IRQ_HANDLED;
}
static int __init my_init(void)
{
int ret;
ret = request_irq(11, my_irq_handler, IRQF_SHARED, "my_device", &my_dev);
if (ret) {
printk(KERN_ERR "request_irq failed: %d\n", ret);
return ret;
}
return 0;
}
2.free_irq------释放已注册中断资源
函数原型:
c
void free_irq(unsigned int irq, void *dev_id);
参数说明:
- irq:要释放的中断号。
- dev_id :与
request_irq中传入的dev参数一致,用于匹配释放对应的中断。
使用实例:
c
static void __exit my_exit(void)
{
free_irq(11, &my_dev);
printk(KERN_INFO "IRQ 11 released\n");
}
3.disable_irq------禁止中断
函数原型:
c
void disable_irq(unsigned int irq);
参数说明:
- irq:要禁止的中断号。
使用实例:
c
disable_irq(11); /* 禁止 11 号中断,中断处理函数不再被调用 */
4.enable_irq------使能中断
函数原型:
c
void enable_irq(unsigned int irq);
参数说明:
- irq:要使能的中断号。
使用实例:
c
enable_irq(11); /* 重新使能 11 号中断 */
综合使用示例:
c
static int __init my_init(void)
{
int ret;
/* 1. 注册中断处理程序 */
ret = request_irq(11, my_irq_handler, IRQF_SHARED, "my_device", &my_dev);
if (ret) {
printk(KERN_ERR "request_irq failed\n");
return ret;
}
/* 2. 在临界区前禁止中断 */
disable_irq(11);
/* 3. 临界区结束后使能中断 */
enable_irq(11);
return 0;
}
static void __exit my_exit(void)
{
/* 4. 释放中断资源 */
free_irq(11, &my_dev);
}
了解完成相关函数我们就需要对我们的驱动进行操作
操作步骤
1.适配设备树
cs
putekey {
#address-cells = <1>;
#size-cells = <1>;
compatible = "pute-key";
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_key>;
key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY-gpio就是我们需要记住的一个标签,后面再=在驱动中我们会按照这个标签去寻找设备地址*/
interrupt-parent = <&gpio1>;
interrupts = <18 IRQ_TYPE_EDGE_RISING>;
};
interrupt-parent,指定父中断。当GPIO1_18 引脚电平发生变化,外部中断到来,先找GPIO1 控制 器。
interrupts,需要描述引脚号,上升触发,下降触发,双边沿触发
在驱动代码中
1.通过of_irq_get/gpio_to_irq ,从设备树中 ,获得中断号。 删除输入输出模式,现在gpio工作在中断 模式
2..调用 request_irq();,申请中断,填入中断处理函数(重要,中断到来后,需要做的事情)
3..编写中断处理函数,需要注意返回值。
4.修改app ,因为main函数while(1){}, 在中断处理函数中,加入输出。
5.继续改进,希望阻塞方式读取。需要加入阻塞功能 . 推荐操作 中断方式+ 等待队列

回顾步骤:
-
设备树 在 设备的节点中,加入属性 interrupt-parent,interrupts
-
pinctrl
-
编写设备节点 引用 pinctrl,引用gpio,引用中断 状态-》 okay
-
misc_register
-
of_函数获得设备树节点
-
of获得gpio
-
向系统申请gpio 资源。
-
设置输入/输出
-
of_irq_get,通过gpiono 找到对应的终端号
-
request_irq ,申请终端
3.等待队列(阻塞io)
Linux内核中可以通过等待队列来实现阻塞进程的唤醒。

使用步骤: 需要先阅读下 wait_event_interruptible,
1.定义等待队列 DECLARE_WAIT_QUEUE_HEAD , 并设置一个 int flag =0;(相当于条件变量)
2.初始化等待队列 init_waitqueue_head
3.应该注册中断处理,可以唤醒等待的事件
4.等待事情触发 ,先flag=0, 在调用 wait_event_interruptible, ,一般是在 就是read函数中 需要阻塞的地方。 按键的话,
5.中断到来,在中断处理函数中调用 wake_up_interruptible,调用函数前flag=1 。唤醒阻塞事件。 注意这个flag 的标志为,置0 ,置1的时机。
4.linux 中断子系统
中断会打断内核进程正常调度和运行,所以系统对高吞吐量的追求势必要求中断服务尽量短小精悍,快 进快出,所以Linux中断处理程序架构为:

为了在中断执行时间尽量短和中断处理需完成的工作尽量大之间找到一个平衡点, Linux将中断处理程序 分解为两个半部: 顶半部(Top Half) 和底半部(BottomHalf)
上半部:也称为中断顶半部,用于完成尽量少的比较紧急的功能,通常被设置为不可被新中断打断。
下半部:也称为中断底半部,用来完成中断事件绝大多数任务,可以被新的中断打断,这也是底半部和 顶半部的最 大不同,因为顶半部往往被设计成不可中断。底半部相对来说并不是非常紧急,而且相对比 较耗时的操作
上半部的使用场景:
要处理的内容不希望被其他中断打断
要处理的任务对时间敏感
要处理的任务与硬件有关
下半部的使用场景:完成并不是非常紧急的耗时的任务
可以被打断
1.软中断
资源比较优先,用于系统级的任务
软中断是一种传统的底半部处理机制,它的执行时机通常是顶半部返回的时候
软中断和tasklet运行于中断上下文,不允许睡眠
2.tasklet
.taskle是利用软终端实现的,软终端调度tasklet;
.tasklet不允许睡眠
3.工作队列
工作队列运用于进程上下文,允许睡眠

① 顶半部(Top Half)
就是handler()处理函数中,硬终断上下文
特点:最快,最急,不能休眠
② 软中断(Softirq)
内核最底层底半部 ,属于底半部,工作在软中断上下文。执行的时机,顶半部返回的时候(ISR 返回 的时候
③ tasklet
基于软中断封装,专门给驱动用
最简单、最安全、最常用
④ workqueue(工作队列)
运行在内核线程,工作在底半部,内核线程上下文
需要延时 需要分配内存 需要大量计算 需要 I2C/SPI 等总线操作