4. 中断URB的生命周期:URB的分配、填充、提交、完成回调与取消机制
中断URB(USB Request Block)是USB中断端点通信的核心载体,其生命周期管理直接决定了实时性表现。一个中断URB从创建到销毁,依次经历分配、填充、提交、完成回调与取消五个关键阶段。本章将深入剖析每个阶段的操作细节、内核接口及实时性优化要点。
核心观点:中断URB的生命周期是一个闭环——提交后由硬件触发完成回调,驱动在回调中重新提交URB以维持连续中断流。任何阶段的延迟或错误都会直接破坏实时性。
4.1 中断URB的分配
URB的分配发生在驱动初始化或打开设备时,内核提供专用的内存分配接口。中断URB通常使用usb_alloc_urb()函数,该函数从URB缓存池中分配内存,避免频繁的kmalloc开销。
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
| 参数 | 说明 | 实时性建议 |
|---|---|---|
iso_packets |
等时传输的包数量,中断URB设为0 | 固定为0 |
mem_flags |
内存分配标志,常用GFP_KERNEL或GFP_ATOMIC |
中断上下文必须使用GFP_ATOMIC |
最佳实践:在probe()函数中使用
GFP_KERNEL预分配URB;若在中断上下文分配,必须切换为GFP_ATOMIC,但后者失败率更高。推荐在初始化阶段一次性分配并复用。
4.2 中断URB的填充
分配完成后,驱动需要填充URB的各个字段,指定传输方向、目标端点、数据缓冲区、回调函数等。中断URB的填充通过usb_fill_int_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_rcvintpipe()或usb_sndintpipe()创建 |
确保端点号与bEndpointAddress匹配 |
transfer_buffer |
DMA安全的数据缓冲区 | 使用usb_alloc_coherent()避免缓存一致性开销 |
buffer_length |
缓冲区大小,通常等于wMaxPacketSize | 精确匹配端点最大包长,避免拆分 |
complete_fn |
完成回调函数指针 | 回调必须轻量,不可睡眠 |
interval |
中断轮询间隔(单位:帧/微帧) | 直接决定中断频率,需与设备协商 |
常见错误:interval字段在USB 2.0下单位为帧(1ms),USB 3.0下单位为微帧(125μs)。驱动必须根据
urb->dev->speed动态计算实际间隔,否则可能导致总线带宽浪费或中断丢失。
4.3 中断URB的提交
填充完成后,驱动调用usb_submit_urb()将URB提交给USB核心层。该函数将URB加入HCD(主机控制器驱动)的调度队列,等待硬件处理。
int usb_submit_urb(struct urb *urb, gfp_t mem_flags);
提交后的URB状态转换如下:
- 提交前:URB处于
URB_FREE状态 - 提交成功:状态变为
URB_SUBMITTED,进入HCD调度队列 - 提交失败:返回负错误码(如
-ENOMEM、-EINVAL),URB状态不变
实时性要点:中断URB的提交应在中断上下文或软中断中完成。若在进程上下文提交,可能因调度延迟错过下一个中断周期。推荐在完成回调中直接重新提交,形成“提交→完成→提交”的连续流。
4.4 中断URB的完成回调
当USB硬件完成数据传输后,HCD通过中断通知内核,内核在中断上下文中调用URB的完成回调函数。回调函数是驱动处理输入数据的入口,也是重新提交URB的时机。
static void intr_urb_complete(struct urb *urb)
{
struct my_device *dev = urb->context;
int ret;
/* 检查URB状态 */
if (urb->status == 0) {
/* 成功:处理接收数据 */
process_input_data(dev, urb->transfer_buffer,
urb->actual_length);
} else if (urb->status == -ECONNRESET ||
urb->status == -ENOENT ||
urb->status == -ESHUTDOWN) {
/* 被取消或设备断开,停止重新提交 */
return;
} else {
/* 其他错误,尝试重新提交 */
dev_err(&dev->intf->dev, "URB error %d\n", urb->status);
}
/* 重新提交URB以接收下一个中断 */
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret)
dev_err(&dev->intf->dev, "resubmit failed %d\n", ret);
}
| urb->status值 | 含义 | 驱动处理 |
|---|---|---|
| 0 | 传输成功 | 处理数据,重新提交 |
-ECONNRESET |
URB被usb_unlink_urb()取消 |
停止重新提交 |
-ENOENT |
URB被usb_kill_urb()终止 |
停止重新提交 |
-ESHUTDOWN |
主机控制器或设备断开 | 停止重新提交 |
-EPIPE |
端点被停止(stall) | 调用usb_clear_halt()后重新提交 |
-EOVERFLOW |
数据超过缓冲区大小 | 增大缓冲区后重新提交 |
实时性优化:完成回调中应避免耗时操作,如内存分配、互斥锁、printk等。推荐使用workqueue将数据推送到进程上下文处理,回调中仅做最小必要操作并立即重新提交URB。
4.5 中断URB的取消机制
在设备断开、驱动卸载或需要重置端点时,驱动必须取消正在进行的URB。内核提供两种取消接口,适用于不同场景。
4.5.1 usb_kill_urb() — 同步取消
void usb_kill_urb(struct urb *urb);
- 行为:等待URB完全停止后才返回,确保URB不再被使用
- 适用场景:驱动卸载、设备断开等确定性场景
- 注意事项:不能在中断上下文调用,可能导致死锁
4.5.2 usb_unlink_urb() — 异步取消
int usb_unlink_urb(struct urb *urb);
- 行为:发起取消请求后立即返回,不等待URB停止
- 适用场景:中断上下文、需要快速响应的场景
- 返回值:0表示取消请求已提交,-EINPROGRESS表示URB正在处理
关键区别:usb_kill_urb()会等待回调执行完毕,适合卸载路径;usb_unlink_urb()仅标记取消,回调仍可能被调用,驱动必须在回调中检查urb->status == -ECONNRESET并停止重新提交。误用usb_kill_urb()在中断上下文会导致内核崩溃。
4.6 中断URB生命周期全景
将上述五个阶段串联起来,形成中断URB的完整生命周期。理解这个闭环是编写稳定、实时中断驱动的基石。
生命周期总结:中断URB的生命周期是一个可循环的闭环——分配→填充→提交→硬件处理→完成回调→重新提交。取消操作(阶段5)是闭环的异常出口,仅在设备断开或驱动卸载时触发。驱动开发者必须确保每个阶段的原子性和低延迟,任何阶段的阻塞都会破坏整个中断链的实时性。