3. Linux 内核驱动模型简介:字符设备、块设备、网络设备
在深入 DPDK 的 UIO/VFIO 机制之前,必须理解 Linux 内核驱动模型的三大分类及其访问路径。传统设备驱动分为字符设备、块设备和网络设备,它们各自定义了不同的用户态交互接口,也构成了用户态直接访问硬件的主要障碍。
3.1 三大设备类型与驱动模型
Linux 内核通过统一的 file_operations 结构体抽象设备访问,但三类设备在数据流、缓存策略和系统调用接口上存在本质差异。
| 设备类型 | 典型设备 | 用户态接口 | 数据流特点 | 内核缓存 |
|---|---|---|---|---|
| 字符设备 | 串口、GPIO、UIO | open/read/write/ioctl |
顺序字节流,无固定块大小 | 通常无页缓存 |
| 块设备 | 硬盘、SSD、SD卡 | 通过文件系统间接访问 | 固定大小块,随机读写 | 页缓存(Page Cache) |
| 网络设备 | 以太网卡、WiFi | socket 系统调用 |
异步数据包收发 | sk_buff 队列 |
关键认知:DPDK 绕过的正是这三类设备的标准内核路径。UIO 将网卡注册为字符设备,VFIO 则通过 IOMMU 提供更安全的 DMA 映射,两者都跳过了网络设备的内核协议栈和块设备的页缓存。
3.2 字符设备驱动模型
字符设备是最简单的设备模型,也是 UIO 的基础。驱动通过注册 struct cdev 和 file_operations 暴露给用户态。
// 字符设备核心结构
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};
用户态通过 /dev/xxx 设备节点操作字符设备。每次 read/write 都会陷入内核,产生上下文切换——这是性能瓶颈之一。
UIO 的切入点:UIO 驱动将硬件寄存器映射到用户态(通过 mmap),中断处理仍由内核完成,但通过
read() 等待中断事件,实现了“内核处理中断,用户态处理数据”的分离模型。
3.3 块设备驱动模型
块设备以固定大小块(通常 512B/4KB)为单位进行 I/O,内核引入页缓存(Page Cache)作为中间层,导致用户态无法直接控制数据落盘时机和位置。
// 块设备 I/O 路径(简化)
应用程序 → write() → VFS → 页缓存(脏页) → pdflush/内核线程 →
块设备层(I/O调度器) → 块设备驱动 → 磁盘
DPDK 不直接操作块设备,但理解块设备模型有助于对比:块设备的内核路径比字符设备更长,包含 I/O 调度、合并、排序等环节,延迟不可控。DPDK 的“零拷贝”思想正是要消除这些中间层。
3.4 网络设备驱动模型
网络设备是 DPDK 最直接相关的设备类型。标准网络驱动通过 struct net_device 注册,数据包通过 sk_buff 结构体在内核协议栈中传递。
// 网络设备收包路径(标准内核)
NIC 硬件 → DMA 到 Ring Buffer → 硬中断 → 软中断 (NET_RX_SOFTIRQ) →
netif_receive_skb() → 协议栈(IP/TCP/UDP) → socket 缓冲区 →
用户态 recvfrom()
这条路径包含至少 4 次数据拷贝(NIC→内核缓冲区、内核→协议栈、协议栈→socket、socket→用户态)和多次上下文切换,是高性能网络应用的主要瓶颈。
3.5 用户态访问硬件的传统障碍
综合三类设备模型,用户态程序直接访问硬件面临以下核心障碍:
- 权限隔离:用户态无法执行
in/out指令或访问物理内存地址,所有硬件访问必须通过内核系统调用。 - 上下文切换:每次
read/write/ioctl都涉及用户态↔内核态切换,延迟约 50-100ns,高频率下不可接受。 - 数据拷贝:内核与用户态之间的数据传递需要
copy_from_user/copy_to_user,增加 CPU 开销和缓存污染。 - 中断处理:硬件中断由内核处理,用户态无法直接响应,需通过信号或事件通知机制,增加延迟抖动。
- 缓存一致性:内核可能对数据做缓存(如 Page Cache、sk_buff),用户态无法控制缓存策略和刷新时机。
DPDK 的破局思路:UIO 通过
mmap 将设备内存映射到用户态,绕过 read/write 拷贝;VFIO 进一步利用 IOMMU 提供 DMA 重映射,允许用户态直接控制 DMA 操作,同时保证隔离性。两者都通过“将设备注册为字符设备”的方式,绕过了网络设备和块设备的内核路径。
3.6 三类设备与 DPDK 驱动选择的关系
| 设备类型 | DPDK 是否绕过 | 绕过方式 | 对应 DPDK 机制 |
|---|---|---|---|
| 字符设备 | 部分绕过 | 保留字符设备接口,但通过 mmap 直接映射 | UIO 框架 |
| 块设备 | 完全绕过 | DPDK 不直接操作块设备,但 SPDK 使用 UIO/VFIO | SPDK + UIO/VFIO |
| 网络设备 | 完全绕过 | 将网卡注册为 UIO/VFIO 字符设备,用户态 PMD 直接控制 | UIO + igb_uio / VFIO + vfio-pci |
理解本质:DPDK 并没有创造新的设备类型,而是利用字符设备的
mmap 能力,将网络设备“伪装”成字符设备暴露给用户态。内核仍然管理设备枚举和中断,但数据路径完全在用户态闭环。