自己从事Android应用层开发,接触并参与部分Framework开发,自学开发板,仅仅从个人角度来看待中断并总结。
资料参考
一)中断概念
中断是指在 CPU 正常运行期间,由外部或内部事件引起的一种机制。当中断发生时,CPU
会停止当前正在执行的程序,并转而执行触发该中断的中断处理程序。处理完中断处理程序后,CPU 会返回到中断发生的地方,继续执行被中断的程序。中断机制允许 CPU 在实时响应外部或内部事件的同时,保持对其他任务的处理能力。
二)中断基本api
中断申请
request_irq
request_irq 函数是在 Linux 内核中用于注册中断处理程序的函数。它用于请求一个中断号
(IRQ number)并将一个中断处理程序与该中断关联起来
gpio_to_irq
gpio_to_irq 函数用于将 GPIO 引脚的编号(GPIO pin number)转换为对应的中断请求号
(interrupt request number)
free_irq
free_irq 函数用于释放之前通过 request_irq 函数注册的中断处理程序。它的作用是取消对
中断的注册并释放相关的系统资源。
irqreturn_t handler(int irq, void *dev_id)
中断服务:中断处理程序是在中断事件发生时自动调用的函数。它负责处理与中断相关的操作,例如 读取数据、清除中断标志、更新状态等
中断服务
irqreturn_t handler(int irq, void *dev_id)
中断处理程序是在中断事件发生时自动调用的函数。它负责处理与中断相关的操作,例如
读取数据、清除中断标志、更新状态等
中断申请实验
我们先不从概念的角度、死磕api 方法参数的角度来理解中断,我们先以一个程序实验来看看中断到底怎么回事。
- 中断服务:如果中断产生,不就是需要有个回调来处理中断吗? 这个就是中断服务
- 申请、注册:要想中断服务能够正常产生,总要告诉系统我来监听你吧,这个就是中断申请
- 中断号:要想实现申请注册,总要知道你监听什么吧,硬件相关的都是引脚编号,这里需要用到的是中断号。那就把引脚编号转换成中断号,我告诉系统你,我要监听哪一个中断,对应的就是中断号,再底层就是引脚号。
- 释放:资源释放怎么办呢,在驱动卸载的时候,需要释放资源。
简单程序如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#define GPIO_PIN 101
// 中断处理函数:中断服务
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
printk(KERN_INFO "Interrupt occurred on GPIO %d\n", GPIO_PIN);
printk(KERN_INFO "This is irq_handler\n");
return IRQ_HANDLED;
}
static int __init interrupt_init(void)
{
int irq_num;
printk(KERN_INFO "Initializing GPIO Interrupt Driver\n");
// 将GPIO引脚映射到中断号
irq_num = gpio_to_irq(GPIO_PIN);
printk(KERN_INFO "GPIO %d mapped to IRQ %d\n", GPIO_PIN, irq_num);
// 请求中断
if (request_irq(irq_num, gpio_irq_handler, IRQF_TRIGGER_RISING, "irq_test", NULL) != 0){
printk(KERN_ERR "Failed to request IRQ %d\n", irq_num);
// 请求中断失败,释放GPIO引脚
gpio_free(GPIO_PIN);
return -ENODEV;
}
return 0;
}
static void __exit interrupt_exit(void)
{
int irq_num = gpio_to_irq(GPIO_PIN);
// 释放中断
free_irq(irq_num, NULL);
printk(KERN_INFO "GPIO Interrupt Driver exited successfully\n");
}
module_init(interrupt_init);
module_exit(interrupt_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wang fang chen");
坑点
- 引脚号转变成中断号:gpio_to_irq(GPIO_PIN),引脚号怎么算呢? 这个里面需要看电路图上查看TP的引脚
- kernel 无法打印日志,重置日志等级即可:echo 7 4 1 7 > /proc/sys/kernel/printk
- 我们的实验是在rk3568 开发板上面做屏幕触摸实验,编译固件时候先要把所有屏参全部去掉
三)中断下文 tasklet
概念
tasklet 是一种特殊的软中断机制,被广泛用于处理中断下文相关的任务。
它是一种常见且有效的方法,在多核处理系统上可以避免并发问题。Tasklet 绑定的函数在同一时间只能在一个 CPU 上运行,因此不会出现并发冲突。然而,需要注意的是,tasklet 绑定的函数中不能调用可能导致休眠的函数,否则可能引起内核异常。
函数
DECLARE_TASKLET 静态初始化
静态初始化函数 DECLARE_TASKLET(my_tasklet, my_tasklet_handler, 0);
// 声明 tasklet 结构体
static struct tasklet_struct my_tasklet;
// 初始化 tasklet
tasklet_init(&my_tasklet, my_tasklet_handler, 0);
tasklet_enable
tasklet_enable 函数来使能(启用)一个已经初始化的 tasklet
tasklet_disabled
tasklet_disabled 函数来关闭一个已经初始化的 tasklet
tasklet_schedule
tasklet_schedule 函数来调度(触发)一个已经初始化的 tasklet
执行。
tasklet_kill
tasklet_kill 函数来销毁一个已经初始化的 tasklet,释放相关资源
tasklet 实验
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
// #include <linux/delay.h>
int irq;
struct tasklet_struct mytasklet;
// 定义tasklet处理函数
void mytasklet_func(unsigned long data)
{
printk("data is %ld\n", data);
// msleep(3000);
}
// 中断处理函数
irqreturn_t test_interrupt(int irq, void *args)
{
printk("This id test_interrupt\n");
tasklet_schedule(&mytasklet); // 调度tasklet执行
return IRQ_RETVAL(IRQ_HANDLED);
}
// 模块初始化函数
static int interrupt_irq_init(void)
{
int ret;
irq = gpio_to_irq(101); // 将GPIO转换为中断号
printk("irq is %d\n", irq);
// 请求中断
ret = request_irq(irq, test_interrupt, IRQF_TRIGGER_RISING, "test", NULL);
if (ret < 0)
{
printk("request_irq is error\n");
return -1;
}
// 初始化tasklet
tasklet_init(&mytasklet, mytasklet_func, 1);
tasklet_enable(&mytasklet); // 使能tasklet(可选)
return 0;
}
// 模块退出函数
static void interrupt_irq_exit(void)
{
tasklet_disable(&mytasklet);
free_irq(irq, NULL);
tasklet_kill(&mytasklet); // 销毁tasklet
printk("bye bye\n");
}
module_init(interrupt_irq_init); // 指定模块的初始化函数
module_exit(interrupt_irq_exit); // 指定模块的退出函数
MODULE_LICENSE("GPL"); // 模块使用的许可证
MODULE_AUTHOR("wang fang chen "); // 模块的作者
实验总结
tasklet 用法同中断基本实验的注意点
- tasklet 基本的api 注册、使能、调度、停止使能、消除
- tasklet 用法,就是在中断注册服务中让tasklet 调度。
- task 调度之前必须先注册并开启使能
- 调度方法执行后,执行的就是tasklet 注册时候关联的tasklet回调方法
四) 软中断
概念
打开 Linux 源码 linux_sdk/kernel/include/linux/interrupt.h 文件,如下所示
enum
{
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
IRQ_POLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the numbering. Sigh! */
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
};
以上代码定义了一个枚举类型,用于标识软中断的不同类型或优先级。每个枚举常量对应一个特定的软中断类型。以下是每个枚举常量的含义
HI_SOFTIRQ:高优先级软中断
TIMER