Linux驱动之中断

自己从事Android应用层开发,接触并参与部分Framework开发,自学开发板,仅仅从个人角度来看待中断并总结。

文章目录


资料参考

迅为资料-Linux驱动

一)中断概念

中断是指在 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);

  • my_tasklet 是 tasklet 的名称,

  • my_tasklet_handler 是 tasklet 的处理函数,

  • 0是传递给处理函数的参数。

    tasklet_init 动态初始化

 // 声明 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值