找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3757

积分

0

好友

488

主题
发表于 2 小时前 | 查看: 3| 回复: 0

1. 概述

在驱动开发中,当我们需要实时响应外部硬件事件时,你会选择哪种方式?比如:

  • 用户按下了某个物理按键。
  • 传感器发出了数据就绪信号。
  • 硬件模块完成了某项传输任务。

除了简单但低效的轮询(Polling),我们还有另一种更强大、更高效的武器——中断(Interrupt)。本文将以GPIO按键为例,带你从零开始,一步一步构建一个完整的中断驱动程序,掌握从设备树描述到驱动代码实现的全流程。

中断本质上是一种异步触发机制。它允许CPU暂时中断当前正在执行的任务,优先去处理一个更高优先级的突发硬件事件。整个过程涉及到“保存现场”和“恢复现场”,确保处理完中断后,CPU能无缝衔接回原来的工作。

中断处理流程:
CPU中断处理流程示意图

在Linux内核驱动开发中,我们需要熟悉以下几个关键的中断概念:

概念 全称 含义
IRQ Number Interrupt Request Number 中断号,内核识别中断的唯一ID,由硬件连接或设备树映射生成。
ISR Interrupt Service Routine 中断服务程序,中断发生时被执行的驱动注册函数。
顶半部 / 底半部 Top/Bottom Half 顶半部(ISR)负责快速响应硬件,底半部(Tasklet/Workqueue)负责处理耗时逻辑。
触发方式 Trigger Type 上升沿(Rising)、下降沿(Falling)、高/低电平(Level)。

⚠️ 注意
在中断上下文(Interrupt Context)中,绝对不能执行休眠、调度或耗时的操作(比如 msleepmutex_lock 等)。记住,快进快出是中断顶半部设计的铁律!

2. 设备树

要在驱动中使用中断,我们首先需要在设备树中清晰地描述硬件连接状态。以下是一个GPIO按键的设备树节点示例:

key_input_node {
    compatible = "abc,key-input";
    /* 指定 GPIO 引脚,GPIO_ACTIVE_LOW 表示按下时物理电平为低 */
    gpios = <&gpio2 4 GPIO_ACTIVE_LOW>;
    /* 描述中断信息,interrupt-parent 指定中断控制器 */
    interrupt-parent = <&gpio2>;
    /* 4 是 GPIO 引脚号,IRQ_TYPE_EDGE_FALLING 表示下降沿触发 */
    interrupts = <4 IRQ_TYPE_EDGE_FALLING>;
};

说明
gpios 属性是驱动获取GPIO对象的关键。而 interrupts 属性则用于显式声明中断。实际上,对于GPIO中断,内核提供了 gpiod_to_irq() 接口,因此设备树中可以省略 interrupts 属性,直接通过 gpios 属性进行映射,这种方式更为常用和简洁。

3. 驱动代码

我们将遵循标准的Platform驱动框架,并使用 devm_ 系列函数来管理资源。这样做的好处是,在驱动卸载时,内核会自动帮我们释放相关资源,避免了手动管理的繁琐和可能的内存泄漏。

驱动初始化流程:
GPIO中断驱动Probe流程图

3.1 核心实现

#include <linux/module.h>
#include <linux/platform_device.h>
/* 中断相关接口 */
#include <linux/interrupt.h>
/* GPIO descriptor(gpiod_)接口 */
#include <linux/gpio/consumer.h>

/* 中断服务函数 (ISR) - 顶半部
 * 注意:这里不能执行耗时操作!
 */
static irqreturn_t key_irq_handler(int irq, void *dev_id)
{
    /* 在实际项目中,可以根据需要在这里唤醒底半部 */
    pr_info("Key Pressed! IRQ Number: %d\n", irq);

    /* 告诉内核中断已被正确处理 */
    return IRQ_HANDLED;
}

