嵌入式Linux编程,如何把硬件中断交给用户态--UIO 的机制

把硬件中断交给用户态:UIO 的机制、边界与工程用法

1. 概述:UIO 解决什么问题

UIO 做的事情,说白了是把中断处理拆成两半:内核负责接住中断、把设备的中断线暂时按住,然后通知用户态;用户态负责判断发生了什么、清中断源、处理数据,最后告诉内核可以继续接收中断。

它解决的是内核驱动太重、业务逻辑又必须频繁改动的问题,不是让用户态直接接管 CPU 中断。中断仍然先送到内核,用户态拿到的只是一个"有事发生了"的通知。

这套机制适合事件不多、处理流程长、和上层状态机耦合紧的设备。高频 DMA 完成中断、共享中断线、需要严格时间确定性的场景,用 UIO 往往不划算,或者需要额外的批处理和兜底设计。

读下去不需要事先懂内核驱动,但最好知道进程、虚拟内存和中断这几个词。全文按"为什么需要、怎么工作、工程上怎么用对"三部分展开,先补齐背景,再进入设备树和代码。

2. 背景:中断、权限与 MCU 的差别

这一节先讲中断本身,再讲用户态为什么接不了中断,最后用 MCU 做一次对照。

2.1 中断是什么,ISR 为什么必须短

CPU 大部分时间在按顺序执行指令。外设有事时,不可能让 CPU 一直停下来问"你有事吗",否则大量时间会浪费在空转上。更好的办法是外设主动拉一根信号线,告诉中断控制器"我有事",中断控制器再把请求转给 CPU。CPU 暂停当前工作,跳到一个预先注册的函数里处理,处理完再回来。

这个函数叫中断服务例程,简称 ISR。ISR 运行在中断上下文,要求很短:不能睡眠,不能等锁,不能做大量拷贝。中断上下文没有独立的进程上下文,睡眠后调度器无法安全地恢复它;同时它占用的是 CPU 的紧急通道,拖久了会挡住其他中断,也会让调度器迟迟拿不回控制权。

中断控制器负责把多个外设的请求汇总、排序,再送给 CPU。ARM 平台上常见的是 GIC,也就是 Generic Interrupt Controller;Cortex-M 单片机上对应的是 NVIC,即 Nested Vectored Interrupt Controller。设备树里的 interrupt-parent 和 interrupts 描述的就是"这根线接到哪个控制器、用哪个编号、什么触发方式"。

2.2 为什么用户态不能直接接中断

CPU 通常有特权级划分。内核态可以执行特权指令、改页表、配置中断控制器、访问设备寄存器;用户态只能访问自己的虚拟地址空间,不能关中断,也不能直接和中断控制器对话。

中断是送给 CPU 的,不是送给某个进程的。CPU 收到中断时,当前跑的是哪个进程并不重要,内核会先接管,再决定后续怎么处理。用户态进程可能正在睡眠、被换出、等锁,甚至根本不在运行,它没有能力在中断到来的那一刻立刻响应。

可以用前台和住户打个比方。外设按门铃,内核是前台,用户态进程是住户。前台负责接铃、挡住后续门铃、通知住户;真正开门处理事情的是住户。UIO 就是这套前台协议:前台只做最少的动作,剩下的交给住户。

所以"把中断导出到用户态"这句话要打个折扣。用户态并没有拿到中断向量,也没有拿到中断控制器的操作权。它拿到的是一个可以 read 的文件描述符,以及一个累计的事件计数。

2.3 和 MCU 中断处理的区别

很多做嵌入式的人第一次看到 UIO 会问:MCU 上中断来了直接进 ISR,几行代码就处理完了,为什么 Linux 要绕这么大一圈?

MCU 的模型通常是单地址空间。以常见的 Cortex-M 单片机为例,中断控制器 NVIC 根据优先级把 CPU 跳到向量表里的入口,ISR 直接读写寄存器,处理完返回。ISR 里不能阻塞,通常只做清中断、置标志、发信号量,重活交给主循环或 RTOS 任务。整个路径没有系统调用,没有进程切换,延迟通常是微秒级,抖动也小。

Linux 的模型多了一层特权边界。CPU 收到中断后先进内核,UIO 驱动屏蔽中断线并唤醒用户态线程;用户态线程被调度到 CPU 后才开始处理。中间多了 threaded IRQ、唤醒、调度和可能的缺页,延迟从微秒级变成几十微秒到毫秒级,而且会受系统负载影响。
#mermaid-svg-iI3oIz4O9hKb6jyD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#17324D;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iI3oIz4O9hKb6jyD .error-icon{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);}#mermaid-svg-iI3oIz4O9hKb6jyD .error-text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);stroke:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iI3oIz4O9hKb6jyD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iI3oIz4O9hKb6jyD .marker{fill:#5B6B7C;stroke:#5B6B7C;}#mermaid-svg-iI3oIz4O9hKb6jyD .marker.cross{stroke:#5B6B7C;}#mermaid-svg-iI3oIz4O9hKb6jyD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iI3oIz4O9hKb6jyD p{margin:0;}#mermaid-svg-iI3oIz4O9hKb6jyD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#17324D;}#mermaid-svg-iI3oIz4O9hKb6jyD .cluster-label text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-iI3oIz4O9hKb6jyD .cluster-label span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-iI3oIz4O9hKb6jyD .cluster-label span p{background-color:transparent;}#mermaid-svg-iI3oIz4O9hKb6jyD .label text,#mermaid-svg-iI3oIz4O9hKb6jyD span{fill:#17324D;color:#17324D;}#mermaid-svg-iI3oIz4O9hKb6jyD .node rect,#mermaid-svg-iI3oIz4O9hKb6jyD .node circle,#mermaid-svg-iI3oIz4O9hKb6jyD .node ellipse,#mermaid-svg-iI3oIz4O9hKb6jyD .node polygon,#mermaid-svg-iI3oIz4O9hKb6jyD .node path{fill:#E8F1FB;stroke:#3B82C4;stroke-width:1px;}#mermaid-svg-iI3oIz4O9hKb6jyD .rough-node .label text,#mermaid-svg-iI3oIz4O9hKb6jyD .node .label text,#mermaid-svg-iI3oIz4O9hKb6jyD .image-shape .label,#mermaid-svg-iI3oIz4O9hKb6jyD .icon-shape .label{text-anchor:middle;}#mermaid-svg-iI3oIz4O9hKb6jyD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iI3oIz4O9hKb6jyD .rough-node .label,#mermaid-svg-iI3oIz4O9hKb6jyD .node .label,#mermaid-svg-iI3oIz4O9hKb6jyD .image-shape .label,#mermaid-svg-iI3oIz4O9hKb6jyD .icon-shape .label{text-align:center;}#mermaid-svg-iI3oIz4O9hKb6jyD .node.clickable{cursor:pointer;}#mermaid-svg-iI3oIz4O9hKb6jyD .root .anchor path{fill:#5B6B7C!important;stroke-width:0;stroke:#5B6B7C;}#mermaid-svg-iI3oIz4O9hKb6jyD .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-iI3oIz4O9hKb6jyD .edgePath .path{stroke:#5B6B7C;stroke-width:2.0px;}#mermaid-svg-iI3oIz4O9hKb6jyD .flowchart-link{stroke:#5B6B7C;fill:none;}#mermaid-svg-iI3oIz4O9hKb6jyD .edgeLabel{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-iI3oIz4O9hKb6jyD .edgeLabel p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-iI3oIz4O9hKb6jyD .edgeLabel rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-iI3oIz4O9hKb6jyD .labelBkg{background-color:rgba(240.9999999999, 251, 231.9999999998, 0.5);}#mermaid-svg-iI3oIz4O9hKb6jyD .cluster rect{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);stroke:hsl(31.5789473684, 30.3703703704%, 89.7058823529%);stroke-width:1px;}#mermaid-svg-iI3oIz4O9hKb6jyD .cluster text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-iI3oIz4O9hKb6jyD .cluster span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-iI3oIz4O9hKb6jyD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);border:1px solid hsl(31.5789473684, 30.3703703704%, 89.7058823529%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iI3oIz4O9hKb6jyD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#17324D;}#mermaid-svg-iI3oIz4O9hKb6jyD rect.text{fill:none;stroke-width:0;}#mermaid-svg-iI3oIz4O9hKb6jyD .icon-shape,#mermaid-svg-iI3oIz4O9hKb6jyD .image-shape{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-iI3oIz4O9hKb6jyD .icon-shape p,#mermaid-svg-iI3oIz4O9hKb6jyD .image-shape p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);padding:2px;}#mermaid-svg-iI3oIz4O9hKb6jyD .icon-shape .label rect,#mermaid-svg-iI3oIz4O9hKb6jyD .image-shape .label rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-iI3oIz4O9hKb6jyD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iI3oIz4O9hKb6jyD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iI3oIz4O9hKb6jyD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-iI3oIz4O9hKb6jyD .hw>*{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .hw span{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .hw tspan{fill:#5A1A14!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .kernel>*{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .kernel span{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .kernel tspan{fill:#123F33!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .user>*{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .user span{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-iI3oIz4O9hKb6jyD .user tspan{fill:#17324D!important;} Linux + UIO
MCU 裸机 / RTOS
write 1 重新使能
设备拉高中断线
NVIC

