☰
RK平台按键中断驱动开发:从设备树到中断处理函数的完整实践
2026/10/18 7:01:00 网站建设 项目流程

做按键中断这个实验,我印象最深的是第一次按下按键时,系统直接卡死重启了。当时用的还是比较老的开发板,内核日志也没有现在这么友好,排查了一整天才发现是中断处理函数里调用了一个会睡眠的函数,直接在中断上下文触发了内核调度器的警告。从那以后我写中断驱动都会先想清楚一个问题:这个中断处理函数里到底能不能做这件事。这篇就以RK平台为背景,把按键中断从设备树配置、驱动代码到实测排查的完整链路再过一遍。

1. 为什么按键一定要用中断驱动:轮询与中断的取舍

很多刚从裸机开发转到Linux驱动开发的同学会有个疑问:按键检测这么简单的事,我在主循环里不断读GPIO电平不就行了,为什么非要引入中断?

直接给结论:轮询在简单场景下确实能用,但放到Linux里,问题会被放大。

1.1 轮询方案在Linux里的三个致命问题

第一个问题是CPU占用和功耗。应用层起一个线程,每隔几毫秒去读一次/sys/class/gpio或者通过ioctl读GPIO,看起来不复杂。但系统里可能有多个驱动、多个任务在争抢CPU,你的轮询线程为了保证按键响应及时,频率不能太低,比如10ms读一次,CPU占用率不高但是会不断触发系统调用和调度,睡眠唤醒的功耗开销在低功耗设备上完全不可接受。

第二个问题是响应延迟的不确定性。Linux是抢占式调度系统,你的轮询线程什么时候能跑到,取决于当前CPU的负载、调度器的优先级、中断的处理情况。负载高的时候,10ms的轮询周期可能变成50ms甚至100ms才轮到一次,按键手感完全没法保证。

第三个问题是资源浪费。如果系统里只有一两个按键,轮询也许能接受。但如果是矩阵键盘、旋钮编码器、多路传感器开关,每个都要轮询,CPU就成天在问"你变了没",这在工程上属于典型的错误抽象。

1.2 中断的本质:让硬件主动通知你

中断驱动和轮询最大的区别在于:轮询是CPU主动去问,中断是硬件主动来通知。按键被按下,GPIO电平发生变化,触发中断控制器,CPU保存现场进入内核的中断处理流程,调用你注册的处理函数。这个过程是硬件级的异步通知,不依赖调度器,延迟是微秒量级的,而且CPU在按键没有动作时完全不用管这个设备,可以安心睡大觉。

我在实际项目里做过一个粗略对比:同样是一个三键输入设备,用2ms轮询跑起来,空闲状态下系统整体电流比中断方案高出接近5倍,而且高负载时按键偶尔丢事件。换中断方案之后,事件一个不丢,功耗优势也很明显。

1.3 什么时候轮询反而是正确选择

把话说回来,中断不是万能的。有些场景下轮询反而更合理:

  • GPIO没有对应的中断控制器,比如某些通过I2C扩展的IO口芯片,本身不支持边沿中断。
  • 中断频率极高且需要持续采集,比如PWM信号测量,这时候中断处理函数本身的开销反而比轮询更大。
  • 硬件抖动严重且没有硬件滤波,在极端环境下中断风暴会把CPU打死,轮询加软件滤波更稳定。

判断标准很简单:事件是稀疏的还是密集的。稀疏事件用中断,密集且持续的事件用轮询。按键是最典型的稀疏事件,所以中断驱动是绝对的主流。

2. RK平台中断链路拆解:从硬件引脚到request_irq的中断号

RK平台的中断链路比想象中的要长一层。芯片内部的GPIO控制器分为若干个GPIO bank,每个bank下面有32个pin,中断控制器负责把这些pin上的边沿或电平信号汇总后上报给ARM GIC(通用中断控制器)。

2.1 硬件连接与GPIO编号的映射关系

先说硬件侧。假设我们用一个按键,一端接GND,另一端接SoC的某个GPIO pin,内部上拉使能,按键按下时电平被拉低。驱动要做的第一件事就是通过设备树告诉内核"这个GPIO要被用作中断源"。

RK平台在设备树里描述GPIO中断通常长这样:

