2. URB 数据结构详解

URB(USB Request Block)是 Linux USB 子系统中最核心的数据结构,它承载了主机与 USB 设备之间所有数据传输的请求与响应。无论是控制传输、批量传输、中断传输还是等时传输,最终都通过 struct urb 来封装。本章将深入剖析该结构的每个关键成员、传输标志位的含义、回调函数的执行机制以及缓冲区的管理策略。

2.1 struct urb 核心成员解析

struct urb 定义在 <linux/usb.h> 中,包含数十个成员。下表列出了驱动开发中最常操作的字段及其用途:

成员名称 类型 用途说明
dev struct usb_device * 指向目标 USB 设备结构体,URB 提交前必须赋值
pipe unsigned int 端点管道信息,包含端点号、方向、传输类型,通过 usb_rcvbulkpipe() 等宏构造
transfer_buffer void * 数据缓冲区的内核虚拟地址,用于存放待发送或接收的数据
transfer_buffer_length int 缓冲区长度(字节),决定本次传输的数据量
transfer_flags unsigned int 传输标志位,控制 DMA 映射、短包处理、中断上下文等行为
actual_length int 实际传输的字节数,在回调函数中读取,用于校验完整性
status int 传输完成状态,0 表示成功,负值为错误码(如 -EPIPE、-ENOENT)
complete usb_complete_t 回调函数指针,URB 完成(成功/失败/取消)时被调用
context void * 回调函数的私有上下文,通常指向请求对应的上层结构体
interval int 中断/等时传输的轮询间隔(单位:帧/微帧),批量传输通常设为 0
number_of_packets int 等时传输的包数量,批量传输固定为 1
iso_frame_desc[] struct usb_iso_packet_descriptor 等时传输的帧描述数组,批量传输不使用
关键提示: 在批量传输场景中,pipe 必须通过 usb_sndbulkpipe() 或 usb_rcvbulkpipe() 构造,number_of_packets 固定为 1,interval 被忽略。驱动开发者最需要关注的是 transfer_buffer、transfer_buffer_length、complete 和 context 四个字段。

2.2 传输标志位(transfer_flags)详解

transfer_flags 是一个位掩码,控制 URB 在内核中的处理行为。以下是批量传输中最常用的标志位:

  • URB_NO_TRANSFER_DMA_MAP(0x0001):告知内核不要对 transfer_buffer 进行 DMA 映射,适用于驱动已自行管理 DMA 缓冲区的场景。
  • URB_ZERO_PACKET(0x0040):在批量 OUT 传输中,如果数据长度恰好是端点最大包长的整数倍,自动追加一个零长度包,用于通知设备传输结束。
  • URB_SHORT_NOT_OK(0x0080):在批量 IN 传输中,如果实际接收数据少于请求长度,将 status 置为 -EREMOTEIO,驱动可通过此标志检测短包。
  • URB_FREE_BUFFER(0x0100):URB 完成时自动释放 transfer_buffer,适用于一次性提交的场景。
  • URB_NO_INTERRUPT(0x8000):禁止在 URB 完成时触发中断,适用于轮询模式或高吞吐场景。
性能陷阱: 批量传输中,如果未设置 URB_ZERO_PACKET 且数据长度恰好为端点最大包长的整数倍,设备可能一直等待后续数据,导致传输挂起。这是驱动开发中常见的隐蔽 bug。

下表总结了不同传输场景下的标志位推荐组合:

传输场景 推荐标志位组合 说明
批量 OUT(数据块) URB_ZERO_PACKET 确保整数倍包长时发送 ZLP
批量 IN(流式读取) URB_SHORT_NOT_OK 检测设备是否提前结束传输
批量 IN(可变长度) 0 允许短包,由 actual_length 判断实际数据量
高性能批量(DMA 预分配) URB_NO_TRANSFER_DMA_MAP 避免内核重复映射,提升吞吐

2.3 回调函数机制

回调函数是 URB 异步模型的核心。当 URB 完成(包括成功、失败、被取消)时,USB 核心会在适当的上下文中调用 complete 函数。回调函数的原型为:

void (*usb_complete_t)(struct urb *urb);

回调函数在以下三种情况下被触发:

  1. 正常完成:数据传输成功,urb->status 为 0,urb->actual_length 包含实际传输字节数。
  2. 错误完成:硬件错误、端点停止、设备断开等,urb->status 为负值(如 -EPIPE、-ESHUTDOWN)。
  3. 取消完成:调用 usb_unlink_urb() 或 usb_kill_urb() 后,urb->status 为 -ENOENT 或 -ECONNRESET。