向量表直接跳转
ISR

清源、置标志
主循环 / RTOS 任务

处理数据
设备拉高中断线
GIC

转交 CPU
内核接管

屏蔽中断线
threaded IRQ

唤醒用户态
用户态线程

清源、处理数据

图 1:同样的设备中断,MCU 直接跳进 ISR;Linux 多出内核接管、线程唤醒和用户态调度。

维度 MCU 裸机 / RTOS Linux + UIO
地址空间与权限 单地址空间,通常没有用户态/内核态切换 内核态与用户态隔离,设备访问受权限约束
中断入口 向量表直接跳到 ISR CPU 进内核,UIO 驱动接管后再通知用户态
处理上下文 ISR,或 ISR 唤醒的 RTOS 任务 内核 threaded IRQ,加用户态进程
延迟与确定性 微秒级,路径短,抖动小 几十微秒到毫秒级,受调度和负载影响
能否阻塞 ISR 不能,任务可以 用户态可以阻塞、等锁、分配内存
设备访问 指针直接读写寄存器 mmap 映射后再读写,注意缓存属性
故障影响 指针错误可能导致整机复位 进程崩溃,硬件可能保持旧状态
典型场景 电机控制、安全回路、高频采样 配置管理、状态机、日志与网络上报

这不是说 Linux 的路径更差。MCU 的确定性来自"所有代码在一个地址空间里",代价是任何指针错误都可能让整机复位;Linux 用 MMU 和进程隔离换来了更复杂的业务处理能力,代价是延迟和抖动。两者解决的不是同一个问题。

实际系统里也经常混用。MCU 负责快速、确定的安全动作,Linux 负责策略、配置和运维;或者 Linux 内核驱动负责高频数据面,UIO 只负责低频控制事件。把实时性要求最高的那部分留在 MCU 或内核,比在用户态反复调优先级更可靠。

2.4 寄存器、MMIO 和 DMA

理解了 MCU 与 Linux 的路径差别,再看 UIO 依赖的两条硬件通路:寄存器和 DMA。

设备通常通过寄存器暴露接口。写控制寄存器启动采集,读状态寄存器判断完成,写清除寄存器确认中断。CPU 访问这些寄存器的方式叫 MMIO,也就是把设备寄存器映射到一段物理地址,然后用读写内存的方式访问。

在用户态,MMIO 通常通过 mmap 完成。每个进程看到的是自己的虚拟地址空间,物理地址由页表映射;设备寄存器要先用 mmap 映射进这段空间,进程才能用普通指针访问。映射建立后,进程拿到一个虚拟地址,读写这个地址就等于读写设备寄存器。物理寄存器映射一般会被内核标记为 non-cached,避免 CPU 缓存留下过期值;如果映射的是普通内存,还要考虑缓存一致性和内存屏障。

