把硬件中断交给用户态: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 电机与互锁:先把系统带到安全状态
电机转速过低和上盖互锁是典型的故障中断。它们的处理不能只做一次判断,而要经过一条完整的状态迁移:
- 记录中断并做去重,避免重复上报同一个故障。
- 屏蔽该中断源,防止故障持续期间反复进入。
- 更新故障状态寄存器,向上一层上报故障。
- 关闭输出,进入安全状态。
- 读取电机转速或互锁状态,等待条件恢复。
- 条件恢复后恢复输出、清除故障、重新使能中断。
这条链路里有两个要求。处理必须幂等,重复中断不能重复关闭输出、重复上报,否则日志和上层状态机会被刷爆。重新使能必须放在清源之后,如果先 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 的事件计数。
排查问题时,几个观测点很有用:
/proc/interrupts看内核是否收到中断,计数是否增长。/sys/class/uio/uioN/name确认设备名和绑定关系。- 用户态读到的
event计数,看是否发生合并或漏唤醒。 - ftrace 的
irq:irq_handler_entry/exit和sched:sched_switch,看硬中断、线程唤醒和用户态调度的时间分布。
如果 /proc/interrupts 增长而用户态计数不增长,先看中断是否被屏蔽、write(1) 是否执行、read 是否阻塞在错误的 fd 上。如果用户态计数跳变很大,先看处理线程的调度延迟和单次处理耗时。
5. 小结
UIO 把"接中断"和"处理中断"拆开:内核负责接住、屏蔽、唤醒,用户态负责清源、处理、重新使能。它适合事件不多、业务复杂、需要快速迭代的设备;不适合把高频 DMA 中断当成普通事件逐个唤醒。
6. 上线前检查清单
上线前我会先确认几件事:
- DTS 的
compatible和启动参数uio_pdrv_genirq.of_id是否一致。 - 每个 UIO 节点是否只对应一根中断线,是否存在共享中断。
- 用户态是否按
name查找设备,是否避免硬编码uioN。 - 每次中断处理后是否
write(1),是否在清源之后执行。 - DMA 环形深度是否按最坏用户态延迟计算,是否保留安全余量。
- 用户态死亡或卡死时,是否有独立看门狗或硬件兜底进入安全状态。
7. 参考资料
- The Userspace I/O HOWTO, Linux kernel documentation. https://docs.kernel.org/driver-api/uio-howto.html
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.cdrivers/uio/uio.c, Linux kernel source. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/uio/uio.c- Generic IRQ handling, Linux kernel documentation. https://docs.kernel.org/core-api/genericirq.html
- Devicetree Specification. https://www.devicetree.org/specifications/