2. URB数据结构深度解析:struct urb关键成员及其生命周期

URB(USB Request Block)是USB设备驱动与主机控制器之间的核心通信单元。理解 struct urb 的每个关键成员及其生命周期,是排查URB泄漏、优化传输性能的基础。本章将从数据结构定义出发,逐一剖析核心字段的作用、使用约束与生命周期管理要点。

核心观点:URB 的生命周期从分配(usb_alloc_urb)开始,经过初始化、提交(usb_submit_urb)、回调(complete callback)或取消(usb_unlink_urb),最终释放(usb_free_urb)。任何环节的遗漏都会导致泄漏。

2.1 struct urb 核心成员一览

以下表格列出了 struct urb 中最关键的成员,以及它们在驱动开发中的典型用途和注意事项。

成员名称 类型 方向 核心用途 生命周期关键点
pipe unsigned int 输入/输出 编码端点类型、方向、设备地址、端点号 提交前必须设置,提交后不应修改
transfer_buffer void * 输入/输出 指向数据缓冲区的指针 必须与 transfer_buffer_length 配合,DMA 安全要求
transfer_dma dma_addr_t 输入/输出 DMA 模式下的缓冲区物理地址 使用 URB_NO_TRANSFER_DMA_MAP 标志时有效
transfer_buffer_length int 输入 缓冲区字节长度 提交前设置,回调中读取实际传输长度
actual_length int 输出 实际传输的字节数(回调时有效) 仅在 complete 回调中读取
status int 输出 传输完成状态(0 成功,负值错误码) 回调中必须检查,决定是否重试或释放
complete usb_complete_t 输入 传输完成后的回调函数指针 提交前赋值,回调中执行清理或数据解析
context void * 输入 传递给回调函数的私有上下文指针 通常指向请求对应的设备结构或完成量
interval int 输入 中断/isochronous 传输的轮询间隔(ms) 仅对中断/等时端点有效
number_of_packets int 输入 等时传输的包数量 仅用于 isochronous 传输
iso_frame_desc[] struct usb_iso_packet_descriptor 输入/输出 等时传输的帧描述数组 每个帧的 offset/length/actual_length/status
开发提示:在调试URB泄漏时,优先检查 transfer_buffer 和 context 指向的内存是否在URB释放前被正确释放。这两个指针最容易导致“二次泄漏”。

2.2 pipe 成员:端点通信的编码器

pipe 是一个位编码的整数,它包含了USB通信所需的全部路由信息。内核提供了一组宏来构建和解析 pipe 值。

// 构建 pipe 的典型宏
unsigned int pipe = usb_sndctrlpipe(dev, 0);   // 控制输出端点0
unsigned int pipe = usb_rcvbulkpipe(dev, 1);   // 批量输入端点1
unsigned int pipe = usb_sndintpipe(dev, 2);    // 中断输出端点2
unsigned int pipe = usb_rcvisocpipe(dev, 3);   // 等时输入端点3

// 从 pipe 中提取信息(调试时常用)
unsigned int epnum = usb_pipeendpoint(pipe);
unsigned int dir    = usb_pipeout(pipe) ? USB_DIR_OUT : USB_DIR_IN;
unsigned int type   = usb_pipetype(pipe);      // PIPE_ISOCHRONOUS / PIPE_INTERRUPT / PIPE_CONTROL / PIPE_BULK
常见错误:在URB重用(reinit)时忘记重新设置 pipe,导致数据发往错误的端点。每次 usb_submit_urb 前都应确保 pipe 与当前传输意图一致。

2.3 transfer_buffer 与 DMA 安全

transfer_buffer 指向的数据缓冲区必须满足DMA一致性要求。对于批量/中断传输,推荐使用 usb_alloc_coherent 或 kmalloc 配合 GFP_KERNEL 标志。栈上分配的缓冲区(局部数组)绝对不能用于URB传输。

// 正确分配方式示例
struct urb *urb;
void *buf;