DMA 是另一条通路。设备不经过 CPU,直接把数据写进内存。CPU 只读描述符或者读写索引,就知道哪块内存有新数据。DMA 省掉了 CPU 逐字节拷贝,但也带来两个问题:设备写得太快,CPU 读得太慢,环形缓冲区会被覆盖;设备写内存后,CPU 可能从缓存里读到旧数据,需要一致性维护。
#mermaid-svg-AexethhjGeVZLu6H{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#17324D;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AexethhjGeVZLu6H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AexethhjGeVZLu6H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AexethhjGeVZLu6H .error-icon{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);}#mermaid-svg-AexethhjGeVZLu6H .error-text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);stroke:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-AexethhjGeVZLu6H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AexethhjGeVZLu6H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AexethhjGeVZLu6H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AexethhjGeVZLu6H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AexethhjGeVZLu6H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AexethhjGeVZLu6H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AexethhjGeVZLu6H .marker{fill:#5B6B7C;stroke:#5B6B7C;}#mermaid-svg-AexethhjGeVZLu6H .marker.cross{stroke:#5B6B7C;}#mermaid-svg-AexethhjGeVZLu6H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AexethhjGeVZLu6H p{margin:0;}#mermaid-svg-AexethhjGeVZLu6H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#17324D;}#mermaid-svg-AexethhjGeVZLu6H .cluster-label text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-AexethhjGeVZLu6H .cluster-label span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-AexethhjGeVZLu6H .cluster-label span p{background-color:transparent;}#mermaid-svg-AexethhjGeVZLu6H .label text,#mermaid-svg-AexethhjGeVZLu6H span{fill:#17324D;color:#17324D;}#mermaid-svg-AexethhjGeVZLu6H .node rect,#mermaid-svg-AexethhjGeVZLu6H .node circle,#mermaid-svg-AexethhjGeVZLu6H .node ellipse,#mermaid-svg-AexethhjGeVZLu6H .node polygon,#mermaid-svg-AexethhjGeVZLu6H .node path{fill:#E8F1FB;stroke:#3B82C4;stroke-width:1px;}#mermaid-svg-AexethhjGeVZLu6H .rough-node .label text,#mermaid-svg-AexethhjGeVZLu6H .node .label text,#mermaid-svg-AexethhjGeVZLu6H .image-shape .label,#mermaid-svg-AexethhjGeVZLu6H .icon-shape .label{text-anchor:middle;}#mermaid-svg-AexethhjGeVZLu6H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AexethhjGeVZLu6H .rough-node .label,#mermaid-svg-AexethhjGeVZLu6H .node .label,#mermaid-svg-AexethhjGeVZLu6H .image-shape .label,#mermaid-svg-AexethhjGeVZLu6H .icon-shape .label{text-align:center;}#mermaid-svg-AexethhjGeVZLu6H .node.clickable{cursor:pointer;}#mermaid-svg-AexethhjGeVZLu6H .root .anchor path{fill:#5B6B7C!important;stroke-width:0;stroke:#5B6B7C;}#mermaid-svg-AexethhjGeVZLu6H .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-AexethhjGeVZLu6H .edgePath .path{stroke:#5B6B7C;stroke-width:2.0px;}#mermaid-svg-AexethhjGeVZLu6H .flowchart-link{stroke:#5B6B7C;fill:none;}#mermaid-svg-AexethhjGeVZLu6H .edgeLabel{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-AexethhjGeVZLu6H .edgeLabel p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-AexethhjGeVZLu6H .edgeLabel rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-AexethhjGeVZLu6H .labelBkg{background-color:rgba(240.9999999999, 251, 231.9999999998, 0.5);}#mermaid-svg-AexethhjGeVZLu6H .cluster rect{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);stroke:hsl(31.5789473684, 30.3703703704%, 89.7058823529%);stroke-width:1px;}#mermaid-svg-AexethhjGeVZLu6H .cluster text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-AexethhjGeVZLu6H .cluster span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-AexethhjGeVZLu6H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);border:1px solid hsl(31.5789473684, 30.3703703704%, 89.7058823529%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AexethhjGeVZLu6H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#17324D;}#mermaid-svg-AexethhjGeVZLu6H rect.text{fill:none;stroke-width:0;}#mermaid-svg-AexethhjGeVZLu6H .icon-shape,#mermaid-svg-AexethhjGeVZLu6H .image-shape{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-AexethhjGeVZLu6H .icon-shape p,#mermaid-svg-AexethhjGeVZLu6H .image-shape p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);padding:2px;}#mermaid-svg-AexethhjGeVZLu6H .icon-shape .label rect,#mermaid-svg-AexethhjGeVZLu6H .image-shape .label rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-AexethhjGeVZLu6H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AexethhjGeVZLu6H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AexethhjGeVZLu6H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-AexethhjGeVZLu6H .hw>*{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-AexethhjGeVZLu6H .hw span{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-AexethhjGeVZLu6H .hw tspan{fill:#5A1A14!important;}#mermaid-svg-AexethhjGeVZLu6H .kernel>*{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-AexethhjGeVZLu6H .kernel span{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-AexethhjGeVZLu6H .kernel tspan{fill:#123F33!important;}#mermaid-svg-AexethhjGeVZLu6H .user>*{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-AexethhjGeVZLu6H .user span{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-AexethhjGeVZLu6H .user tspan{fill:#17324D!important;} 硬件
内核
用户态
mmap
non-cached 映射
读写寄存器
配置并启动
直接写内存
读 write / read 索引
业务线程
页表
设备寄存器
DMA 引擎
内存:环形缓冲

图 2:控制面通过 mmap 访问寄存器,数据面由 DMA 写入内存,用户态只读索引。

UIO 能同时暴露寄存器和中断:寄存器通过 mmap 给用户态,中断通过 /dev/uioN 通知用户态。DMA 缓冲区可以由内核预留内存、/dev/mem 或 UIO 的 dmem 映射提供,具体要看平台设计。

2.5 设备树负责描述硬件

设备树是一份硬件描述,不是驱动代码5。它在启动时告诉内核:板子上有哪些设备、寄存器在哪个地址、中断接到哪个控制器、用哪根中断线。内核根据 compatible 字符串找匹配的驱动,根据 reg 建立寄存器映射,根据 interrupts 申请中断。

设备树不创建 /dev 节点。它只把资源描述给内核;驱动匹配并完成初始化后调用 uio_register_device,内核才创建 UIO 设备和 sysfs 属性,udev 再根据这些信息生成 /dev/uioN。所以"DTS 写了但 /dev/uioN 不出现"并不奇怪,问题可能卡在驱动匹配、初始化、权限或 udev 规则上。

2.6 UIO 的位置与适用边界

把上面的背景连起来,UIO 的位置就很清楚了:它不替代中断控制器,也不替代设备驱动,只是在"内核接住中断"和"用户态处理业务"之间搭了一座桥。
#mermaid-svg-odQe3JXOih7ntGHL{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#17324D;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-odQe3JXOih7ntGHL .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-odQe3JXOih7ntGHL .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-odQe3JXOih7ntGHL .error-icon{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);}#mermaid-svg-odQe3JXOih7ntGHL .error-text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);stroke:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-odQe3JXOih7ntGHL .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-odQe3JXOih7ntGHL .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-odQe3JXOih7ntGHL .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-odQe3JXOih7ntGHL .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-odQe3JXOih7ntGHL .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-odQe3JXOih7ntGHL .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-odQe3JXOih7ntGHL .marker{fill:#5B6B7C;stroke:#5B6B7C;}#mermaid-svg-odQe3JXOih7ntGHL .marker.cross{stroke:#5B6B7C;}#mermaid-svg-odQe3JXOih7ntGHL svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-odQe3JXOih7ntGHL p{margin:0;}#mermaid-svg-odQe3JXOih7ntGHL .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#17324D;}#mermaid-svg-odQe3JXOih7ntGHL .cluster-label text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-odQe3JXOih7ntGHL .cluster-label span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-odQe3JXOih7ntGHL .cluster-label span p{background-color:transparent;}#mermaid-svg-odQe3JXOih7ntGHL .label text,#mermaid-svg-odQe3JXOih7ntGHL span{fill:#17324D;color:#17324D;}#mermaid-svg-odQe3JXOih7ntGHL .node rect,#mermaid-svg-odQe3JXOih7ntGHL .node circle,#mermaid-svg-odQe3JXOih7ntGHL .node ellipse,#mermaid-svg-odQe3JXOih7ntGHL .node polygon,#mermaid-svg-odQe3JXOih7ntGHL .node path{fill:#E8F1FB;stroke:#3B82C4;stroke-width:1px;}#mermaid-svg-odQe3JXOih7ntGHL .rough-node .label text,#mermaid-svg-odQe3JXOih7ntGHL .node .label text,#mermaid-svg-odQe3JXOih7ntGHL .image-shape .label,#mermaid-svg-odQe3JXOih7ntGHL .icon-shape .label{text-anchor:middle;}#mermaid-svg-odQe3JXOih7ntGHL .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-odQe3JXOih7ntGHL .rough-node .label,#mermaid-svg-odQe3JXOih7ntGHL .node .label,#mermaid-svg-odQe3JXOih7ntGHL .image-shape .label,#mermaid-svg-odQe3JXOih7ntGHL .icon-shape .label{text-align:center;}#mermaid-svg-odQe3JXOih7ntGHL .node.clickable{cursor:pointer;}#mermaid-svg-odQe3JXOih7ntGHL .root .anchor path{fill:#5B6B7C!important;stroke-width:0;stroke:#5B6B7C;}#mermaid-svg-odQe3JXOih7ntGHL .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-odQe3JXOih7ntGHL .edgePath .path{stroke:#5B6B7C;stroke-width:2.0px;}#mermaid-svg-odQe3JXOih7ntGHL .flowchart-link{stroke:#5B6B7C;fill:none;}#mermaid-svg-odQe3JXOih7ntGHL .edgeLabel{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-odQe3JXOih7ntGHL .edgeLabel p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-odQe3JXOih7ntGHL .edgeLabel rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-odQe3JXOih7ntGHL .labelBkg{background-color:rgba(240.9999999999, 251, 231.9999999998, 0.5);}#mermaid-svg-odQe3JXOih7ntGHL .cluster rect{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);stroke:hsl(31.5789473684, 30.3703703704%, 89.7058823529%);stroke-width:1px;}#mermaid-svg-odQe3JXOih7ntGHL .cluster text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-odQe3JXOih7ntGHL .cluster span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-odQe3JXOih7ntGHL div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);border:1px solid hsl(31.5789473684, 30.3703703704%, 89.7058823529%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-odQe3JXOih7ntGHL .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#17324D;}#mermaid-svg-odQe3JXOih7ntGHL rect.text{fill:none;stroke-width:0;}#mermaid-svg-odQe3JXOih7ntGHL .icon-shape,#mermaid-svg-odQe3JXOih7ntGHL .image-shape{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-odQe3JXOih7ntGHL .icon-shape p,#mermaid-svg-odQe3JXOih7ntGHL .image-shape p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);padding:2px;}#mermaid-svg-odQe3JXOih7ntGHL .icon-shape .label rect,#mermaid-svg-odQe3JXOih7ntGHL .image-shape .label rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-odQe3JXOih7ntGHL .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-odQe3JXOih7ntGHL .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-odQe3JXOih7ntGHL :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-odQe3JXOih7ntGHL .hw>*{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-odQe3JXOih7ntGHL .hw span{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-odQe3JXOih7ntGHL .hw tspan{fill:#5A1A14!important;}#mermaid-svg-odQe3JXOih7ntGHL .kernel>*{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-odQe3JXOih7ntGHL .kernel span{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-odQe3JXOih7ntGHL .kernel tspan{fill:#123F33!important;}#mermaid-svg-odQe3JXOih7ntGHL .user>*{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-odQe3JXOih7ntGHL .user span{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-odQe3JXOih7ntGHL .user tspan{fill:#17324D!important;} 用户态
内核态
硬件
IRQ
IRQ
event count
mmap
read, poll
write 1
enable_irq
设备

