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 从创建到销毁,经历以下关键阶段:

  1. 分配(Allocation): 由 USB 设备驱动或核心层通过 usb_alloc_urb() 分配,初始化 URB 结构体。
  2. 初始化(Initialization): 填充 URB 的各个字段,包括端点、传输类型、数据缓冲区、回调函数、标志位等。
  3. 提交(Submission): 通过 usb_submit_urb() 将 URB 提交给 USB 核心层,核心层将其加入主机控制器驱动(HCD)的调度队列。
  4. 调度与执行(Scheduling & Execution): HCD 根据传输类型和调度策略,将 URB 分解为一个或多个 USB 事务(Transaction),通过主机控制器硬件执行。
  5. 完成回调(Completion Callback): 事务执行完毕后,HCD 调用 URB 中注册的完成回调函数 complete,通知设备驱动处理结果。
  6. 回收(Recycling / Deallocation): 驱动在回调中处理完数据后,调用 usb_free_urb() 释放 URB,或将其重新初始化并再次提交。
URB 生命周期流程图 分配 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 传输类型与标志位映射关系

传输类型与 URB 标志位映射 控制传输 批量传输 中断传输 等时传输 URB_NO_SETUP_DMA_MAP URB_SHORT_NOT_OK URB_SHORT_NOT_OK URB_ISO_ASAP 通用标志位 URB_NO_TRANSFER_DMA_MAP URB_ASYNC_UNLINK URB_FREE_BUFFER URB_DIR_IN / URB_DIR_OUT (由 pipe 自动设置, 通常无需手动指定)

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 的提交、完成和错误事件,是定位批量传输性能瓶颈的利器。