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 保护的临界区中,必须使用
在中断处理函数、tasklet 或 spinlock 保护的临界区中,必须使用
GFP_ATOMIC。使用 GFP_KERNEL 会触发调度器休眠,导致内核崩溃。
最佳实践:在 probe 函数或工作队列中预先分配 URB 池,避免在数据路径中动态分配。对于批量传输,推荐在驱动初始化时一次性分配固定数量的 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 映射错误或数据损坏 |
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 的分配位置、调用栈和释放时机,快速定位泄漏源头。