FPGA / 外设
中断控制器
设备寄存器

MMIO
UIO 驱动

屏蔽中断、累计事件、唤醒
业务线程

图 3:中断走内核,数据面走 mmap,控制面走 read/write。用户态没有直接接触中断控制器。

几个词先对齐:

词 在这里的意思
中断 设备向 CPU 发出的异步通知
ISR 内核里的中断处理函数,要求短、不能睡眠
内核态 / 用户态 CPU 的两种权限级别
MMIO 把设备寄存器映射成内存地址来访问
DMA 设备直接读写内存,不经过 CPU 拷贝
环形缓冲区 固定长度、读写索引循环推进的缓冲区
DTS 描述硬件资源的设备树源文件
UIO 内核只做中断接管,业务逻辑放用户态
sysfs 内核暴露设备和属性的文件系统
udev Linux 的设备管理器,负责创建设备节点

什么时候不该用 UIO?如果设备吞吐很高,用户态每次唤醒的开销会超过收益;如果中断线被多个设备共享,通用 UIO 驱动无法判断中断属于谁;如果系统要求严格的时间确定性,用户态调度和缺页都可能带来不可控抖动。这些场景更适合内核驱动,或者至少需要把数据面下沉到内核。

下面从设备树开始,走一遍从硬件描述到用户态处理的完整链路。

3. 工作机制:从设备树到用户态

这一节按启动顺序走:设备树描述资源,驱动接住中断,用户态等待并完成处理。

3.1 设备树写什么,不写什么

一个典型的 UIO 节点长这样:

dts 复制代码
fpga_uio: uio@a0000000 {
    compatible = "example,fpga-uio";
    reg = <0x0 0xa0000000 0x0 0x1000>;
    interrupt-parent = <&gic>;
    interrupts = <0 42 IRQ_TYPE_LEVEL_HIGH>;
    linux,uio-name = "fpga-irq";
};

reg 的单元数量由父节点的 #address-cells 和 #size-cells 决定,上面的写法假设地址和长度各两个单元。interrupts 的格式由中断控制器决定,GIC 常见形式是"类型 + 编号 + 触发方式";IRQ_TYPE_LEVEL_HIGH 来自 dt-bindings/interrupt-controller/irq.h,实际 DTS 文件要包含这个头文件。linux,uio-name 不是必需的,较新内核支持这个属性,它决定了 /sys/class/uio/uioN/name 的内容。

通用 UIO 平台驱动的匹配方式有一个容易忽略的细节:uio_pdrv_genirq 的 OF 匹配表默认是空的,需要在内核启动参数里用 of_id 指定 compatible 字符串:

text 复制代码
uio_pdrv_genirq.of_id=example,fpga-uio

这意味着同一块板子上的多个 UIO 节点通常复用同一个 compatible,靠 reg 和 interrupts 区分。如果确实需要多个不同的 compatible,通用驱动做不到,需要自带一个 UIO 驱动,在 of_match_table 里列出所有字符串;of_id 只是一个字符串参数,不能同时绑定多个 compatible。