回调执行上下文: 回调函数通常在 中断上下文 中执行(除非 URB 提交时指定了 GFP_KERNEL 且系统支持)。因此,回调内部禁止调用可能导致睡眠的函数(如 mutex_lock()、kmalloc(GFP_KERNEL))。如果需要执行阻塞操作,应使用工作队列(workqueue)将任务推迟到进程上下文。

典型的回调函数实现模板:

static void bulk_complete(struct urb *urb)
{
    struct my_device *dev = urb->context;

    if (urb->status == 0) {
        /* 传输成功,处理 urb->actual_length 字节数据 */
        process_received_data(dev, urb->transfer_buffer,
                              urb->actual_length);
    } else if (urb->status == -ENOENT) {
        /* URB 被主动取消,无需处理 */
    } else {
        /* 错误处理:重试或上报 */
        dev_err(&dev->interface->dev,
                "URB failed with status %d\n", urb->status);
    }

    /* 如果需要持续接收,在此重新提交 URB */
    if (dev->running)
        submit_urb_for_read(dev);
}

2.4 缓冲区管理策略

缓冲区管理直接影响批量传输的性能和稳定性。Linux USB 子系统提供三种缓冲区管理方式:

管理方式 分配函数 适用场景 性能特点
内核堆分配 kmalloc() 小数据量、低频传输 简单但可能产生碎片,DMA 不安全
DMA 一致性分配 usb_alloc_coherent() 高频批量传输、流式数据 缓存一致性保证,无 DMA 映射开销
DMA 流式映射 usb_buffer_map() 已有缓冲区需临时 DMA 访问 灵活但需手动管理映射/取消映射

对于批量传输性能测试,推荐使用 DMA 一致性分配 配合 URB_NO_TRANSFER_DMA_MAP 标志位,避免每次提交时内核重复执行 DMA 映射操作,从而降低 CPU 开销。

/* 使用 usb_alloc_coherent 分配 DMA 安全缓冲区 */
dma_addr_t dma_handle;
void *buf = usb_alloc_coherent(dev->udev, BUF_SIZE,
                               GFP_KERNEL, &dma_handle);
if (!buf)
    return -ENOMEM;

/* 初始化 URB */
usb_fill_bulk_urb(urb, dev->udev,
                  usb_rcvbulkpipe(dev->udev, EP_IN),
                  buf, BUF_SIZE, bulk_complete, dev);
urb->transfer_dma = dma_handle;
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

2.5 URB 生命周期与状态流转

下图展示了 URB 从创建到销毁的完整状态机,以及各阶段涉及的关键操作:

URB 生命周期状态流转图 创建 (usb_alloc_urb) 初始化 (usb_fill_bulk_urb) 提交 (usb_submit_urb) 完成 (回调触发) 取消 (usb_kill_urb) 释放 (usb_free_urb) 关键说明: • 创建后必须初始化 pipe 和回调 • 提交后 URB 归 USB 核心管理 • 完成/取消后必须检查 status
最佳实践: 在批量传输性能测试中,建议采用 URB 池化策略——预先分配一组 URB 和 DMA 缓冲区,循环提交和回收,避免频繁的分配/释放操作。这能显著降低延迟抖动,提升吞吐稳定性。

2.6 批量传输 URB 配置速查

以下代码片段展示了批量传输 URB 的完整配置流程,涵盖 OUT 和 IN 两个方向:

/* 批量 OUT 传输配置 */
struct urb *urb_out = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_bulk_urb(urb_out, udev,
                  usb_sndbulkpipe(udev, EP_OUT),
                  out_buf, out_len,
                  out_complete, out_ctx);
urb_out->transfer_flags |= URB_ZERO_PACKET;

/* 批量 IN 传输配置 */
struct urb *urb_in = usb_alloc_urb(0, GFP_KERNEL);
usb_fill_bulk_urb(urb_in, udev,
                  usb_rcvbulkpipe(udev, EP_IN),
                  in_buf, in_len,
                  in_complete, in_ctx);
urb_in->transfer_flags |= URB_SHORT_NOT_OK;

/* 提交 URB */
ret = usb_submit_urb(urb_in, GFP_KERNEL);
if (ret)
    dev_err(&interface->dev, "submit failed: %d\n", ret);