static int key_input_probe(struct platform_device *pdev)
{
    int irq;
    int ret;
    struct gpio_desc *key_gpio;
    struct device *dev = &pdev->dev;

    pr_info("Key Input Driver Probing...\n");

    /* 1. 获取 GPIO 对象 (根据 DTS 中的 compatible 和 gpios 匹配) */
    key_gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
    if (IS_ERR(key_gpio)) {
        dev_err(dev, "Failed to get GPIO\n");
        return PTR_ERR(key_gpio);
    }

    /* 2. 将 GPIO 转换为对应的 IRQ 号 */
    irq = gpiod_to_irq(key_gpio);
    if (irq < 0) {
        dev_err(dev, "Failed to map GPIO to IRQ\n");
        return irq;
    }
    pr_info("Mapped GPIO to IRQ: %d\n", irq);

    /* 3. 申请中断
     * 参数说明:
     * - dev: 设备对象
     * - irq: 中断号
     * - handler: 中断服务函数
     * - flags: 触发标志
     * - name: /proc/interrupts 节点中显示的名字
     * - dev_id: 传递给 ISR 的私有数据 (通常传 device 结构体,这里演示用 NULL)
     */
    ret = devm_request_irq(dev, irq, key_irq_handler,
                   IRQF_TRIGGER_FALLING, "abc_key_irq", NULL);
    if (ret) {
        dev_err(dev, "Failed to request IRQ: %d\n", ret);
        return ret;
    }

    return 0;
}

3.2 驱动注册

static const struct of_device_id key_input_of_match[] = {
    { .compatible = "abc,key-input" },
    { }
};
MODULE_DEVICE_TABLE(of, key_input_of_match);

static struct platform_driver key_input_driver = {
    .probe = key_input_probe,
    .driver = {
        .name = "key_input",
        .of_match_table = key_input_of_match,
    },
};

module_platform_driver(key_input_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("dump linux");
MODULE_DESCRIPTION("Simple GPIO Interrupt Driver");

4. 测试与验证

驱动代码编写完成后,下一步就是加载测试,验证中断是否成功注册并正常工作。

4.1 加载驱动

sudo insmod key_input.ko

如果 dmesg 中看到 Key Input Driver Probing...Mapped GPIO to IRQ: ... 等成功信息,说明驱动加载成功。

4.2 查看系统中断表

/proc/interrupts 是调试中断最有效的工具之一,它统计了内核中所有已注册中断的信息。

cat /proc/interrupts | grep abc_key_irq

输出示例:

# 149:软件中断号,0:在某个 CPU 上的触发次数,Edge:触发方式
149: 0 0 0 0 0 0 0 0  GICv3 4 Edge  abc_key_irq

4.3 触发测试

  1. 执行 cat /proc/interrupts | grep abc_key_irq 记录下当前触发次数。
  2. 按下对应的GPIO按键。
  3. 查看 dmesg,你应该能看到 Key Pressed! IRQ Number: ... 的输出。
  4. 再次执行 cat /proc/interrupts | grep abc_key_irq,对比观察中断触发次数是否增加了。

5. 调试

在实际开发中,你可能会遇到一些问题。下面这张表汇总了一些常见现象和排查思路:

现象 可能原因 排查手段
Probe失败,gpiod_to_irq() < 0 GPIO控制器不支持中断或设备树配置错误。 检查芯片手册GPIO相关说明;检查设备树interrupt-parent属性。
驱动注册成功,但按键按下无反应 1. 没有触发电平变化。
2. 引脚复用未正确配置。
1. 测量物理引脚电平状态。
2. 检查设备树pinctrl设置。
按一次按键,中断触发很多次 按键抖动。 硬件上并联电容或软件去抖(例如在中断处理中增加去抖逻辑)。
系统卡死 中断顶半部中使用了导致阻塞的函数。 严禁在中断顶半部中睡眠,检查ISR中是否调用了msleep, mutex_lock等可能导致调度的函数。

6. 总结

中断是驱动开发中的核心机制,相比于轮询,它能极大提升系统的实时性和响应效率。几乎所有的外设交互都离不开中断的参与,因此,掌握中断驱动开发是内核驱动开发者的必修课。

本文通过一个GPIO按键的实例,详细展示了从设备树配置、驱动代码编写到测试调试的全过程。关键点在于理解“快进快出”的顶半部设计原则,以及何时、如何将耗时任务合理地调度到底半部(如Tasklet或Workqueue)中去执行。希望这篇实践指南能帮助你在Linux内核驱动的道路上走得更稳、更远。如果你想深入学习更多网络/系统相关的内核知识,欢迎来云栈社区交流探讨。




上一篇:AI争议与投资周报:工程师质疑龙虾暴利论,GitHub趋势榜首开源项目获陈天桥3000万投资
下一篇:Linux字符设备驱动从零实现:掌握/dev节点创建与读写
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-10 11:23 , Processed in 0.582023 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表