共享中断是另一个限制。uio_pdrv_genirq 不支持 IRQF_SHARED,因为通用处理函数不知道中断到底属于哪个设备。多设备共享一根线时,需要先在硬件或内核里做解复用,再给每个 UIO 节点分配独立的中断。
#mermaid-svg-MASnH4dGDoEVJJzm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#17324D;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-MASnH4dGDoEVJJzm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MASnH4dGDoEVJJzm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MASnH4dGDoEVJJzm .error-icon{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);}#mermaid-svg-MASnH4dGDoEVJJzm .error-text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);stroke:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-MASnH4dGDoEVJJzm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MASnH4dGDoEVJJzm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MASnH4dGDoEVJJzm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MASnH4dGDoEVJJzm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MASnH4dGDoEVJJzm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MASnH4dGDoEVJJzm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MASnH4dGDoEVJJzm .marker{fill:#5B6B7C;stroke:#5B6B7C;}#mermaid-svg-MASnH4dGDoEVJJzm .marker.cross{stroke:#5B6B7C;}#mermaid-svg-MASnH4dGDoEVJJzm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MASnH4dGDoEVJJzm p{margin:0;}#mermaid-svg-MASnH4dGDoEVJJzm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#17324D;}#mermaid-svg-MASnH4dGDoEVJJzm .cluster-label text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-MASnH4dGDoEVJJzm .cluster-label span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-MASnH4dGDoEVJJzm .cluster-label span p{background-color:transparent;}#mermaid-svg-MASnH4dGDoEVJJzm .label text,#mermaid-svg-MASnH4dGDoEVJJzm span{fill:#17324D;color:#17324D;}#mermaid-svg-MASnH4dGDoEVJJzm .node rect,#mermaid-svg-MASnH4dGDoEVJJzm .node circle,#mermaid-svg-MASnH4dGDoEVJJzm .node ellipse,#mermaid-svg-MASnH4dGDoEVJJzm .node polygon,#mermaid-svg-MASnH4dGDoEVJJzm .node path{fill:#E8F1FB;stroke:#3B82C4;stroke-width:1px;}#mermaid-svg-MASnH4dGDoEVJJzm .rough-node .label text,#mermaid-svg-MASnH4dGDoEVJJzm .node .label text,#mermaid-svg-MASnH4dGDoEVJJzm .image-shape .label,#mermaid-svg-MASnH4dGDoEVJJzm .icon-shape .label{text-anchor:middle;}#mermaid-svg-MASnH4dGDoEVJJzm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MASnH4dGDoEVJJzm .rough-node .label,#mermaid-svg-MASnH4dGDoEVJJzm .node .label,#mermaid-svg-MASnH4dGDoEVJJzm .image-shape .label,#mermaid-svg-MASnH4dGDoEVJJzm .icon-shape .label{text-align:center;}#mermaid-svg-MASnH4dGDoEVJJzm .node.clickable{cursor:pointer;}#mermaid-svg-MASnH4dGDoEVJJzm .root .anchor path{fill:#5B6B7C!important;stroke-width:0;stroke:#5B6B7C;}#mermaid-svg-MASnH4dGDoEVJJzm .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-MASnH4dGDoEVJJzm .edgePath .path{stroke:#5B6B7C;stroke-width:2.0px;}#mermaid-svg-MASnH4dGDoEVJJzm .flowchart-link{stroke:#5B6B7C;fill:none;}#mermaid-svg-MASnH4dGDoEVJJzm .edgeLabel{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-MASnH4dGDoEVJJzm .edgeLabel p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-MASnH4dGDoEVJJzm .edgeLabel rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-MASnH4dGDoEVJJzm .labelBkg{background-color:rgba(240.9999999999, 251, 231.9999999998, 0.5);}#mermaid-svg-MASnH4dGDoEVJJzm .cluster rect{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);stroke:hsl(31.5789473684, 30.3703703704%, 89.7058823529%);stroke-width:1px;}#mermaid-svg-MASnH4dGDoEVJJzm .cluster text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-MASnH4dGDoEVJJzm .cluster span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-MASnH4dGDoEVJJzm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);border:1px solid hsl(31.5789473684, 30.3703703704%, 89.7058823529%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MASnH4dGDoEVJJzm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#17324D;}#mermaid-svg-MASnH4dGDoEVJJzm rect.text{fill:none;stroke-width:0;}#mermaid-svg-MASnH4dGDoEVJJzm .icon-shape,#mermaid-svg-MASnH4dGDoEVJJzm .image-shape{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-MASnH4dGDoEVJJzm .icon-shape p,#mermaid-svg-MASnH4dGDoEVJJzm .image-shape p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);padding:2px;}#mermaid-svg-MASnH4dGDoEVJJzm .icon-shape .label rect,#mermaid-svg-MASnH4dGDoEVJJzm .image-shape .label rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-MASnH4dGDoEVJJzm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MASnH4dGDoEVJJzm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MASnH4dGDoEVJJzm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-MASnH4dGDoEVJJzm .dts>*{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-MASnH4dGDoEVJJzm .dts span{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-MASnH4dGDoEVJJzm .dts tspan{fill:#17324D!important;}#mermaid-svg-MASnH4dGDoEVJJzm .kernel>*{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-MASnH4dGDoEVJJzm .kernel span{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-MASnH4dGDoEVJJzm .kernel tspan{fill:#123F33!important;}#mermaid-svg-MASnH4dGDoEVJJzm .sys>*{fill:#FFF4E0!important;stroke:#D99A2B!important;stroke-width:1.5px!important;color:#5A3B00!important;}#mermaid-svg-MASnH4dGDoEVJJzm .sys span{fill:#FFF4E0!important;stroke:#D99A2B!important;stroke-width:1.5px!important;color:#5A3B00!important;}#mermaid-svg-MASnH4dGDoEVJJzm .sys tspan{fill:#5A3B00!important;}#mermaid-svg-MASnH4dGDoEVJJzm .dev>*{fill:#F1E8FB!important;stroke:#7E57C2!important;stroke-width:1.5px!important;color:#2E1A47!important;}#mermaid-svg-MASnH4dGDoEVJJzm .dev span{fill:#F1E8FB!important;stroke:#7E57C2!important;stroke-width:1.5px!important;color:#2E1A47!important;}#mermaid-svg-MASnH4dGDoEVJJzm .dev tspan{fill:#2E1A47!important;} DTS 节点

compatible / reg / interrupts
platform device
uio_pdrv_genirq

of_id 匹配
uio_register_device
/sys/class/uio/uioN

name / maps / event
udev
/dev/uioN
request_threaded_irq

图 4:DTS 只描述资源。/dev/uioN 是驱动 probe 后注册字符设备,再由 udev 创建的。

uioN 的编号由注册顺序决定,不是稳定标识。今天 DMA 是 uio0,明天改一个节点顺序就可能变成 uio1。可靠的做法是按 name 查找,或者用 udev 规则建固定软链接:

text 复制代码
SUBSYSTEM=="uio", ATTR{name}=="fpga-irq", \
  SYMLINK+="fpga-irq", MODE="0660", GROUP="fpga"

3.2 内核接住中断以后

uio_pdrv_genirq 的中断处理函数很短2,核心动作只有一个:在中断控制器里屏蔽这根中断线,避免在用户态处理期间反复进入。

c 复制代码
static irqreturn_t uio_pdrv_genirq_handler(int irq, struct uio_info *info)
{
    disable_irq_nosync(irq);
    return IRQ_HANDLED;
}

static int uio_pdrv_genirq_irqcontrol(struct uio_info *info, s32 on)
{
    if (on)
        enable_irq(info->irq);
    else
        disable_irq_nosync(info->irq);
    return 0;
}

上面的代码省略了锁和状态位。主线实现用 UIO_IRQ_DISABLED 位记录当前状态,保证 enable_irq 和 disable_irq_nosync 成对出现,避免中断嵌套深度被破坏。disable_irq_nosync 不会等待 threaded handler 完成,适合在硬中断上下文调用;会睡眠的 disable_irq 不能放在硬中断里。

UIO 核心并没有在硬中断里直接唤醒用户态14。request_threaded_irq 把处理拆成两段:硬中断只调用驱动的 handler,返回 IRQ_WAKE_THREAD;真正的通知在 threaded IRQ 里完成。threaded IRQ 跑在内核线程上下文,可以睡眠,适合做唤醒和较重的收尾。

c 复制代码
atomic_inc(&idev->event);
wake_up_interruptible(&idev->wait);
kill_fasync(&idev->async_queue, SIGIO, POLL_IN);

event 是一个累计计数3。用户态 read 返回的是这个计数的当前值,而不是"本次中断携带的数据量"。如果用户态处理得慢,三次中断可能只换来一次唤醒,读到的计数直接跳了三下。这不是丢中断,而是事件被合并了。

可以把 event 想成前台的计数器:它只告诉你今天来过多少位访客,不会替你排队,也不会告诉你每位访客要办什么事。真正的工作内容要去读设备状态。