urb = usb_alloc_urb(0, GFP_KERNEL);
buf = kmalloc(size, GFP_KERNEL);   // 或 usb_alloc_coherent(dev, size, GFP_KERNEL, &urb->transfer_dma);

urb->transfer_buffer = buf;
urb->transfer_buffer_length = size;
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;  // 如果使用DMA地址

2.4 complete 与 context:回调机制的核心

complete 回调函数在URB传输完成(成功、失败或被取消)时被调用。回调运行在中断上下文或软中断上下文,因此不能执行阻塞操作。context 指针允许驱动将私有数据传递给回调函数,通常指向一个完成量(completion)或请求描述符。

// 典型的 complete 回调实现
static void my_urb_complete(struct urb *urb)
{
    struct my_device *mydev = (struct my_device *)urb->context;

    if (urb->status == 0) {
        // 传输成功,处理 urb->actual_length 字节数据
        process_received_data(mydev, urb->transfer_buffer, urb->actual_length);
    } else if (urb->status == -ECONNRESET) {
        // 被 usb_unlink_urb 取消,正常清理
    } else {
        // 其他错误,可能需要重试
        dev_err(&mydev->interface->dev, "URB failed with status %d\n", urb->status);
    }

    // 在回调中重新提交(循环URB)或释放
    if (mydev->running) {
        usb_submit_urb(urb, GFP_ATOMIC);
    } else {
        usb_free_urb(urb);
    }
}

2.5 URB 生命周期状态机

下图展示了URB从创建到销毁的完整状态转换。理解这个状态机是定位泄漏的关键:任何停留在“已提交”或“已取消但未释放”状态的URB都是潜在泄漏点。

URB 生命周期状态机 已分配 (Allocated) 已初始化 (Initialized) 已提交 (Submitted) 已完成 (Completed) 已取消 (Unlinked) 已释放 (Freed) usb_fill_int_urb / 手动赋值 usb_submit_urb complete 回调 (status=0) usb_unlink_urb usb_free_urb usb_free_urb ⚠ 泄漏高风险点 • 提交后未等待回调就释放 • 取消后忘记调用 usb_free_urb • 回调中未释放 transfer_buffer

2.6 生命周期管理最佳实践

  1. 分配与初始化配对:每个 usb_alloc_urb 必须对应一个 usb_free_urb。推荐在驱动探测时分配,在断开时释放。
  2. 提交前检查:调用 usb_submit_urb 前,确保 pipe、transfer_buffer、complete、context 均已正确赋值。
  3. 回调中释放:对于一次性URB,在 complete 回调中调用 usb_free_urb。对于循环URB,在驱动停止时先 usb_unlink_urb 再释放。
  4. 取消后的清理:usb_unlink_urb 返回后,URB 的 complete 回调仍可能被调用。必须在回调中处理 -ECONNRESET 状态,并最终释放URB。
  5. 缓冲区生命周期:transfer_buffer 指向的内存必须在URB完全释放(所有引用计数归零)后才能释放。使用 usb_free_coherent 或 kfree 时注意时序。
泄漏排查口诀:分配计数要匹配,提交之前查四件(pipe/buf/complete/context);回调之中清状态,取消之后不忘放。

2.7 关键成员生命周期对比表

下表总结了不同成员在URB生命周期各阶段的有效性和责任方。

成员 分配后 初始化后 提交后 回调中 释放前
pipe 未定义 必须设置 只读 只读 无需操作
transfer_buffer 未定义 必须设置 只读(内核可能修改内容) 读取数据 驱动释放
transfer_buffer_length 未定义 必须设置 只读 只读 无需操作
actual_length 未定义 未定义 内核写入 读取有效值 无需操作
status 未定义 未定义 内核写入 必须检查 无需操作
complete 未定义 必须设置 只读 可在此函数内 无需操作
context 未定义 建议设置 只读 读取使用 驱动释放(如指向动态内存)
调试技巧:在 complete 回调入口处打印 urb->status 和 urb->actual_length,可以快速定位传输失败或数据截断问题。使用 usb_urb_dir_in(urb) 宏判断传输方向。