主要目标降低cpu占用率今天来讲一下Linux驱动开发中的中断和等待队列。这两个东西分别是干嘛的呢首先学过mcu的朋友对中断一定不会陌生它的作用就是当中断触发时打断cpu当前执行的任务来执行中断的操作。当中断任务执行结束后cpu再继续执行之前的任务。那等待队列是干啥的呢类似于mcu的信号量等待一个触发的条件再让cpu来执行。但这俩又不完全一样它们在“同步”和“阻塞”的目的上是相似的。MCU 中的信号量/标志位轮询在裸机或 RTOS 中你通常设置一个全局变量 key_pressed 0。主循环不断检查 if (key_pressed 1)。如果为 0主循环继续跑其他代码或进入低功耗模式WFI。这本质上是一种主动查询或基于标志位的协作式调度。Linux 等待队列它是一种内核级的进程休眠机制。当 read 发现没数据时它不是简单地返回或继续跑循环而是告诉内核“把我这个进程挂起Remove from CPU scheduler放到这个队列里睡觉直到有人叫醒我”。核心区别主动性 vs 被动性MCU 信号量通常需要你在主循环里主动去查除非你用 RTOS 的任务挂起 API那才更像等待队列。Linux 等待队列是被动休眠。进程调用 wait_event 后它就彻底从 CPU 调度器中消失了完全不占用 CPU 时间片直到被唤醒。上下文切换MCU 中改变标志位通常不涉及复杂的上下文保存/恢复除非触发任务切换。Linux 中wake_up 会触发内核调度器进行完整的进程上下文切换保存当前寄存器、加载被唤醒进程的寄存器等开销比 MCU 大但功能更强支持多进程、权限管理、信号处理等。结论你可以把等待队列理解为 “RTOS 中带有条件判断的任务挂起Task Suspend with Condition”而不是简单的二进制信号量。它让进程在没有数据时“彻底消失”从而极大降低 CPU 负载。知道了这两个东西分别是干啥的我们能用他们来做什么呢降低cpu的占用率。降低 CPU 占用依靠等待队列让无事件时的进程彻底休眠不再消耗 CPU 周期依靠中断让 CPU 仅在硬件事件发生时才介入以按键为例我们设置一个检测按键上升沿的中断然后在read函数里设置好等待队列条件为假让等待队列挂起当按键被按下触发中断中断服务函数被执行此时等待队列条件为真等待队列被释放释放后继续执行read函数里操作把读取到的值传递到应用层。在上述过程中cpu只有在中断触发后才分配时间片给read函数其他时间都不再执行read函数这就会大幅降低cpu的占用率。在这个过程中cpu在read函数中遇到等待队列函数后就不再继续执行read函数了但是如何让等待队列释放唤醒read函数继续执行呢原理是什么也就是说既然 read 函数在休眠没分到时间片哪是谁检测到按键并告诉 CPU 执行 read这是初学者最大的误区并不是“谁告诉 CPU 去执行 read 函数”而是“硬件中断打断了 CPU 当前的工作内核在中断处理程序中唤醒了休眠的 read 进程”。这里有一个关键的硬件机制GPIO 控制器的中断功能。详细流程拆解休眠状态Read 进程应用层调用 read()。驱动发现没按键按下调用 wait_event_interruptible()。关键点此时read 函数的代码执行暂停了。这个进程的状态变为 TASK_INTERRUPTIBLE可中断休眠。CPU 在干嘛 CPU 此时正在运行其他进程比如桌面界面、网络服务、或者空闲任务 idle。它完全不知道 read 的存在也不分配时间片给 read。硬件事件发生按键按下你按下了按键GPIO 引脚电平发生变化例如从低变高。谁检测到的 不是 CPU也不是驱动程序而是 SoC 内部的 GPIO 控制器硬件电路。GPIO 控制器内部有一个比较器或边沿检测电路它独立于 CPU 核心运行。一旦检测到电平跳变它会向 CPU 的中断控制器如 GIC发送一个电信号Interrupt Request, IRQ。中断响应CPU 被硬件打断CPU 收到中断信号后无论它当前正在运行什么代码哪怕是空闲任务都会立即暂停当前工作。CPU 硬件自动保存当前上下文跳转到预先注册好的中断处理函数ISR, Interrupt Service Routine。注意此时执行的代码是驱动中注册的 irq_handler而不是 read 函数。中断处理与唤醒关键步骤在 irq_handler 中驱动代码执行以下操作读取 GPIO 状态确认是按键按下可能包含消抖逻辑。将键值存入全局缓冲区。设置条件标志 data_available 1。调用 wake_up_interruptible(my_wait_queue)。wake_up 做了什么 它通知内核调度器“等待在 my_wait_queue 上的那个进程也就是之前休眠的 read 进程现在条件满足了把它状态改为 TASK_RUNNING就绪态放入调度队列。”重要wake_up 并没有立即执行 read 函数的剩余代码。它只是把 read 进程标记为“可以运行了”。进程恢复与数据返回中断处理函数执行完毕CPU 返回到刚才被打断的地方可能是空闲任务或其他进程继续运行。在下一次内核调度时机例如中断返回时、时间片用完时调度器发现 read 进程现在是“就绪态”且优先级合适。调度器切换上下文恢复 read 函数的执行。wait_event_interruptible 宏检测到 data_available 为真于是从休眠中返回。read 函数继续执行后续代码copy_to_user 将数据发给应用层然后返回。总结回答上面的疑问是谁检测到按键是 GPIO 硬件控制器。它独立于 CPU 软件运行专门监控引脚电平。是谁告诉 CPU要去执行中断服务函数了是 中断控制器GIC 通过硬件电信号通知 CPU 核心。CPU 去执行 read 函数了吗没有直接执行 read。 CPU 先去执行中断服务函数ISR。在 ISR 中内核通过 wake_up 机制唤醒了休眠的 read 进程。随后由内核调度器决定何时恢复 read 进程的运行。形象比喻Read 进程像一个在候诊室睡觉的病人休眠。GPIO 硬件像门口的门铃按钮。中断病人按铃硬件触发护士中断处理程序听到铃声。Wake_up护士走到候诊室把病人的状态牌从“睡觉”翻成“待诊”唤醒但护士不会立刻给病人看病而是回到前台。调度器医生CPU看完手头的急诊中断处理完查看待诊列表发现这个病人醒了于是叫他的号上下文切换病人开始看病read 函数继续执行并返回数据。在这个过程中病人在睡觉时完全不占用医生的时间CPU 资源只有按铃和处理的那一瞬间需要医生介入。这就是为什么它能降低 CPU 占用率。代码示例此示例中的按键为双边沿触发#include linux/kernel.h#include linux/module.h#include linux/fs.h#include linux/gpio.h#include linux/uaccess.h#include linux/platform_device.h#include linux/of.h#include linux/of_gpio.h#include linux/miscdevice.h//1、添加头文件#include linux/irq.h#include linux/interrupt.h//9、添加等待队列头文件#include linux/wait.h#include linux/sched.h//10、申请等待队列列头DECLARE_WAIT_QUEUE_HEAD(mywait);//杂项设备开发框架linux设备树和平台总线开发框架struct miscdevice misc;struct file_operations ops;int key1,key2;//3、定义中断号int irq1;int irq2;int value;int ret;//12、定义等待队列条件int wait_flag;// 5、定义中断服务函数irqreturn_t myirqfunc(int irq, void *arg){if (irq irq1){value 1;}else if (irq irq2){value 2;}//进入中断说明按键完成一次按下// value 1; //先赋值再释放//14、等待队列条件置为真wait_flag 1;//15、等待队列释放wake_up_interruptible(mywait);return 0;}//1、定义open文件接口int myopen(struct inode *inode,struct file *file){gpio_request(key1,key1);gpio_direction_input(key1);gpio_request(key2,key2);gpio_direction_input(key2);// 2、获取中断号// irq1 gpio_to_irq(key1);// 使能中断 --- 对于当前板子不需要// 4、注册中断服务函数ret request_irq(irq1,myirqfunc,IRQ_TYPE_EDGE_BOTH,irq1,NULL);ret request_irq(irq2,myirqfunc,IRQ_TYPE_EDGE_BOTH,irq2,NULL);return 0;}//3、定义close文件接口int myclose(struct inode *inode, struct file *file){// 释放中断服务函数free_irq(irq1,NULL);free_irq(irq2,NULL);// 失能中断 --- 对于当前板子不需要return 0;}//定义read文件接口ssize_t myread(struct file *file, char __user *buf, size_t size, loff_t *offt){//13、等待队列条件清0wait_flag 0;//11、等待队列挂起wait_event_interruptible(mywait ,wait_flag);//程序会一直停在这个分号后ret copy_to_user(buf,value,size);value 0;return 0;}//4、定义probe函数int myprobe(struct platform_device *pdev){printk(匹配成功\n);//8、获取节点信息key1 of_get_named_gpio(pdev-dev.of_node,gpios,0);key2 of_get_named_gpio(pdev-dev.of_node,gpios,1);//8、设备树获取中断号irq1 platform_get_irq(pdev,0);irq2 platform_get_irq(pdev,1);ops.owner THIS_MODULE;//2、把open加入核心结构体ops.open myopen;//4、把release加入核心结构体ops.release myclose;//把read加入核心结构体ops.read myread;misc.fops ops;misc.minor 255;misc.name key;misc_register(misc);return 0;};//5、定义remove函数int myremove(struct platform_device *pdev){misc_deregister(misc);return 0;};//6、定义匹配核心结构体struct of_device_id myid {.compatible key-platdata,};// 3、定义驱动端核心结构体struct platform_driver pdrv {.probe myprobe,.remove myremove,.driver.owner THIS_MODULE,.driver.name 1012,.driver.of_match_table myid,};static int __init myinit(void){// 2、驱动端注册platform_driver_register(pdrv);return 0;}static int __exit myexit(void){// 7、驱动端注销platform_driver_unregister(pdrv);}module_init(myinit);module_exit(myexit);MODULE_LICENSE(GPL);设备树文件xyd_key: xyd-key {compatible key-platdata;gpios gpio1 RK_PA7 GPIO_ACTIVE_LOW,gpio1 RK_PB1 GPIO_ACTIVE_LOW;interrupt-parent gpio1;interrupts RK_PA7 IRQ_TYPE_EDGE_BOTH,RK_PB1 IRQ_TYPE_EDGE_BOTH;status okay;};