&gpio3 { key_power { gpios = <&gpio3 RK_PB2 GPIO_ACTIVE_LOW>; linux,code = <KEY_POWER>; label = "KEY-POWER"; gpio-key,wakeup; }; };

如果走标准的gpio-keys框架,设备树写完之后驱动逻辑内核已经帮你做好了,应用层通过输入子系统直接读事件。但我们这里要讲的是自己写中断驱动,所以设备树要稍微偏底层一点,直接把一个GPIO定义成中断源:

key_device: key-device { compatible = "example,key-driver"; pinctrl-names = "default"; pinctrl-0 = <&key_pin_default>; interrupt-parent = <&gpio3>; interrupts = <RK_PB2 IRQ_TYPE_EDGE_FALLING>; status = "okay"; };
&pinctrl { key_pin_default: key-pin-default { rockchip,pins = <3 RK_PB2 RK_FUNC_GPIO &pcfg_pull_up>; }; };

这里有几个地方值得注意。interrupt-parent指向的是gpio3这个GPIO控制器节点,interrupts的第一个参数是pin的索引,RK_PB2对应的是GPIO3_B2,换算下来是第18号pin,第二个参数是触发类型。RK平台用的是IRQ_TYPE_EDGE_FALLING表示下降沿触发,对应按键按下瞬间由高到低的跳变。

2.2 中断控制器级联:为什么不能直接写GIC的中断号

很多第一次接触RK平台的人会问:为什么interrupts里不直接写GIC的中断号?因为GPIO控制器本身就是一个中断控制器,它挂在GIC下面,属于级联关系。GPIO控制器收到引脚中断后,会通过它自己的中断线向GIC发中断,GIC再通知CPU。

这就意味着,一个pin的中断链路是:GPIO引脚 → GPIO控制器内部的引脚映射 → GPIO控制器的中断输出线 → GIC → CPU。Linux中断子系统用irq_domain把每一级的中断域串起来,驱动里拿到的中断号已经是内核抽象出来的Linux IRQ号,不是硬件中断号,也不是GIC号。

驱动里获取中断号有两种常见方式:

/* 方式一:直接用GPIO API转换 */ int irq = gpio_to_irq(gpio_num); /* 方式二:通过platform_get_irq从设备树解析 */ int irq = platform_get_irq(pdev, 0);

我倾向于在按键这种简单场景下用gpio_to_irq,因为它更直观。但要记住,这个函数必须在GPIO已经被申请之后调用,否则拿到的中断号可能是无效的。

2.3 电平触发和边沿触发的区别与选择

另一件必须搞清楚的事情是触发方式。RK平台的GPIO中断支持四种:高电平触发、低电平触发、上升沿触发、下降沿触发。

按键场景下,最容易踩坑的是电平和边沿的选择。电平触发的特点是:只要按键保持按下状态,只要电平没有恢复,中断就会一直被触发。这意味着你在中断处理函数里哪怕只做了一次打印,按住按键不放,中断会以极快的速度反复触发,直接把你系统打爆。边沿触发只在电平跳变的那一瞬间产生一次中断,按住不放不会持续触发。

但边沿触发也有它的代价:如果按键抖动比较严重,一个物理按键动作可能产生多次边沿跳变,也就是会出现多次中断。这个问题后面专门讲怎么处理。

对比项电平触发边沿触发
触发条件电平保持期间持续触发仅在电平跳变的瞬间触发一次
按键按住不放中断风暴,必须处理只有一次中断,天然免疫
抖动影响相对较小,但持续触发频繁抖动会产生多次边沿,需软件防抖
典型应用唤醒源、状态检测按键、编码器

3. 按键中断驱动的完整实现:从request_irq到input子系统上报

设备树配置好之后,真正写驱动代码的时候反而简单了,核心就是probe函数里把中断注册好,中断处理函数里把事件上报出去。

3.1 驱动的整体框架

我习惯用platform_driver框架来做。模块加载时通过compatible匹配设备树节点,probe函数执行GPIO申请、中断注册、输入设备注册,remove函数做反向回收。

