3. URB生命周期:从创建、填充、提交到完成回调与销毁的完整流程

URB(USB Request Block)是Linux USB子系统中主机与设备之间进行数据交换的核心载体。一个URB从诞生到消亡,需要经历创建 → 填充 → 提交 → 完成回调 → 销毁五个阶段。理解这一完整生命周期,是掌握中断端点批量数据传输、实时性保障以及内存管理的基础。

核心要点:URB的生命周期是异步的。提交后立即返回,数据到达或错误发生时通过回调函数通知驱动。驱动必须严格遵循各阶段的API规范,否则会导致内存泄漏、数据损坏或系统崩溃。
URB 生命周期全景流程图 ① 创建 usb_alloc_urb() ② 填充 usb_fill_int_urb() ③ 提交 usb_submit_urb() ④ 完成回调 urb->complete ⑤ 销毁 usb_free_urb() 异步返回(可重新提交) ⚠ 生命周期关键规则 • 创建与销毁必须配对:每次 usb_alloc_urb() 必须对应一次 usb_free_urb(),否则内存泄漏。 • 提交后不可修改:URB 提交后,在完成回调返回前,不得修改其 transfer_buffer 或 transfer_flags。 • 中断端点 URB 可重复使用:完成回调中重新填充并提交,实现连续中断传输。

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
URB 填充前后关键字段对比 填充前(初始状态) dev = NULL pipe = 0 transfer_buffer = NULL complete = NULL interval = 0 status = -EINPROGRESS usb_fill_int_urb() 填充后(就绪状态) dev = 指向 usb_device pipe = usb_rcvintpipe() transfer_buffer = 0x????0000 complete = my_urb_complete interval = 8 (ms) status = -EINPROGRESS

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核心层使用:

  1. 调用 usb_kill_urb() 或 usb_unlink_urb() 取消正在进行的传输。
  2. 等待完成回调执行完毕(usb_kill_urb 是同步的,返回时保证回调已完成)。
  3. 调用 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);
}
URB 状态机与生命周期 已创建 已填充 已提交 完成回调 已销毁 usb_fill_int_urb usb_submit_urb 传输完成 重新提交(中断端点循环) usb_kill_urb + usb_free_urb 💡 中断端点URB通常在完成回调中重新提交,形成"已提交→完成回调→已提交"的循环,直到驱动卸载或设备断开。

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在重新提交期间存在短暂的空窗期,可能导致数据丢失。