3. URB生命周期:从创建、填充、提交到完成回调与销毁的完整流程
URB(USB Request Block)是Linux USB子系统中主机与设备之间进行数据交换的核心载体。一个URB从诞生到消亡,需要经历创建 → 填充 → 提交 → 完成回调 → 销毁五个阶段。理解这一完整生命周期,是掌握中断端点批量数据传输、实时性保障以及内存管理的基础。
核心要点:URB的生命周期是异步的。提交后立即返回,数据到达或错误发生时通过回调函数通知驱动。驱动必须严格遵循各阶段的API规范,否则会导致内存泄漏、数据损坏或系统崩溃。
3.1 创建阶段:usb_alloc_urb()
URB的创建通过 usb_alloc_urb() 函数完成。该函数在堆上分配一个 struct urb 结构体,并初始化内部引用计数为1。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
| 参数 | 说明 | 典型值 |
|---|---|---|
iso_packets |
等时传输的包数量。中断/批量/控制传输设为0 | 0 |
mem_flags |
内存分配标志 | GFP_KERNEL(进程上下文)GFP_ATOMIC(中断上下文) |
最佳实践:在 probe() 函数中预先分配 URB,而不是在每次数据传输时临时创建。中断端点通常使用固定的 URB 池,避免频繁的内存分配开销。
3.2 填充阶段:usb_fill_int_urb()
对于中断端点,使用专用的 usb_fill_int_urb() 辅助函数填充URB。该函数自动设置端点类型、传输方向、中断间隔等关键字段。
void usb_fill_int_urb(struct urb *urb,
struct usb_device *dev,
unsigned int pipe,
void *transfer_buffer,
int buffer_length,
usb_complete_t complete_fn,
void *context,
int interval);
| 字段 | 含义 | 中断端点典型设置 |
|---|---|---|
pipe |
USB管道,由端点描述符创建 | usb_rcvintpipe(dev, endpoint) 或 usb_sndintpipe() |
transfer_buffer |
数据缓冲区指针(DMA安全) | 使用 usb_alloc_coherent() 或 kmalloc 分配 |
buffer_length |
缓冲区大小(字节) | 通常等于端点 wMaxPacketSize |
complete_fn |
完成回调函数指针 | 驱动自定义的 my_urb_complete |
context |
回调上下文参数 | 通常传递设备结构体或 urb 私有数据 |
interval |
中断轮询间隔(毫秒) | 由端点描述符 bInterval 决定,通常 1~255 |
3.3 提交阶段:usb_submit_urb()
填充完成后,调用 usb_submit_urb() 将URB提交给USB核心层。该函数是异步非阻塞的,提交后立即返回,实际数据传输由USB主机控制器在后台完成。
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
返回值含义:
- 0:提交成功,URB已加入主机控制器调度队列。
- -EINVAL:参数错误(如无效的pipe、NULL缓冲区、interval为0)。
- -ENODEV:设备已断开连接。
- -ENOMEM:内存不足,无法分配DMA描述符。
- -ESHUTDOWN:USB主机控制器已关闭。
常见错误:在中断上下文(如定时器回调、tasklet)中提交URB时,必须使用
GFP_ATOMIC 标志。使用 GFP_KERNEL 会导致内核调度器异常(可能导致休眠在不可休眠上下文中)。
3.4 完成回调阶段:urb->complete
当USB主机控制器完成数据传输(或发生错误)时,USB核心层调用驱动注册的完成回调函数。回调函数在软中断上下文中执行,不能休眠。
static void my_urb_complete(struct urb *urb)
{
struct my_device *dev = urb->context;
/* 检查传输状态 */
if (urb->status == 0) {
/* 成功:处理接收到的数据 */
process_received_data(dev, urb->transfer_buffer,
urb->actual_length);
} else if (urb->status == -ENOENT) {
/* URB被 usb_kill_urb() 取消 */
return;
} else if (urb->status == -ECONNRESET) {
/* 设备断开 */
return;
} else {
/* 其他错误 */
dev_err(&dev->interface->dev,
"URB error %d\n", urb->status);
}
/* 对于中断端点:重新提交URB以接收下一次中断 */
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret)
dev_err(&dev->interface->dev,
"resubmit failed %d\n", ret);
}
| urb->status 值 | 含义 | 驱动处理建议 |
|---|---|---|
| 0 | 传输成功 | 处理数据,重新提交URB |
| -EINPROGRESS | 传输尚未完成(不应出现在回调中) | 忽略(内核Bug) |
| -ENOENT | 被 usb_kill_urb() 取消 | 释放资源,不重新提交 |
| -ECONNRESET | URB被 usb_unlink_urb() 取消 | 根据设计决定是否重新提交 |
| -EPROTO | USB协议错误(bitstuff、CRC等) | 记录错误,重新提交 |
| -ETIMEDOUT | 传输超时 | 记录错误,重新提交 |
| -ESHUTDOWN | 主机控制器已关闭 | 停止所有URB,等待设备重新连接 |
3.5 销毁阶段:usb_free_urb()
当URB不再需要时(通常在驱动卸载或设备断开时),调用 usb_free_urb() 释放URB占用的内存。该函数会递减引用计数,当计数归零时真正释放内存。
void usb_free_urb(struct urb *urb);
销毁前必须确保URB不再被USB核心层使用:
- 调用
usb_kill_urb()或usb_unlink_urb()取消正在进行的传输。 - 等待完成回调执行完毕(usb_kill_urb 是同步的,返回时保证回调已完成)。
- 调用
usb_free_urb()释放内存。
典型销毁流程代码:
/* 在 disconnect() 或 remove() 中 */
usb_kill_urb(dev->int_urb); /* 同步取消,等待回调完成 */
usb_free_urb(dev->int_urb); /* 释放URB内存 */
dev->int_urb = NULL; /* 防止悬空指针 */
3.6 完整生命周期示例:中断端点连续接收
以下代码展示了中断端点URB从创建到销毁的完整流程,包含错误处理和资源清理:
/* 驱动 probe() 中初始化中断URB */
static int my_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *udev = interface_to_usbdev(intf);
struct usb_endpoint_descriptor *ep;
struct my_device *dev;
int ret;
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
/* 1. 分配URB */
dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!dev->int_urb) {
ret = -ENOMEM;
goto err_free_dev;
}
/* 2. 分配DMA安全缓冲区 */
dev->buf = usb_alloc_coherent(udev, MAX_PACKET,
GFP_KERNEL, &dev->buf_dma);
if (!dev->buf) {
ret = -ENOMEM;
goto err_free_urb;
}
/* 3. 查找中断IN端点 */
ep = &intf->cur_altsetting->endpoint[0].desc;
/* 4. 填充URB */
usb_fill_int_urb(dev->int_urb, udev,
usb_rcvintpipe(udev, ep->bEndpointAddress),
dev->buf, MAX_PACKET,
my_urb_complete, dev,
ep->bInterval);
/* 5. 设置DMA标志 */
dev->int_urb->transfer_dma = dev->buf_dma;
dev->int_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
/* 6. 提交URB */
ret = usb_submit_urb(dev->int_urb, GFP_KERNEL);
if (ret) {
dev_err(&intf->dev, "submit failed %d\n", ret);
goto err_free_coherent;
}
usb_set_intfdata(intf, dev);
return 0;
err_free_coherent:
usb_free_coherent(udev, MAX_PACKET, dev->buf, dev->buf_dma);
err_free_urb:
usb_free_urb(dev->int_urb);
err_free_dev:
kfree(dev);
return ret;
}
/* 驱动 disconnect() 中销毁URB */
static void my_disconnect(struct usb_interface *intf)
{
struct my_device *dev = usb_get_intfdata(intf);
struct usb_device *udev = interface_to_usbdev(intf);
/* 7. 同步取消URB */
usb_kill_urb(dev->int_urb);
/* 8. 释放DMA缓冲区 */
usb_free_coherent(udev, MAX_PACKET, dev->buf, dev->buf_dma);
/* 9. 销毁URB */
usb_free_urb(dev->int_urb);
kfree(dev);
}
3.7 生命周期中的关键规则总结
| 阶段 | 关键函数 | 注意事项 |
|---|---|---|
| 创建 | usb_alloc_urb() |
中断端点 iso_packets=0;probe() 中预分配 |
| 填充 | usb_fill_int_urb() |
interval 从端点描述符获取;缓冲区必须DMA安全 |
| 提交 | usb_submit_urb() |
中断上下文用 GFP_ATOMIC;检查返回值 |
| 回调 | 自定义 complete 函数 | 软中断上下文,不能休眠;检查 status |
| 销毁 | usb_kill_urb() + usb_free_urb() |
先同步取消,再释放;防止use-after-free |
性能提示:对于高频率中断端点(如鼠标、游戏手柄),建议在 probe() 中分配多个URB组成环形缓冲池,交替提交以降低延迟。单个URB在重新提交期间存在短暂的空窗期,可能导致数据丢失。