write 走另一条路径:它把 4 字节的整数传给驱动的 irqcontrol,0 表示屏蔽,1 表示重新使能。写入长度不是 4 字节会直接返回 -EINVAL。
#mermaid-svg-Qp6SlE8c4uel3f04{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#17324D;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Qp6SlE8c4uel3f04 .error-icon{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);}#mermaid-svg-Qp6SlE8c4uel3f04 .error-text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);stroke:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Qp6SlE8c4uel3f04 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Qp6SlE8c4uel3f04 .marker{fill:#5B6B7C;stroke:#5B6B7C;}#mermaid-svg-Qp6SlE8c4uel3f04 .marker.cross{stroke:#5B6B7C;}#mermaid-svg-Qp6SlE8c4uel3f04 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Qp6SlE8c4uel3f04 p{margin:0;}#mermaid-svg-Qp6SlE8c4uel3f04 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#17324D;}#mermaid-svg-Qp6SlE8c4uel3f04 .cluster-label text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-Qp6SlE8c4uel3f04 .cluster-label span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-Qp6SlE8c4uel3f04 .cluster-label span p{background-color:transparent;}#mermaid-svg-Qp6SlE8c4uel3f04 .label text,#mermaid-svg-Qp6SlE8c4uel3f04 span{fill:#17324D;color:#17324D;}#mermaid-svg-Qp6SlE8c4uel3f04 .node rect,#mermaid-svg-Qp6SlE8c4uel3f04 .node circle,#mermaid-svg-Qp6SlE8c4uel3f04 .node ellipse,#mermaid-svg-Qp6SlE8c4uel3f04 .node polygon,#mermaid-svg-Qp6SlE8c4uel3f04 .node path{fill:#E8F1FB;stroke:#3B82C4;stroke-width:1px;}#mermaid-svg-Qp6SlE8c4uel3f04 .rough-node .label text,#mermaid-svg-Qp6SlE8c4uel3f04 .node .label text,#mermaid-svg-Qp6SlE8c4uel3f04 .image-shape .label,#mermaid-svg-Qp6SlE8c4uel3f04 .icon-shape .label{text-anchor:middle;}#mermaid-svg-Qp6SlE8c4uel3f04 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Qp6SlE8c4uel3f04 .rough-node .label,#mermaid-svg-Qp6SlE8c4uel3f04 .node .label,#mermaid-svg-Qp6SlE8c4uel3f04 .image-shape .label,#mermaid-svg-Qp6SlE8c4uel3f04 .icon-shape .label{text-align:center;}#mermaid-svg-Qp6SlE8c4uel3f04 .node.clickable{cursor:pointer;}#mermaid-svg-Qp6SlE8c4uel3f04 .root .anchor path{fill:#5B6B7C!important;stroke-width:0;stroke:#5B6B7C;}#mermaid-svg-Qp6SlE8c4uel3f04 .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-Qp6SlE8c4uel3f04 .edgePath .path{stroke:#5B6B7C;stroke-width:2.0px;}#mermaid-svg-Qp6SlE8c4uel3f04 .flowchart-link{stroke:#5B6B7C;fill:none;}#mermaid-svg-Qp6SlE8c4uel3f04 .edgeLabel{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-Qp6SlE8c4uel3f04 .edgeLabel p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-Qp6SlE8c4uel3f04 .edgeLabel rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-Qp6SlE8c4uel3f04 .labelBkg{background-color:rgba(240.9999999999, 251, 231.9999999998, 0.5);}#mermaid-svg-Qp6SlE8c4uel3f04 .cluster rect{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);stroke:hsl(31.5789473684, 30.3703703704%, 89.7058823529%);stroke-width:1px;}#mermaid-svg-Qp6SlE8c4uel3f04 .cluster text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-Qp6SlE8c4uel3f04 .cluster span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-Qp6SlE8c4uel3f04 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);border:1px solid hsl(31.5789473684, 30.3703703704%, 89.7058823529%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Qp6SlE8c4uel3f04 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#17324D;}#mermaid-svg-Qp6SlE8c4uel3f04 rect.text{fill:none;stroke-width:0;}#mermaid-svg-Qp6SlE8c4uel3f04 .icon-shape,#mermaid-svg-Qp6SlE8c4uel3f04 .image-shape{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-Qp6SlE8c4uel3f04 .icon-shape p,#mermaid-svg-Qp6SlE8c4uel3f04 .image-shape p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);padding:2px;}#mermaid-svg-Qp6SlE8c4uel3f04 .icon-shape .label rect,#mermaid-svg-Qp6SlE8c4uel3f04 .image-shape .label rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-Qp6SlE8c4uel3f04 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Qp6SlE8c4uel3f04 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Qp6SlE8c4uel3f04 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-Qp6SlE8c4uel3f04 .hw>*{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .hw span{fill:#FDE8E8!important;stroke:#C0392B!important;stroke-width:1.5px!important;color:#5A1A14!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .hw tspan{fill:#5A1A14!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .kernel>*{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .kernel span{fill:#E6F6F1!important;stroke:#2F9E7A!important;stroke-width:1.5px!important;color:#123F33!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .kernel tspan{fill:#123F33!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .user>*{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .user span{fill:#E8F1FB!important;stroke:#3B82C4!important;stroke-width:1.5px!important;color:#17324D!important;}#mermaid-svg-Qp6SlE8c4uel3f04 .user tspan{fill:#17324D!important;} 设备拉高中断线
UIO 硬中断

disable_irq_nosync
threaded IRQ

event++ / wake_up
用户态 read 返回事件计数
清中断源

读取工作状态
处理数据或故障
write 1 重新使能
enable_irq

图 5:一次完整的中断生命周期。屏蔽发生在内核,清源和处理发生在用户态,重新使能由用户态显式触发。

这里有一个评审时必须确认的点:有些实现会在初始化时写一次 1 来确保中断处于使能状态,但这不代表以后每次都能自动续上。使用通用 uio_pdrv_genirq 时,处理完必须再次 write(1),否则第二次中断永远不会到达。

触发方式也要注意。电平触发表示中断线一直保持高电平,直到设备清掉中断源;边沿触发是短脉冲。电平触发如果先 write(1) 再清源,会立刻再次进入;边沿触发如果丢了脉冲,往往要读状态寄存器补救。

3.3 用户态怎么等、怎么访问

用户态先打开设备文件,然后在循环里等待事件:

c 复制代码
int fd = open("/dev/uio0", O_RDWR);
uint32_t count;

for (;;) {
    if (read(fd, &count, sizeof(count)) != sizeof(count))
        break;

    clear_device_irq();
    drain_work();

    uint32_t on = 1;
    write(fd, &on, sizeof(on));
}

fd 是打开 /dev/uioN 后拿到的整数句柄。read 默认阻塞,直到事件计数发生变化;也可以用 poll 加超时,避免线程永久卡死。count 可以用来判断是否漏掉了唤醒:如果两次 read 之间计数增长远大于处理次数,说明处理线程被调度延迟了,或者中断率超过了用户态消费能力。

寄存器访问通常通过 mmap:

c 复制代码
void *regs = mmap(NULL, map_size, PROT_READ | PROT_WRITE,
                  MAP_SHARED, fd, 0);  /* map0 */

UIO 的 mmap 偏移不是字节地址,而是映射编号乘以页大小。map0 用 0,map1 用 getpagesize(),依此类推。物理内存映射会被内核标记为 non-cached,适合 MMIO;如果映射的是普通内存,还要考虑缓存一致性和屏障。

4. 工程细节:按事件类型处理

