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() 等待中断事件,实现了“内核处理中断,用户态处理数据”的分离模型。
字符设备访问路径(传统 vs UIO) 用户态应用程序 read/write/ioctl VFS + 字符设备驱动 硬件设备 用户态(DPDK) mmap 直接映射 UIO 字符设备 硬件寄存器/内存 传统路径:每次 I/O 需系统调用 → 上下文切换。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→用户态)和多次上下文切换,是高性能网络应用的主要瓶颈。

网络设备数据路径:内核协议栈 vs DPDK 直通 标准内核路径 NIC 硬件 DMA → Ring Buffer 协议栈处理 socket 缓冲区 拷贝① 拷贝②③ 拷贝④ DPDK 直通路径 NIC 硬件 UIO/VFIO 映射 用户态 PMD 驱动 DPDK 应用 零拷贝(mmap) 无系统调用 传统网络路径:4 次数据拷贝 + 多次上下文切换 + 协议栈处理 DPDK 直通路径:0 次拷贝(硬件→用户态直接 DMA)+ 0 次内核上下文切换 代价:用户态需自行处理中断、轮询、内存管理

3.5 用户态访问硬件的传统障碍

综合三类设备模型,用户态程序直接访问硬件面临以下核心障碍:

  1. 权限隔离:用户态无法执行 in/out 指令或访问物理内存地址,所有硬件访问必须通过内核系统调用。
  2. 上下文切换:每次 read/write/ioctl 都涉及用户态↔内核态切换,延迟约 50-100ns,高频率下不可接受。
  3. 数据拷贝:内核与用户态之间的数据传递需要 copy_from_user/copy_to_user,增加 CPU 开销和缓存污染。
  4. 中断处理:硬件中断由内核处理,用户态无法直接响应,需通过信号或事件通知机制,增加延迟抖动。
  5. 缓存一致性:内核可能对数据做缓存(如 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 能力,将网络设备“伪装”成字符设备暴露给用户态。内核仍然管理设备枚举和中断,但数据路径完全在用户态闭环。