尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
浅析Linux UIO驱动框架
文章目录概述UIO工作原理访问设备的内存响应设备中断UIO驱动框架实现struct uio_devicestrcut uio_infostruct uio_mem驱动API接口uio_register_deviceuio_event_notifyUIO设备访问打开UIO设备映射设备内存uio_mmap_physical监听中断事件相关参考本文基于Linux 6.6.101版本内核源码进行分析。概述UIO全称为用户空间IO(Userspace I/O)最早于Linux 2.6.32版本引入是Linux提供的一种在用户态空间实现设备驱动的框架。通过UIO框架用户态程序可以直接操控硬件设备从而旁路内核减少调用流程以此获得性能上的提升。UIO工作原理对于驱动程序来说核心职能是与硬件设备进行交互这里包含两个重要的基础功能访问设备内存响应设备中断要在用户态实现设备驱动就必须要解决这两个问题。由于在Linux的设计模型里硬件设备都是由内核驱动进行接管基于这一前提UIO驱动框架只在内核态保留了精简的驱动实现主要负责通过Sysfs和uio字符设备将硬件设备的资源信息暴露给用户态而用户态层获取到硬件资源信息后在用户空间实现完整的设备驱动如下是uio驱动框架的整体框图访问设备的内存Linux通过映射物理设备的内存到用户态来提供访问但是这种方法会引入安全性和可靠性的问题。UIO通过限制不相关的物理设备的映射改善了这个问题。由此基于UIO开发的用户态驱动不需要关心与内存映射相关的安全性和可靠性的问题。响应设备中断中断本身需要在内核处理因此针对这个限制还需要一个小的内核模块通过最基本的中断服务程序来处理。这个中断服务程序可以只是向操作系统确认中断或者关闭中断等最基础的操作剩下的具体操作可以在用户态处理。UIO驱动框架实现UIO驱动框架的实现并不复杂其核心功能是向用户空间暴露硬件设备的内存以及中断事件传递。UIO驱动框架提供了设备注册接口主要用于为每个需要实现用户态驱动的硬件设备生成对应的UIO字符设备后续用户态空间可以通过字符设备访问硬件资源。UIO驱动框架的核心数据结构及与UIO字符设备间的关联示意如下struct uio_devicestruct uio_device唯一对应一个硬件设备每个uio_device都会生成/dev/uioX字符设备。struct uio_device { struct module *owner; struct device dev; int minor; atomic_t event; struct fasync_struct *async_queue; wait_queue_head_t wait; struct uio_info *info; struct mutex info_lock; struct kobject *map_dir; struct kobject *portio_dir; };关键字段描述minor/dev/uioX的次设备号event中断事件计数wait中断事件等待队列进程对/dev/uioX发起read操作时UIO框架会将进程挂入等待队列中断触发时唤醒map_dir用于在sysfs中生成mmap信息。strcut uio_infostruct uio_info用于描述硬件设备的资源信息包括设备内存空间、中断信息以及特定于驱动的处理回调。struct uio_info { struct uio_device *uio_dev; const char *name; const char *version; struct uio_mem mem[MAX_UIO_MAPS]; struct uio_port port[MAX_UIO_PORT_REGIONS]; long irq; unsigned long irq_flags; void *priv; // 驱动的私有数据 irqreturn_t (*handler)(int irq, struct uio_info *dev_info); int (*mmap)(struct uio_info *info, struct vm_area_struct *vma); int (*open)(struct uio_info *info, struct inode *inode); int (*release)(struct uio_info *info, struct inode *inode); int (*irqcontrol)(struct uio_info *info, s32 irq_on); };关键字段功能描述如下mem设备内存信息描述包括物理地址和长度最多可以支持5段空间irq设备中断号UIO框架对中断的支持比较有限只能支持一个中断handler 驱动自定义的中断处理函数mmap驱动自定义的mmap回调通过mmap映射设备内存时会调用非空时irqcontrol驱动提供的中断控制函数用于打开或关闭中断通过/dev/uioX设备写0/1进行操作。struct uio_memstruct uio_mem用于描述设备内存信息。struct uio_mem { const char *name; phys_addr_t addr; unsigned long offs; resource_size_t size; int memtype; void __iomem *internal_addr; struct uio_map *map; };关键字段功能描述如下addr设备内存起始地址需要对齐到物理页面大小offs页面内偏移size设备内存长度memtype内存类型UIO驱动框架定义了相关的宏#define UIO_MEM_NONE 0 #define UIO_MEM_PHYS 1 #define UIO_MEM_LOGICAL 2 #define UIO_MEM_VIRTUAL 3 #define UIO_MEM_IOVA 4驱动API接口Linux提供了操作接口定义在include/linux/uio_driver.h用于设备驱动实现自己的uio驱动模块关键接口如下接口描述uio_register_device注册UIO设备uio_unregister_device去注册UIO设备devm_uio_register_device注册UIO设备uio_register_deviceuio_register_device负责注册新的UIO设备到UIO驱动框架中UIO驱动框架会负责生成/dev/uioX设备和sysfs文件系统内相关内容。int __uio_register_device(struct module *owner, struct device *parent, struct uio_info *info) { struct uio_device *idev; int ret 0; if (!uio_class_registered) return -EPROBE_DEFER; if (!parent || !info || !info-name || !info-version) return -EINVAL; info-uio_dev NULL; // 分配uio_device数据结构 idev kzalloc(sizeof(*idev), GFP_KERNEL); if (!idev) { return -ENOMEM; } // uio_device结构初始化 idev-owner owner; idev-info info; mutex_init(idev-info_lock); init_waitqueue_head(idev-wait); atomic_set(idev-event, 0); // 申请UIO字符设备的次设备号主设备号在UIO框架初始化的时候已经进行了分配 ret uio_get_minor(idev); if (ret) { kfree(idev); return ret; } device_initialize(idev-dev); idev-dev.devt MKDEV(uio_major, idev-minor); idev-dev.class uio_class; idev-dev.parent parent; idev-dev.release uio_device_release; dev_set_drvdata(idev-dev, idev); // 设置字符设备名/dev/uioXX表示子设备号 ret dev_set_name(idev-dev, uio%d, idev-minor); if (ret) goto err_device_create; ret device_add(idev-dev); if (ret) goto err_device_create; // 创建/dev/uioX字符设备 ret uio_dev_add_attributes(idev); if (ret) goto err_uio_dev_add_attributes; info-uio_dev idev; // 设备中断号有效时UIO框架会注册中断处理函数中断统一回调uio_interrupt if (info-irq (info-irq ! UIO_IRQ_CUSTOM)) { ret request_irq(info-irq, uio_interrupt, info-irq_flags, info-name, idev); if (ret) { info-uio_dev NULL; goto err_request_irq; } } return 0; err_request_irq: uio_dev_del_attributes(idev); err_uio_dev_add_attributes: device_del(idev-dev); err_device_create: uio_free_minor(idev-minor); put_device(idev-dev); return ret; }uio_event_notifyuio_event_notify用于中断事件通知其内部实现比较简单主要是增加事件计数然后唤醒挂在uio_device等待队列上的进程。void uio_event_notify(struct uio_info *info) { struct uio_device *idev info-uio_dev; atomic_inc(idev-event); wake_up_interruptible(idev-wait); // 唤醒等待进程 kill_fasync(idev-async_queue, SIGIO, POLL_IN); }UIO设备访问UIO字符设备是Linux内核提供给用户态驱动访问物理设备资源的入口用户态驱动可以使用标准文件的系统调用访问设备信息包括设备内存和设备中断资源。用户空间操作UIO设备的常规流程如下打开/dev/uioX字符设备获取文件描述符fd执行mmap系统调用映射指定的设备内存到进程地址空间关键传入参数为设备内存空间的索引完成mmap映射后用户态驱动可以访问硬件设备寄存器空间并对设备进行配置若用户态驱动需要处理中断事件执行read系统调用监听中断用户态驱动可以执行write调用控制中断的开关。/dev/uioX作为标准的字符设备执行read/write/mmap等系统调用时内核会调用uio_fios中注册的各类回调函数。uio_fios定义如下static const struct file_operations uio_fops { .owner THIS_MODULE, .open uio_open, .release uio_release, .read uio_read, .write uio_write, .mmap uio_mmap, .poll uio_poll, .fasync uio_fasync, .llseek noop_llseek, };打开UIO设备打开UIO设备的主要目的是为了获取文件描述符建立用户态驱动与UIO内核驱动间的信息访问通道。open调用最终会调用uio_openstatic int uio_open(struct inode *inode, struct file *filep) { struct uio_device *idev; struct uio_listener *listener; int ret 0; mutex_lock(minor_lock); // 根据设备号查找匹配的uio_device结构 idev idr_find(uio_idr, iminor(inode)); if (!idev) { ret -ENODEV; mutex_unlock(minor_lock); goto out; } get_device(idev-dev); mutex_unlock(minor_lock); // 获取模块引用 if (!try_module_get(idev-owner)) { ret -ENODEV; goto err_module_get; } listener kmalloc(sizeof(*listener), GFP_KERNEL); if (!listener) { ret -ENOMEM; goto err_alloc_listener; } listener-dev idev; listener-event_count atomic_read(idev-event); filep-private_data listener; mutex_lock(idev-info_lock); if (!idev-info) { mutex_unlock(idev-info_lock); ret -EINVAL; goto err_infoopen; } // 调用UIO设备驱动自定义open函数 if (idev-info-open) ret idev-info-open(idev-info, inode); mutex_unlock(idev-info_lock); if (ret) goto err_infoopen; return 0; err_infoopen: kfree(listener); err_alloc_listener: module_put(idev-owner); err_module_get: put_device(idev-dev); out: return ret; }映射设备内存用户态驱动通过mmap系统调用完成设备物理内存空间到进程虚拟地址空间的映射UIO设备最大可以支持5段MMIO空间上层可以指定空间的索引。mmap系统调用最终会调用uio驱动框架的uio_mmap函数进行实际的映射操作。static int uio_mmap(struct file *filep, struct vm_area_struct *vma) { struct uio_listener *listener filep-private_data; struct uio_device *idev listener-dev; int mi; unsigned long requested_pages, actual_pages; int ret 0; if (vma-vm_end vma-vm_start) return -EINVAL; vma-vm_private_data idev; mutex_lock(idev-info_lock); if (!idev-info) { ret -EINVAL; goto out; } mi uio_find_mem_index(vma); if (mi 0) { ret -EINVAL; goto out; } requested_pages vma_pages(vma); actual_pages ((idev-info-mem[mi].addr ~PAGE_MASK) idev-info-mem[mi].size PAGE_SIZE -1) PAGE_SHIFT; if (requested_pages actual_pages) { ret -EINVAL; goto out; } // 若UIO设备驱动自定义mmap函数uio_info-mmap则调用驱动自定义mmap进行内存映射 if (idev-info-mmap) { ret idev-info-mmap(idev-info, vma); goto out; } switch (idev-info-mem[mi].memtype) { case UIO_MEM_IOVA: case UIO_MEM_PHYS: // 映射设备物理内存主要是用这条路径 ret uio_mmap_physical(vma); break; case UIO_MEM_LOGICAL: case UIO_MEM_VIRTUAL: ret uio_mmap_logical(vma); break; default: ret -EINVAL; } out: mutex_unlock(idev-info_lock); return ret; }uio_mmap_physicaluio_mmap_physical完成设备内存空间到进程虚拟地址空间的映射。static int uio_mmap_physical(struct vm_area_struct *vma) { struct uio_device *idev vma-vm_private_data; int mi uio_find_mem_index(vma); struct uio_mem *mem; if (mi 0) return -EINVAL; mem idev-info-mem mi; if (mem-addr ~PAGE_MASK) return -ENODEV; if (vma-vm_end - vma-vm_start mem-size) return -EINVAL; vma-vm_ops uio_physical_vm_ops; // 这里要注意映射的属性由于是访问的设备内存不能使用Cache if (idev-info-mem[mi].memtype UIO_MEM_PHYS) vma-vm_page_prot pgprot_noncached(vma-vm_page_prot); return remap_pfn_range(vma, vma-vm_start, mem-addr PAGE_SHIFT, vma-vm_end - vma-vm_start, vma-vm_page_prot); }监听中断事件当用户空间需要监听硬件设备的中断事件时可以执行read系统调用在设备无中断时read默认会阻塞当硬件设备产生中断事件时文件描述符会切换到可读状态触发read系统调用返回用户空间由此感知到设备有中断进行中断响应。read系统调用到内核后会执行uio_read函数static ssize_t uio_read(struct file *filep, char __user *buf, size_t count, loff_t *ppos) { struct uio_listener *listener filep-private_data; struct uio_device *idev listener-dev; DECLARE_WAITQUEUE(wait, current); ssize_t retval 0; s32 event_count; if (count ! sizeof(s32)) return -EINVAL; // 将当前进程添加到中断事件等待队列上 add_wait_queue(idev-wait, wait); do { mutex_lock(idev-info_lock); if (!idev-info || !idev-info-irq) { retval -EIO; mutex_unlock(idev-info_lock); break; } mutex_unlock(idev-info_lock); // 设置当前进程状态为TASK_INTERRUPTIBLE set_current_state(TASK_INTERRUPTIBLE); event_count atomic_read(idev-event); if (event_count ! listener-event_count) { __set_current_state(TASK_RUNNING); if (copy_to_user(buf, event_count, count)) retval -EFAULT; else { listener-event_count event_count; retval count; } break; } // 若文件描述符是非阻塞式则直接返回 if (filep-f_flags O_NONBLOCK) { retval -EAGAIN; break; } if (signal_pending(current)) { retval -ERESTARTSYS; break; } // 触发调度此时当前进程会睡眠直到有中断事件或者进程信号 schedule(); } while (1); __set_current_state(TASK_RUNNING); remove_wait_queue(idev-wait, wait); return retval; }相关参考Linux UIOUserspace I/O驱动模型技术详解
RELATED

相关推荐

极简弹窗封装,基于 @kit.ArkUI 封装通用工具类

极简弹窗封装,基于 @kit.ArkUI 封装通用工具类

在日常鸿蒙开发中,弹窗是最高频的交互组件之一:操作反馈用 Toast、二次确认用对话框、加载状态用等待框、多选操作用底部菜单。原生 kit.ArkUI 虽然提供了完整的弹窗能力,但每次调用都要重复写参数、统一样式麻烦,页面多了之后维护…

📅 2026/11/11 11:33:17
关于我在腾讯阅文实习---Claude Code 使用心得

关于我在腾讯阅文实习---Claude Code 使用心得

从会聊天到能交付:AI 应用开发实习中,我这样用 Claude Code 本文记录的是个人在 AI 应用开发实习中的通用工作流。示例均为脱敏、抽象后的工程场景,不涉及任何业务代码、线上数据、内部账号、配额或公司流程。 刚开始用 Claude Code 时&#…

📅 2026/11/10 15:36:38
为什么顶尖咨询公司已全员切换Perplexity?——2024Q2内部培训材料首次公开(限时72小时)

为什么顶尖咨询公司已全员切换Perplexity?——2024Q2内部培训材料首次公开(限时72小时)

更多请点击: https://intelliparadigm.com 第一章:Perplexity的核心价值与咨询行业适配逻辑 Perplexity 作为基于语言模型的实时知识检索增强系统,其核心价值不在于替代人类专家,而在于将“信息发现效率”转化为“认知决策带宽”…

📅 2026/10/25 22:04:31
MORE NEWS

更多资讯

📰

电工证能跨省通用吗 2026河南最新政策解读

电工证能跨省通用吗 2026河南最新政策解读 之前考的证在外省能不能转过来?这是很多特种作业人员的共同疑问,特别是在河南以外的地区务工或生活的人群。2026年河南最新政策对电工证的跨省通用性和年审要求有了新的调整,本文将从政策变化、证书效力…

📰

电工证科目二选几科啊 在哪考能省时间不耽误干活

电工证科目二选几科啊 在哪考能省时间不耽误干活 工地太忙根本没时间复习考试,我天天扛着工具箱满工地跑,晚上回家连饭都顾不上吃,哪还有空看电工证科目二的教材?这事儿我太懂了,去年刚毕业的时候也是这样,想着先混个证,结果一查科目二要选几科,差点…

📰

珠海电工证哪家好考在哪考别再交冤枉钱

珠海电工证哪家好考在哪考别再交冤枉钱 怕考不过白交培训费,珠海电工证哪家好考,这是很多准备报考电工证的小伙伴最关心的问题。你不是一个人,大家都怕花钱又费力还考不过,白白浪费时间和金钱。其实,只要选对机构,避开坑,顺利拿到证不是问题。今天咱们…

📰

建筑电工证考试挂靠怎么报名全流程解析

建筑电工证考试挂靠怎么报名全流程解析 之前考的证在外省能不能转过来?这几乎是每个从外地来鹤壁做公路工程的电工都会问的问题。别急,今天就从报名到挂靠全流程给你讲明白,帮你把证书“搬”到本地,顺利上岗。 一、建筑电工证考试挂靠的核心流程 1.…

📰

低压电工证考三相电吗怎么考?考前押题必备干货

低压电工证考三相电吗怎么考?考前押题必备干货 急需特种作业证才能上岗找工作,现在考低压电工证是很多人的首选。但很多人心里都在打鼓:**低压电工证考三相电吗?**这个问题关系到你以后能不能胜任岗位、能不能顺利通过考试。今天就来详细说说低压电工…

📰

香港电工证有啥用途 电子证书怎么查

香港电工证有啥用途 电子证书怎么查 怕考不过白交培训费,这事儿真不是开玩笑。我之前带过一批人报考电工证,结果有好几个交完钱,连考试都没参加就退了。你可能也遇到过类似情况,培训费动不动几千块,要是考不过,钱就打水漂了。现在市面上各种电工证五花…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