4.1 四类事件的差异

四类事件的处理方式不太一样,可以先看一张表:

事件源 触发条件 真正的工作来源 处理要点
DMA 块完成,或写指针逼近读指针 环形读写索引 计算环深、检测溢出、连续消费
电机 转速低于阈值 转速与状态寄存器 关闭输出、等待恢复、幂等去重
上盖互锁 上盖被打开 互锁状态 锁存故障、禁止出光、恢复后重试
协处理器 事件寄存器置位 事件位 有界批处理、逐位清除

这张表里没有"事件计数"这一列,因为它只负责把线程叫醒,不负责说明要做什么。

4.2 DMA:索引比中断计数可靠

DMA 数据通常被切成 N 个块,设备往块里写,用户态从块里读。写指针追上读指针就是溢出,说明生产速度超过了消费速度。

处理函数先读 write_index 和 read_index。索引按环深取模比较:相等表示没有新数据;写索引加一等于读索引表示环已满,通常保留一个空槽区分空满;写索引加二等于读索引时,只剩一个块的安全余量,再不来及消费就要覆盖。

text 复制代码
if (write_index + 2 == read_index)
    buffer_overrun = true;

while (read_index != write_index)
    consume_one_block(read_index++);

环形深度要按最坏情况算:

text 复制代码
N > 生产速率 × 用户态最坏响应时间 + 安全余量

生产速率是每秒写入的块数,响应时间包括中断屏蔽、threaded IRQ、唤醒、调度、处理和重新使能。UIO 路径比纯内核线程多了 threaded IRQ 和用户态调度,这个值不能按硬中断延迟估算。

事件计数在这里只是门铃。一次唤醒可能对应多个块,用户态必须一直消费到读指针追上写指针。反过来,如果按"一次 read 消费一个块"写代码,积压会越来越多,最后触发溢出。

如果 DMA 块率很高,用户态每次唤醒的开销会变得明显。这时可以考虑合并中断、一次唤醒消费多个块,或者把数据面消费者下沉到内核,只把控制面留给用户态。

4.3 电机与互锁:先把系统带到安全状态

电机转速过低和上盖互锁是典型的故障中断。它们的处理不能只做一次判断,而要经过一条完整的状态迁移:

  1. 记录中断并做去重,避免重复上报同一个故障。
  2. 屏蔽该中断源,防止故障持续期间反复进入。
  3. 更新故障状态寄存器,向上一层上报故障。
  4. 关闭输出,进入安全状态。
  5. 读取电机转速或互锁状态,等待条件恢复。
  6. 条件恢复后恢复输出、清除故障、重新使能中断。

这条链路里有两个要求。处理必须幂等,重复中断不能重复关闭输出、重复上报,否则日志和上层状态机会被刷爆。重新使能必须放在清源之后,如果先 write(1) 再清故障,电平触发的中断线仍然是高电平,会立刻再次进入。

更关键的是失效安全。上面的"关闭输出"发生在用户态,如果进程崩溃、线程被卡死或者调度饥饿,输出可能保持开启。安全相关设计不能只依赖用户态存活,至少要有独立看门狗、内核侧 GPIO 兜底,或者硬件在心跳丢失时自动切断输出使能。

4.4 协处理器:一次中断处理有限个事件

这里的协处理器指执行具体计算或状态机的处理单元,例如 FPGA 逻辑、DSP 或从核;它不是 GIC/NVIC 这类中断控制器。后者只负责汇总和转发中断,不产生业务事件。

协处理器的事件中断通常对应一个事件寄存器,里面可能同时置起多位。合理做法是在一次中断里做有界批处理:读事件寄存器,逐位处理,清位,循环到上限。

text 复制代码
for (i = 0; i < BATCH_LIMIT; i++) {
    events = read_event_register();
    if (events == 0)
        break;
    handle_events(events);
    clear_event_bits(events);
}

上限的作用是给单次中断处理封顶,避免用户态线程长时间占用 CPU,影响其他实时任务。代价是超出上限的事件要留到下一次中断或兜底扫描。这里不能盲目清掉未处理的事件位,否则会静默丢故障。

4.5 线程、优先级和观测

多个 UIO 设备可以各有自己的中断线程,也可以由一个线程 poll 所有 fd。每设备一线程的故障隔离好、单类事件延迟低,但线程多、调度和栈开销大;单线程 poll 实现简单、上下文切换少,但一类事件处理慢会拖住其他类。

