4. URB(USB Request Block)机制:生命周期、分配/提交/完成回调与传输映射
URB(USB Request Block)是 USB 主机控制器驱动与 USB 设备驱动之间传递 I/O 请求的核心数据结构。理解 URB 的完整生命周期、分配与提交机制、完成回调处理以及不同传输类型(中断、批量、等时、控制)在 URB 标志位上的映射关系,是分析和优化 USB Host 驱动性能的基础。
核心要点:URB 是 USB 子系统中的“I/O 请求包”,类似于网络协议栈中的 sk_buff。每个 URB 代表一次或一组 USB 事务,其生命周期管理直接决定了数据传输的延迟与吞吐量。
4.1 URB 生命周期全景
一个 URB 从创建到销毁,经历以下关键阶段:
- 分配(Allocation): 由 USB 设备驱动或核心层通过
usb_alloc_urb()分配,初始化 URB 结构体。 - 初始化(Initialization): 填充 URB 的各个字段,包括端点、传输类型、数据缓冲区、回调函数、标志位等。
- 提交(Submission): 通过
usb_submit_urb()将 URB 提交给 USB 核心层,核心层将其加入主机控制器驱动(HCD)的调度队列。 - 调度与执行(Scheduling & Execution): HCD 根据传输类型和调度策略,将 URB 分解为一个或多个 USB 事务(Transaction),通过主机控制器硬件执行。
- 完成回调(Completion Callback): 事务执行完毕后,HCD 调用 URB 中注册的完成回调函数
complete,通知设备驱动处理结果。 - 回收(Recycling / Deallocation): 驱动在回调中处理完数据后,调用
usb_free_urb()释放 URB,或将其重新初始化并再次提交。
4.2 URB 分配与初始化
URB 的分配通过 usb_alloc_urb() 函数完成,该函数在 <linux/usb.h> 中声明:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
- iso_packets: 等时传输所需的包数量,非等时传输传 0。
- mem_flags: 内存分配标志,常用
GFP_KERNEL(进程上下文)或GFP_ATOMIC(中断上下文)。
初始化 URB 时,需要填充以下关键字段:
| 字段 | 类型 | 说明 |
|---|---|---|
dev |
struct usb_device * |
目标 USB 设备指针 |
pipe |
unsigned int |
端点管道,由 usb_snd/rcv*pipe() 宏生成 |
transfer_buffer |
void * |
数据缓冲区指针 |
transfer_buffer_length |
int |
缓冲区长度(字节) |
complete |
usb_complete_t |
完成回调函数指针 |
context |
void * |
回调上下文(通常指向请求私有数据) |
transfer_flags |
unsigned int |
传输标志位(见 4.4 节) |
interval |
int |
中断/等时传输的轮询间隔(帧/微帧) |
性能提示: 在批量传输场景中,尽量复用 URB 而非频繁分配/释放。使用 URB 池(如
usb_alloc_urb + 手动管理链表)可以减少内存分配开销,提升吞吐量。
4.3 URB 提交与完成回调
URB 提交通过 usb_submit_urb() 完成,该函数将 URB 加入 HCD 的调度队列并立即返回(异步)。
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
返回值:0 表示成功,负值表示错误(如 -ENOMEM、-EINVAL、-ESHUTDOWN)。
完成回调函数原型:
void (*complete)(struct urb *urb);
回调在以下上下文中执行:
- 中断上下文: 对于大多数 HCD,回调在硬件中断处理程序或 tasklet 中调用。因此回调中不能调用可能睡眠的函数(如
kmalloc(GFP_KERNEL)、mutex_lock())。 - 进程上下文: 某些 HCD 或使用 URB 的 wrapper(如
usb_bulk_msg())可能在进程上下文调用回调。
常见陷阱: 在回调中再次调用
usb_submit_urb() 是允许的,但必须使用 GFP_ATOMIC 标志,因为回调运行在原子上下文中。忘记这一点会导致内核调度器警告(scheduling while atomic)。
4.4 URB 标志位与传输类型映射
transfer_flags 字段控制 URB 的行为。不同传输类型(中断、批量、等时、控制)有各自的标志位组合要求。
| 标志位 | 值 | 适用传输类型 | 说明 |
|---|---|---|---|
URB_NO_TRANSFER_DMA_MAP |
0x0001 | 全部 | 缓冲区已做 DMA 映射,无需核心层再次映射 |
URB_NO_SETUP_DMA_MAP |
0x0002 | 控制 | setup packet 已做 DMA 映射 |
URB_ASYNC_UNLINK |
0x0004 | 全部 | 允许异步取消 URB,不等待 unlink 完成 |
URB_SHORT_NOT_OK |
0x0008 | 批量、中断 | 收到短包时视为错误,回调中 status 置为 -EREMOTEIO |
URB_ISO_ASAP |
0x0010 | 等时 | 等时传输尽快调度,而非等待指定帧 |
URB_FREE_BUFFER |
0x0040 | 全部 | 回调完成后自动释放 transfer_buffer |
URB_DIR_IN |
0x0200 | 全部 | 数据传输方向为设备到主机(由 pipe 自动设置,通常无需手动指定) |
URB_DIR_OUT |
0x0400 | 全部 | 数据传输方向为主机到设备 |
4.4.1 传输类型与标志位映射关系
4.5 批量传输 URB 优化要点
批量传输是 USB 大块数据(如存储、网络、视频)的主要传输类型。针对批量传输的 URB 优化,需要关注以下参数:
| 参数 | 推荐值 | 优化理由 |
|---|---|---|
transfer_buffer_length |
最大包大小 × N(如 512 × 32 = 16KB) | 增大 URB 数据载荷,减少 URB 提交次数,降低 CPU 开销 |
transfer_flags |
设置 URB_NO_TRANSFER_DMA_MAP(若使用预映射缓冲区) |
避免核心层重复进行 DMA 映射/解映射,减少总线锁定时间 |
number_of_packets |
1(批量传输固定为 1) | 批量传输不支持 scatter/gather 列表(除 USB 3.0 批量流外) |
| URB 复用 | 使用 URB 池,避免 usb_alloc_urb/usb_free_urb 频繁调用 |
减少内存分配/释放开销,降低延迟抖动 |
批量传输性能关键指标: 在 USB 2.0 批量传输中,理论最大吞吐量为 53.248 MB/s(480 Mbps × 90% 协议效率)。实际应用中,URB 大小和提交频率是决定能否接近此上限的核心因素。建议使用 64KB 或更大的 URB 缓冲区,并配合异步提交模式。
4.6 常见问题与调试方法
- URB 提交失败(-EINVAL): 检查
pipe是否与端点描述符匹配,transfer_buffer是否对齐(通常要求 4 字节对齐)。 - URB 超时(-ETIMEDOUT): 批量传输默认无超时,但 HCD 可能实现内部超时。检查硬件连接和设备状态。
- URB 被取消(-ECONNRESET / -ENOENT): 设备断开或驱动主动调用
usb_unlink_urb()导致。 - 回调中无法提交新 URB: 确认使用
GFP_ATOMIC,并检查是否在正确的上下文中。
调试工具: 使用
usbmon 或 ftrace 跟踪 URB 生命周期。通过 /sys/kernel/debug/usb/usbmon 可以捕获每个 URB 的提交、完成和错误事件,是定位批量传输性能瓶颈的利器。