3. URB 分配与初始化:usb_alloc_urb 与 usb_fill_* 系列函数的使用规范与常见陷阱

URB(USB Request Block)是 USB 设备驱动与主机控制器之间交换数据的核心载体。分配与初始化阶段看似简单,却是驱动稳定性的第一道防线。本章深入剖析 usb_alloc_urb 与 usb_fill_* 系列函数的底层行为、参数约束及高频错误模式,帮助开发者建立正确的 URB 生命周期管理意识。

核心原则:URB 的分配与初始化必须遵循“一次分配、正确填充、及时释放”的闭环。任何环节的疏忽都会导致内存泄漏、DMA 缓冲区污染或设备挂起。

3.1 usb_alloc_urb:内存分配与内部结构

usb_alloc_urb 是 URB 对象的工厂函数,其原型定义在 <linux/usb.h> 中:

struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);

该函数在堆上分配 struct urb 结构体,并根据 iso_packets 参数预分配等时传输所需的 ISO 包描述符数组。分配成功后,URB 的引用计数初始化为 1。

参数 说明 常见错误
iso_packets 等时传输的包数量(非等时传输填 0) 非等时传输误填非零值,浪费内存
mem_flags 内存分配标志(GFP_KERNEL / GFP_ATOMIC) 中断上下文使用 GFP_KERNEL 导致休眠崩溃
陷阱 1:中断上下文分配
在中断处理函数、tasklet 或 spinlock 保护的临界区中,必须使用 GFP_ATOMIC。使用 GFP_KERNEL 会触发调度器休眠,导致内核崩溃。
最佳实践:在 probe 函数或工作队列中预先分配 URB 池,避免在数据路径中动态分配。对于批量传输,推荐在驱动初始化时一次性分配固定数量的 URB。
usb_alloc_urb 内存分配流程 usb_alloc_urb() iso_packets > 0? 分配 ISO 描述符数组 iso_packets = 0 返回 struct urb *

3.2 usb_fill_* 系列函数:初始化规范

Linux 内核提供三个专用初始化函数,分别对应三种传输类型。它们负责填充 URB 的公共字段和传输类型特定字段。

函数 适用传输类型 关键参数
usb_fill_control_urb 控制传输 setup_packet、pipe、transfer_buffer、complete
usb_fill_bulk_urb 批量传输 pipe、transfer_buffer、buffer_length、complete
usb_fill_int_urb 中断传输 pipe、transfer_buffer、buffer_length、complete、interval

3.2.1 通用初始化流程

struct urb *urb;
struct usb_device *udev = interface_to_usbdev(intf);

urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb)
    return -ENOMEM;

usb_fill_bulk_urb(urb, udev,
                  usb_rcvbulkpipe(udev, EP_IN),
                  buffer, BUFFER_SIZE,
                  bulk_complete_callback,
                  context);

urb->transfer_dma = buffer_dma_handle;
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
陷阱 2:transfer_buffer 与 DMA 一致性
如果使用 usb_alloc_coherent 分配的 DMA 缓冲区,必须在 transfer_flags 中设置 URB_NO_TRANSFER_DMA_MAP,否则主机控制器会尝试再次映射,导致 DMA 映射错误。

3.3 常见陷阱深度分析

3.3.1 陷阱 3:interval 字段的误解

中断传输的 interval 参数单位因 USB 速度而异:

  • 全速/低速:单位是帧(1 ms),取值范围 1–255
  • 高速:单位是微帧(125 µs),实际间隔 = 2^(interval-1) 微帧
  • 超高速:单位是服务间隔(Service Interval),实际间隔 = 2^(interval-1) × 125 µs

错误设置 interval 会导致中断传输频率异常,轻则 CPU 占用飙升,重则设备无响应。

3.3.2 陷阱 4:URB 复用与重新初始化

URB 在完成回调中可以被重新提交,但必须注意:

/* 错误示例:直接修改已提交的 URB */
urb->transfer_buffer_length = new_size;
usb_submit_urb(urb, GFP_ATOMIC);  // 可能导致竞态

/* 正确做法:在回调中安全复用 */
static void complete_callback(struct urb *urb)
{
    if (urb->status == 0) {
        /* 重新填充必要字段 */
        urb->transfer_buffer_length = BUFFER_SIZE;
        urb->actual_length = 0;
        ret = usb_submit_urb(urb, GFP_ATOMIC);
    }
}

3.4 初始化检查清单

检查项 验证方法 违规后果
分配标志与上下文匹配 检查调用栈是否在中断上下文 内核崩溃(schedule while atomic)
transfer_buffer 非空 提交前检查指针有效性 主机控制器访问 NULL 指针
pipe 方向与端点匹配 使用 usb_rcv*pipe / usb_snd*pipe URB 提交失败(-EINVAL)
complete 回调已注册 非 NULL 检查 URB 完成后无法释放资源
DMA 标志一致性 检查 transfer_flags 与缓冲区来源 DMA 映射错误或数据损坏
URB 初始化状态机与常见错误路径 ALLOCATED INITIALIZED SUBMITTED COMPLETED GFP_KERNEL 中断 transfer_buffer NULL DMA 标志不匹配

3.5 性能优化:URB 池化策略

对于高吞吐量设备(如 USB 摄像头、存储设备),频繁调用 usb_alloc_urb 和 usb_free_urb 会产生大量内存分配开销。推荐使用 URB 池化技术:

struct urb_pool {
    struct urb *pool[MAX_URB_COUNT];
    int count;
    spinlock_t lock;
};

static struct urb *pool_get_urb(struct urb_pool *pool)
{
    struct urb *urb = NULL;
    unsigned long flags;

    spin_lock_irqsave(&pool->lock, flags);
    if (pool->count > 0) {
        urb = pool->pool[--pool->count];
    }
    spin_unlock_irqrestore(&pool->lock, flags);

    if (!urb)
        urb = usb_alloc_urb(0, GFP_ATOMIC);
    return urb;
}

static void pool_put_urb(struct urb_pool *pool, struct urb *urb)
{
    unsigned long flags;

    usb_free_urb(urb);  /* 实际释放,或放入池中 */
}
性能数据:在高速批量传输场景下,使用 URB 池可以减少约 40% 的分配/释放开销,同时降低内存碎片化程度。建议池大小根据端点最大未完成请求数设定。

3.6 调试与验证

内核提供 CONFIG_USB_DEBUG 和 CONFIG_DYNAMIC_DEBUG 选项,可开启 URB 分配跟踪:

# 动态开启 URB 分配日志
echo "file drivers/usb/core/urb.c +p" > /sys/kernel/debug/dynamic_debug/control

# 使用 ftrace 跟踪 URB 生命周期
echo 'usb_alloc_urb' > /sys/kernel/debug/tracing/set_ftrace_filter
echo function > /sys/kernel/debug/tracing/current_tracer

通过上述手段,可以精确监控每个 URB 的分配位置、调用栈和释放时机,快速定位泄漏源头。