#include <linux/module.h> #include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/interrupt.h> #include <linux/of_gpio.h> #include <linux/input.h> #define KEY_DEV_GPIO 16 /* 假设是GPIO3_B2对应的全局GPIO号 */ struct key_priv { int gpio; int irq; int key_code; struct input_dev *input_dev; }; static irqreturn_t key_isr(int irq, void *data) { struct key_priv *priv = data; /* 按键按下,上报按键事件 */ input_report_key(priv->input_dev, priv->key_code, 1); input_sync(priv->input_dev); return IRQ_HANDLED; } static void key_release(struct key_priv *priv) { if (priv->irq) free_irq(priv->irq, priv); if (priv->gpio) gpio_free(priv->gpio); if (priv->input_dev) input_unregister_device(priv->input_dev); } static int key_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct key_priv *priv; struct device_node *np = dev->of_node; int ret; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; platform_set_drvdata(pdev, priv); /* 从设备树解析GPIO和按键码 */ priv->gpio = of_get_named_gpio(np, "key-gpio", 0); if (!gpio_is_valid(priv->gpio)) { dev_err(dev, "invalid gpio\n"); return -EINVAL; } ret = of_property_read_u32(np, "key-code", &priv->key_code); if (ret) priv->key_code = KEY_ENTER; ret = devm_gpio_request_one(dev, priv->gpio, GPIOF_IN | GPIOF_EXPORT_DIR_CHANGE, "key-gpio"); if (ret) { dev_err(dev, "gpio request failed\n"); return ret; } /* 把GPIO转为中断号 */ priv->irq = gpio_to_irq(priv->gpio); if (priv->irq < 0) { dev_err(dev, "gpio_to_irq failed\n"); return priv->irq; } /* 申请中断,注意最后一个参数是传给中断处理函数的data */ ret = request_irq(priv->irq, key_isr, IRQF_TRIGGER_FALLING | IRQF_SHARED, "key-irq", priv); if (ret) { dev_err(dev, "request_irq failed: %d\n", ret); return ret; } /* 注册input设备,这样应用层可以通过/dev/input/eventX读取 */ priv->input_dev = input_allocate_device(); if (!priv->input_dev) { ret = -ENOMEM; goto err_irq; } priv->input_dev->name = "rk-key-driver"; priv->input_dev->evbit[0] = BIT_MASK(EV_KEY); set_bit(priv->key_code, priv->input_dev->keybit); ret = input_register_device(priv->input_dev); if (ret) { input_free_device(priv->input_dev); goto err_irq; } return 0; err_irq: free_irq(priv->irq, priv); return ret; } static int key_remove(struct platform_device *pdev) { struct key_priv *priv = platform_get_drvdata(pdev); key_release(priv); return 0; } static const struct of_device_id key_of_match[] = { { .compatible = "example,key-driver" }, { /* 无匹配项结束标记 */ } }; MODULE_DEVICE_TABLE(of, key_of_match); static struct platform_driver key_driver = { .probe = key_probe, .remove = key_remove, .driver = { .name = "rk_key_driver", .of_match_table = key_of_match, }, }; module_platform_driver(key_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Embedded Engineer");

这段代码里需要重点解释几个设计决策,不然直接抄容易出现莫名其妙的问题。

IRQF_SHARED这个标志很多人看不懂。它表示这个中断允许和其他驱动共享。为什么按键中断要加共享?因为RK平台的GPIO中断在OS上面做了统一管理,一个GPIO bank的所有pin共用同一条中断线,如果其他驱动已经在这条线上注册了中断,你的request_irq不加IRQF_SHARED就会失败。加了共享之后,每次中断触发,内核会把注册在该中断号上的所有处理函数都调用一遍,每个函数需要自己判断是不是自己的引脚产生了中断。

这里要立刻接出一个在实际项目里遇到过的问题,也是后面会单独讲排障的原因之一。**我在GPIO中断里加了IRQF_SHARED,但系统里并没有其他设备共享这个中断。**内核仍然正常工作。后来发现有些情况不加这个标志也能注册成功,取决于中断控制器对共享的支持策略。但作为通用写法,加上会让兼容性更好,尤其是在RK这种GPIO中断统一管理的平台上。

3.2 为什么中断处理函数里直接上报按键事件

上面代码里中断处理函数直接调用了input_report_key和input_sync,这在Linux input子系统的语境里是允许的。input_report_key本质上是把按键事件填到一个缓冲区里,input_sync告诉接收端"这批事件提交了",这两个操作都是可以在中断上下文安全执行的,不存在睡眠行为。