中断线程通常配置为实时调度,优先级高于状态机和网络线程。但优先级高不等于可以在里面做重活:日志落盘、网络上报、复杂计算都应该交给低优先级工作线程,中断线程只做清源、取状态、投递消息。
#mermaid-svg-FZoWwocy2Hlvvc6E{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#17324D;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FZoWwocy2Hlvvc6E .error-icon{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);}#mermaid-svg-FZoWwocy2Hlvvc6E .error-text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);stroke:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FZoWwocy2Hlvvc6E .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FZoWwocy2Hlvvc6E .marker{fill:#5B6B7C;stroke:#5B6B7C;}#mermaid-svg-FZoWwocy2Hlvvc6E .marker.cross{stroke:#5B6B7C;}#mermaid-svg-FZoWwocy2Hlvvc6E svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FZoWwocy2Hlvvc6E p{margin:0;}#mermaid-svg-FZoWwocy2Hlvvc6E .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#17324D;}#mermaid-svg-FZoWwocy2Hlvvc6E .cluster-label text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-FZoWwocy2Hlvvc6E .cluster-label span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-FZoWwocy2Hlvvc6E .cluster-label span p{background-color:transparent;}#mermaid-svg-FZoWwocy2Hlvvc6E .label text,#mermaid-svg-FZoWwocy2Hlvvc6E span{fill:#17324D;color:#17324D;}#mermaid-svg-FZoWwocy2Hlvvc6E .node rect,#mermaid-svg-FZoWwocy2Hlvvc6E .node circle,#mermaid-svg-FZoWwocy2Hlvvc6E .node ellipse,#mermaid-svg-FZoWwocy2Hlvvc6E .node polygon,#mermaid-svg-FZoWwocy2Hlvvc6E .node path{fill:#E8F1FB;stroke:#3B82C4;stroke-width:1px;}#mermaid-svg-FZoWwocy2Hlvvc6E .rough-node .label text,#mermaid-svg-FZoWwocy2Hlvvc6E .node .label text,#mermaid-svg-FZoWwocy2Hlvvc6E .image-shape .label,#mermaid-svg-FZoWwocy2Hlvvc6E .icon-shape .label{text-anchor:middle;}#mermaid-svg-FZoWwocy2Hlvvc6E .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FZoWwocy2Hlvvc6E .rough-node .label,#mermaid-svg-FZoWwocy2Hlvvc6E .node .label,#mermaid-svg-FZoWwocy2Hlvvc6E .image-shape .label,#mermaid-svg-FZoWwocy2Hlvvc6E .icon-shape .label{text-align:center;}#mermaid-svg-FZoWwocy2Hlvvc6E .node.clickable{cursor:pointer;}#mermaid-svg-FZoWwocy2Hlvvc6E .root .anchor path{fill:#5B6B7C!important;stroke-width:0;stroke:#5B6B7C;}#mermaid-svg-FZoWwocy2Hlvvc6E .arrowheadPath{fill:#0b0b0b;}#mermaid-svg-FZoWwocy2Hlvvc6E .edgePath .path{stroke:#5B6B7C;stroke-width:2.0px;}#mermaid-svg-FZoWwocy2Hlvvc6E .flowchart-link{stroke:#5B6B7C;fill:none;}#mermaid-svg-FZoWwocy2Hlvvc6E .edgeLabel{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-FZoWwocy2Hlvvc6E .edgeLabel p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-FZoWwocy2Hlvvc6E .edgeLabel rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-FZoWwocy2Hlvvc6E .labelBkg{background-color:rgba(240.9999999999, 251, 231.9999999998, 0.5);}#mermaid-svg-FZoWwocy2Hlvvc6E .cluster rect{fill:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);stroke:hsl(31.5789473684, 30.3703703704%, 89.7058823529%);stroke-width:1px;}#mermaid-svg-FZoWwocy2Hlvvc6E .cluster text{fill:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-FZoWwocy2Hlvvc6E .cluster span{color:rgb(0.2222222223, 0.7222222223, 1.277777778);}#mermaid-svg-FZoWwocy2Hlvvc6E div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(31.5789473684, 70.3703703704%, 99.7058823529%);border:1px solid hsl(31.5789473684, 30.3703703704%, 89.7058823529%);border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FZoWwocy2Hlvvc6E .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#17324D;}#mermaid-svg-FZoWwocy2Hlvvc6E rect.text{fill:none;stroke-width:0;}#mermaid-svg-FZoWwocy2Hlvvc6E .icon-shape,#mermaid-svg-FZoWwocy2Hlvvc6E .image-shape{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);text-align:center;}#mermaid-svg-FZoWwocy2Hlvvc6E .icon-shape p,#mermaid-svg-FZoWwocy2Hlvvc6E .image-shape p{background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);padding:2px;}#mermaid-svg-FZoWwocy2Hlvvc6E .icon-shape .label rect,#mermaid-svg-FZoWwocy2Hlvvc6E .image-shape .label rect{opacity:0.5;background-color:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);fill:hsl(91.5789473684, 70.3703703704%, 94.7058823529%);}#mermaid-svg-FZoWwocy2Hlvvc6E .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FZoWwocy2Hlvvc6E .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FZoWwocy2Hlvvc6E :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-FZoWwocy2Hlvvc6E .row>*{fill:#F3F4F6!important;stroke:#9AA4B2!important;color:#17324D!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .row span{fill:#F3F4F6!important;stroke:#9AA4B2!important;color:#17324D!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .row tspan{fill:#17324D!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .user>*{fill:#E8F1FB!important;stroke:#3B82C4!important;color:#17324D!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .user span{fill:#E8F1FB!important;stroke:#3B82C4!important;color:#17324D!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .user tspan{fill:#17324D!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .dec>*{fill:#FFF4E0!important;stroke:#D99A2B!important;color:#5A3B00!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .dec span{fill:#FFF4E0!important;stroke:#D99A2B!important;color:#5A3B00!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .dec tspan{fill:#5A3B00!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .warn>*{fill:#FDE8E8!important;stroke:#C0392B!important;color:#5A1A14!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .warn span{fill:#FDE8E8!important;stroke:#C0392B!important;color:#5A1A14!important;}#mermaid-svg-FZoWwocy2Hlvvc6E .warn tspan{fill:#5A1A14!important;} 是
否
是
否
DMA

读 write / read 索引
满?
标记 overrun
消费到写索引
电机 / 互锁

屏蔽并去重
上报故障
关闭输出
恢复?
恢复输出并重新使能
协处理器

读事件寄存器
处理置位
清除事件位
达上限?

图 6:三类事件的处理主线。DMA 看环形索引,电机和互锁看状态,协处理器看事件位;共同点都不是看 UIO 的事件计数。

排查问题时,几个观测点很有用:

  1. /proc/interrupts 看内核是否收到中断,计数是否增长。
  2. /sys/class/uio/uioN/name 确认设备名和绑定关系。
  3. 用户态读到的 event 计数,看是否发生合并或漏唤醒。
  4. ftrace 的 irq:irq_handler_entry/exit 和 sched:sched_switch,看硬中断、线程唤醒和用户态调度的时间分布。

如果 /proc/interrupts 增长而用户态计数不增长,先看中断是否被屏蔽、write(1) 是否执行、read 是否阻塞在错误的 fd 上。如果用户态计数跳变很大,先看处理线程的调度延迟和单次处理耗时。

5. 小结

UIO 把"接中断"和"处理中断"拆开:内核负责接住、屏蔽、唤醒,用户态负责清源、处理、重新使能。它适合事件不多、业务复杂、需要快速迭代的设备;不适合把高频 DMA 中断当成普通事件逐个唤醒。

6. 上线前检查清单

上线前我会先确认几件事:

  1. DTS 的 compatible 和启动参数 uio_pdrv_genirq.of_id 是否一致。
  2. 每个 UIO 节点是否只对应一根中断线,是否存在共享中断。
  3. 用户态是否按 name 查找设备,是否避免硬编码 uioN。
  4. 每次中断处理后是否 write(1),是否在清源之后执行。
  5. DMA 环形深度是否按最坏用户态延迟计算,是否保留安全余量。
  6. 用户态死亡或卡死时,是否有独立看门狗或硬件兜底进入安全状态。

7. 参考资料

  1. The Userspace I/O HOWTO, Linux kernel documentation. https://docs.kernel.org/driver-api/uio-howto.html
  2. drivers/uio/uio_pdrv_genirq.c, Linux kernel source. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/uio/uio_pdrv_genirq.c
  3. drivers/uio/uio.c, Linux kernel source. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/uio/uio.c
  4. Generic IRQ handling, Linux kernel documentation. https://docs.kernel.org/core-api/genericirq.html
  5. Devicetree Specification. https://www.devicetree.org/specifications/
相关推荐
承渊政道1 小时前
Linux网络学习【从深入理解网络层原理到IP分片与重组全过程剖析】
linux·计算机网络·ubuntu·ip分片与重组
~城南花已开~1 小时前
Redfish 事件接收服务搭建指南(Ubuntu Server 实测)
linux·服务器·python·ubuntu
倔强的石头1062 小时前
【Linux指南】进程通信系列(四):命名管道 FIFO —— 让陌生进程按名字见面
linux·服务器·网络
段一凡-华北理工大学2 小时前
高炉能源系统智能化:热风炉、煤气与极致能效~系列文章03:热风炉烧炉与风温的决定因素
大数据·linux·人工智能·能源·热风炉·高炉能源优化
_小许_2 小时前
Linux 常用命令速查:CentOS 与 Ubuntu 双线对照
linux·ubuntu·centos
码农客栈2 小时前
mt7601u移植
linux·运维·服务器
2601_949847752 小时前
io-ts 跨端三方库仓颉适配全流程
linux·运维·ubuntu
readhere13 小时前
从ZYNQ7000开发板,学会启动嵌入式Linux系统——入门学习:EBAZ4205篇目录
linux·启动·嵌入式linux·zynq7000·ebaz4205
打工仔折腾 AI14 小时前
在 Ubuntu ARM64 部署 Q1 V3.0 MuJoCo 模型:加载、单步推理与执行器响应解析
linux·运维·人工智能·python·深度学习·ubuntu·性能优化