Linux 中断驱动程序--按键中断驱动

📅 发布时间:2026/7/9 1:09:39 👁️ 浏览次数:
Linux 中断驱动程序--按键中断驱动
一、为什么需要中断驱动在嵌入式 Linux 开发中按键是最常见的输入设备之一。如果使用轮询Polling方式检测按键状态CPU 需要不停地读取 GPIO 电平这会极大地浪费系统资源导致 CPU 无法处理其他任务。中断Interrupt机制则是解决这一问题的完美方案被动响应CPU 仅在按键按下或抬起时才收到通知并进行处理。高实时性硬件电平变化触发中断系统能立即响应。低功耗CPU 大部分时间处于空闲或处理其他任务的状态无需忙等。二、驱动核心结构1.代码结构分析内核驱动 负责 GPIO 初始化、中断申请、数据读取逻辑。应用测试 负责打开设备节点并调用 read() 函数获取按键状态。设备树节点描述硬件资源GPIO 编号、中断触发方式。2. 工作流程图初始化驱动加载解析设备树申请 GPIO 和中断。阻塞等待用户程序调用 read()驱动将其加入等待队列并休眠。中断触发按键按下硬件产生中断执行中断处理函数。唤醒进程中断处理函数唤醒等待队列中的进程read() 返回按键值。三、代码实现详解1.中断处理函数 (key_irq_handler)static irqreturn_t key_irq_handler(int irq, void * dev) { int arg *(int *)dev; // 获取传入的参数 if(100 ! arg) return IRQ_NONE; // 简单的参数校验 condition 1; // 核心逻辑设置条件为真 wake_up_interruptible(wq); // 唤醒在 wq 队列上睡眠的进程 printk(irq %d dev %d\n, irq, arg); return IRQ_HANDLED; }中断上下文原则ISR 必须快速执行。它只做最必要的事情——设置标志位和唤醒进程避免任何可能引起休眠的操作如copy_to_user。唤醒机制wake_up_interruptible是与wait_event_interruptible完美配对的函数它能将处于可中断睡眠状态的进程标记为可运行由调度器决定何时恢复其执行。2.. 文件操作读取函数 (read)这是驱动与应用交互的接口。static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff) { int ret 0; int status 0; condition 0; // 1. 重置条件标志 wait_event_interruptible(wq, condition); // 2. 进程在此处睡眠直到 condition ! 0 status 1; // 3. 被唤醒后执行 ret copy_to_user(buf, status, sizeof(status)); // 4. 将数据传回用户空间 return sizeof(status); }阻塞式设计wait_event_interruptible 宏实现了优雅的阻塞逻辑。当条件不满足时进程主动让出 CPU一旦被唤醒它会自动重新检查条件并继续执行。2. 用户/内核空间隔离必须使用 copy_to_user 将内核数据安全地拷贝到用户空间缓冲区直接赋值会导致内核崩溃Oops。3.平台驱动探针 (probe)static int probe(struct platform_device * pdev) { // 1. 查找设备树节点 pdts of_find_node_by_path(/ptkey); // 2. 获取 GPIO 和 中断号 key_gpio of_get_named_gpio(pdts, ptkey-gpio, 0); key_irq irq_of_parse_and_map(pdts, 0); // 3. 申请中断 request_irq(key_irq, key_irq_handler, IRQF_TRIGGER_FALLING, key0_irq, arg); // 4. 初始化等待队列 init_waitqueue_head(wq); return 0; }设备树驱动模型通过 of_find_node_by_path 和 of_get_named_gpio 等 API驱动代码与具体的硬件地址解耦使得同一份驱动可以适配不同的硬件平台。资源生命周期管理在 probe 中申请的资源GPIO、IRQ必须在对应的 remove 函数中正确释放否则会造成资源泄漏。4.模块初始化与退出static int __init key_driver_init(void) { platform_driver_register(pdrv); // 注册平台驱动 } static void __exit key_driver_exit(void) { platform_driver_unregister(pdrv); // 注销平台驱动 }模块入口/出口__init 和 __exit 宏告诉内核这些函数只在模块加载/卸载时使用之后其占用的内存可以被回收优化了内核内存。平台总线注册通过 platform_driver_register 将驱动注册到 Linux 的 Platform 总线上使其能够被设备树中的匹配节点发现并调用 probe。四、应用测试key_app.c1.主函数逻辑int main(int argc, const char *argv[]) { int fd open(/dev/key, O_RDWR); // 打开设备节点 int status 0; while(1) { int ret read(fd, status, sizeof status); // 调用驱动的 read 函数 printf(ret %d status %d\n, ret, status); } close(fd); return 0; }标准文件操作应用程序完全将设备视为一个普通文件通过 open、read、close 这些 POSIX 标准接口进行交互体现了 Linux “一切皆文件” 的设计哲学。同步阻塞行为read 调用在此处是同步且阻塞的。程序会一直停在此行直到内核驱动有数据返回即按键被按下这简化了应用层的逻辑无需自行管理轮询或异步通知。五、 关键技术点总结非轮询机制相比传统的 while 循环读取 GPIO 电平这种 中断 等待队列 的方式更加高效不占用 CPU 资源。设备树解耦驱动代码不包含具体的硬件地址而是通过设备树节点/ptkey动态获取 GPIO 和 IRQ提高了驱动的通用性。阻塞 I/Owait_event_interruptible 和 wake_up_interruptible 是成对使用的宏/函数是实现阻塞型驱动的标准范式。参数传递代码中使用 arg 作为中断共享参数虽然本例中仅用于演示但在多设备驱动中通常会传递指向设备私有数据结构struct key_dev的指针。