但如果你做过其他平台的驱动开发,可能会觉得奇怪:很多书上都强调中断处理函数要尽量短,最好只做个标记,具体处理放到底半部。这个矛盾在于按键事件本身极其简单,上报动作的开销很小,不值得为此引入额外的底半部机制。如果按键逻辑里包含了I2C读取、延时等待、复杂滤波这些耗时操作,那就必须考虑底半部处理了。这个我在后面专门开一章节详细讲。

3.3 中断处理函数的签名和返回值

static irqreturn_t key_isr(int irq, void *data)
  • irq是触发的中断号,如果是共享中断,可以用它做区分。
  • data就是request_irq的最后一个参数,这里传了priv结构体指针,处理函数里可以用它访问输入设备和GPIO信息。

返回值用IRQ_HANDLED表示这个中断被你处理了。如果是共享中断但检测下来不是自己设备触发的中断,应该返回IRQ_NONE,表示"不是我的事",让内核继续调用同一中断号上的其他处理函数。这两个宏的定义在linux/interrupt.h里,本质上是返回值的两个枚举。我见过有人不管三七二十一统一返回IRQ_HANDLED,虽然大多数情况下不会出错,但这是错误示范,尤其是共享中断场景下会干扰内核的中断统计和调试判断。

4. 实测现场:中断不响应、抖动误触发、卸载崩溃的完整排查

只看代码的话,按键中断驱动确实不难,难度全在实测阶段。我梳理三个最典型的故障现象,把完整排查链路写出来。这些坑几乎每个做过RK按键驱动的人都踩过。

4.1 中断注册成功但按键按下毫无反应

现象:request_irq返回0,设备和中断都注册成功,驱动日志也正常打印,但按下按键,中断处理函数里的打印完全不出现。

排查链路:

第一步,先确认GPIO有没有被其他模块复用。RK平台的pinctrl是非常典型的"谁先申请谁先用"机制。如果你的按键GPIO和某个I2C、UART、PWM的引脚冲突,而那个外设的驱动先加载了,pinctrl会把引脚复用成外设功能,GPIO中断自然收不到信号。排查方法:查看设备树里pinctrl节点有没有被其他节点引用,或者直接读/sys/kernel/debug/pinctrl下的引脚复用状态。

第二步,确认中断有没有确实发生。中断注册成功后,内核会在/proc/interrupts里面列出这个中断号和它触发的次数。按下按键后再看这个文件,如果次数没变,说明中断信号根本没到CPU,问题在硬件链路或者GPIO配置;如果次数在涨,说明中断确实触发了,问题在驱动代码里中断号和设备树的对应关系。

查看中断计数的命令:

cat /proc/interrupts

会看到类似这行输出:

CPU0 CPU1 336: 5 0 gpio3 18 Edge key-irq

第一列是Linux中断号,中间是CPU上的触发次数统计,gpio3 18 Edge表示这是gpio3控制器的第18号pin,触发方式是边沿。如果次数一直是0,往下查硬件侧。

第三步,用读取GPIO电平的方式反向验证引脚功能是否OK:

cat /sys/kernel/debug/gpio

这个文件会把当前所有GPIO的方向和电平状态列出来。如果按键按下时对应引脚的电平状态有变化,说明GPIO本身是通的,问题可能出在中断控制器映射上。如果电平没有变化,那就回到原理图查硬件吧——按键没焊好、上拉电阻没贴、引脚确认错了,都有可能。

4.2 按一次按键,事件上报了好几次

现象:按键按下释放一次,应用层通过evtest或者自写程序读到了多次按键事件,有时甚至是"按下-释放-按下-释放"的乱序组合。

根因:这是典型的接触抖动问题。机械按键内部是金属弹片,按下和释放的瞬间会有微秒到毫秒级别的机械抖动,反映在电平上就是短时间内多次跳变。边沿触发在这种场景下会把这些跳变全部转换成中断,于是就有了"按一下触发好几次"的效果。

解决方案:软件防抖,思路和单片机上的防抖完全一样——只认稳定状态。对于边沿触发的中断方式,我推荐在中断处理函数里读取一次按键电平,如果确认确实是有效电平(比如按下对应低电平),再启动一个定时器延迟几个毫秒再确认一次。只有两次确认一致,才认为是真正的按键事件。

我的简化版实现思路如下,防抖定时器用内核的timer_list:

static void key_debounce_timer(struct timer_list *t) { struct key_priv *priv = from_timer(priv, t, debounce_timer); /* 再次读取GPIO电平,确认按键仍处于按下状态 */ if (gpio_get_value(priv->gpio) == 0) { input_report_key(priv->input_dev, priv->key_code, 1); input_sync(priv->input_dev); } }

在中断处理函数里不再直接上报,而是重置定时器:

static irqreturn_t key_isr(int irq, void *data) { struct key_priv *priv = data; /* 每次中断都重新启动防抖定时器 */ mod_timer(&priv->debounce_timer, jiffies + msecs_to_jiffies(20)); return IRQ_HANDLED; }

这样做的本质是吸收了抖动期的多次边沿,只有最后一次跳变之后的20毫秒内没有新的边沿,才确认按键真正按下。我一般把按键防抖时间设在15到30毫秒之间,太短会漏掉部分抖动,太长会影响按键手感,按下之后明显感觉"顿了"。

另一个更彻底的办法是使用gpiod_set_debounce接口让硬件或者内核框架帮你做防抖。但RK平台上这个接口是否真正生效取决于GPIO控制器驱动是否实现了debounce配置功能,建议实测验证,不要想当然。

4.3 卸载驱动时系统崩溃或者卡死

现象:执行rmmod卸载驱动,有时候会直接宕机,有时候会在free_irq的时候打印一堆堆栈信息。

根因:request_irq的最后一个参数是dev_id,free_irq需要传入完全相同的dev_id,内核才能找到对应的中断处理函数并精确移除。如果remove函数里free_irq传了NULL或者传了别的指针,和注册时的指针不一致,内核会在中断链表里被搞糊涂。特别是共享中断,内核通过dev_id区分同一个中断号上的多个处理函数,这个指针一旦对不上,直接崩。

我自己的一个惨痛教训是:probe里request_irq时用了priv指针,但remove里不知道为啥写成了dev指针,看起来都是"某个有效的指针",但在共享中断场景下内核完全无法匹配。后面的做法是在remove里从platform_get_drvdata取出priv,严格复用同一个指针。

还有一个系统性的卸载崩溃隐患:如果中断处理函数正在执行的时候执行了free_irq,内核会等待该中断上的处理函数执行完毕再释放,这是free_irq的同步语义。但如果中断处理函数里有msleep之类会睡眠的调用,free_irq等待时就会连带引发调度问题,最终可能导致死锁。所以又回到了那个原则:中断处理函数里不要调用任何可能睡眠的函数。

5. 中断处理函数的轻量化:tasklet、workqueue与threaded IRQ怎么选

如果只是上报一个按键事件,直接在上半部做完完全没有问题。但按键功能往往不会这么单纯,比如你可能需要按键触发一个网络请求、点亮屏幕、启动一段录像,甚至要执行一个I2C设备的初始化流程。这些操作要么耗时,要么需要睡眠等待,在上半部绝对不能做。

Linux内核为此提供了几种把耗时工作推迟到底半部的机制,实际选型要看具体情况。

tasklet(以及新版内核的softirq机制):tasklet运行在软中断上下文,执行速度快,但不能睡眠,不能在tasklet里访问用户态内存,也不能长时间占用CPU。适合做那种耗时很短但必须从上半部挪出来的工作,比如拷贝数据、解析一个简单协议头。

workqueue(工作队列):运行在进程上下文,允许睡眠,可以调用msleep、wait_event、mutex_lock这些会阻塞的函数。代价是执行时机取决于系统负载和调度,延迟比tasklet大。驱动里常见的做法是在中断里schedule_work,然后在一个work_struct里慢慢干活。

threaded IRQ(线程化中断):本质上是把整个中断处理函数放到一个内核线程里去执行。request_threaded_irq的用法和request_irq几乎一样,区别在于处理函数运行在独立的线程上下文,天然可以睡眠,而且可以设置线程的优先级,保证按键事件相对及时。

机制所属上下文能否睡眠调度延迟适用场景
上半部(直接处理)中断上下文不能最低快速置位、简单上报
tasklet/softirq软中断上下文不能低耗时极短的收尾工作
workqueue进程上下文能较高耗时操作、需要睡眠
threaded IRQ内核线程上下文能中低中断本身就需要慢处理

按键驱动的实际工程中,我推荐一个组合方案:上半部只做一件事,disable_irq_nosync关掉中断,同时把一个work_struct挂进系统默认工作队列。底半部在进程上下文读GPIO电平、做防抖确认、上报input事件、最后enable_irq重新打开中断。

这个方案的好处是不会漏事件。如果不用屏蔽中断,防抖期间按键再次抖动产生的边沿会源源不断进中断,定时器反复重置,事件会被无限推迟。用disable_irq_nosync把中断屏蔽掉,等防抖确认完成后再打开,逻辑就闭合了。

static irqreturn_t key_isr(int irq, void *data) { struct key_priv *priv = data; disable_irq_nosync(irq); schedule_work(&priv->debounce_work); return IRQ_HANDLED; } static void key_debounce_work(struct work_struct *work) { struct key_priv *priv = container_of(work, struct key_priv, debounce_work); /* 这里可以做耗时的防抖确认、电平读取、事件上报 */ msleep(20); if (gpio_get_value(priv->gpio) == 0) { input_report_key(priv->input_dev, priv->key_code, 1); input_sync(priv->input_dev); } enable_irq(priv->irq); }

这里有个细节很值得注意:disable_irq_nosync的名字里有个nosync,它只是把中断标记成"待禁用"状态,立即返回,不等处理函数结束。如果在中断处理函数里调用普通的disable_irq,会直接产生死锁——因为disable_irq等待的正是当前正在执行的这个中断处理函数。在中断处理函数里只能调用disable_irq_nosync,这是一个血泪教训换来的死规矩。

6. 进阶调试手段:中断统计、devmem验证与日志分级

写完代码、避完坑,我最后分享几个让调试效率明显提升的工具和方法。这些东西在书本上不算核心内容,但实际干活时比看代码管用得多。

第一个是/proc/interrupts,前面排查中断不响应时已经用到了。它的价值不止于看次数,还能看出中断分散在哪个CPU上。如果你看到某个中断全部集中在CPU0,按键响应偶尔出现卡顿,可以考虑通过irq_set_affinity_hint把中断绑到多个CPU上,或者用echo f > /proc/irq/336/smp_affinity在用户态调整亲和性。

第二个是devmem命令。RK平台通过/dev/mem节点可以随手读寄存器值。驱动开发中遇到GPIO方向不对、上拉没生效这类问题时,用devmem直接读GPIO控制器的寄存器是最快的验证手段。举例来说,读一组GPIO数据寄存器能直接看到按键按下前后引脚电平的原始状态,不需要经过内核GPIO子系统的缓存,这对于判断"硬件到底有没有变化"很有用。

使用示例:

# 假设GPIO3_B2对应的数据寄存器地址是0xFE7A0000相关的bank地址 devmem 0xFE7A0000 32

按下前后对比读出来的数值变化,是排查问题的最硬证据。

第三个是动态调试开关。按键驱动在调试阶段难免要在中断里打印电平状态,但如果打印放在上半部,按下一次按键可能因为抖动打印几十行,直接刷屏。我的做法是把打印放到防抖确认之后,也就是工作队列里,并且用pr_debug而不是printk,这样默认静默,需要时再通过dynamic_debug打开:

echo 'file key_driver.c +p' > /sys/kernel/debug/dynamic_debug/control

这样既能保留调试信息,又不会影响正式版本的行为。

最后一个不得不提的工具是evtest。驱动注册了input设备之后,应用层能不能读到事件,用evtest一测便知:

evtest /dev/input/eventX

它会实时打印事件类型、按键码和按下释放状态。如果evtest里有事件但业务上层没反应,问题就不在驱动侧,而是向上游的应用层或事件分发机制了。

从硬件引脚到设备树,从request_irq到input子系统,从抖动防抖到底半部选型,按键中断驱动这条链路走一遍,基本就能把RK平台中断相关的核心知识点串起来了。我在实际项目里最大的体会是,按键驱动虽然小,但它强迫你把中断上下文的概念、设备树的中断映射关系、内核的工作机制全部过一遍,是性价比很高的驱动练习题目。如果看完这篇你也去做按键实验,建议先别急着抄代码,拿/proc/interrupts和devmem把中断链路先摸一遍,再动手写逻辑,会顺手很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询