4. URB 内存分配 API:usb_alloc_urb 与 usb_free_urb 的底层实现与使用场景
URB(USB Request Block)是 USB 设备驱动与主机控制器之间的核心通信载体。每个 URB 实例的分配与释放,直接决定了数据传输的稳定性与内存泄漏风险。本章深入剖析 usb_alloc_urb 与 usb_free_urb 的底层实现机制、内存布局以及典型使用场景,帮助你在驱动开发中精准管理 URB 生命周期。
4.1 URB 内存结构概览
一个 URB 在内存中并非孤立存在,它由三个连续区域组成:URB 头部、ISO 包描述符数组(仅等时传输使用)以及 Setup 包(仅控制传输使用)。下图展示了 URB 的完整内存布局:
4.2 usb_alloc_urb 底层实现
函数原型定义在 <linux/usb.h> 中:
struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags);
该函数的核心工作流程如下:
- 计算总大小:根据
iso_packets参数计算 ISO 包描述符数组所需字节数,加上sizeof(struct urb)得到总分配大小。 - 调用
kmalloc:使用mem_flags(通常为GFP_KERNEL或GFP_ATOMIC)分配连续内核内存。 - 初始化 URB 头部:将
urb->dev置为NULL,urb->pipe清零,urb->transfer_flags设为URB_NO_TRANSFER_DMA_MAP等默认值。 - 设置 ISO 偏移:将
urb->iso_frame_desc指针指向紧随 URB 头部之后的 ISO 描述符数组起始地址。 - 返回 URB 指针:若分配失败则返回
NULL。
关键提示:
iso_packets 参数仅对等时传输有意义。对于批量、中断和控制传输,应传入 0,避免浪费内存。
4.3 usb_free_urb 底层实现
函数原型:
void usb_free_urb(struct urb *urb);
释放过程包含以下安全机制:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 空指针检查 | 若 urb == NULL,立即返回,避免空指针解引用 |
| 2 | 引用计数递减 | 调用 kref_put(&urb->kref, urb_destroy),仅当引用计数归零时才真正释放 |
| 3 | 调用 kfree |
在 urb_destroy 回调中执行 kfree(urb),归还连续内存块 |
常见错误:直接调用
kfree(urb) 绕过引用计数机制,会导致正在被主机控制器引用的 URB 被释放,引发内核崩溃或 DMA 访问非法内存。
4.4 使用场景对比
不同传输类型对 URB 分配的需求差异显著,下表总结了典型场景:
| 传输类型 | iso_packets 参数 | 典型 mem_flags | 使用场景 |
|---|---|---|---|
| 批量传输 | 0 | GFP_KERNEL(进程上下文) GFP_ATOMIC(中断上下文) |
U 盘读写、网络适配器数据收发 |
| 中断传输 | 0 | GFP_KERNEL / GFP_ATOMIC | 键盘、鼠标、游戏手柄输入 |
| 控制传输 | 0 | GFP_KERNEL | 设备枚举、标准请求(如获取描述符) |
| 等时传输 | 帧数(如 8/16/32) | GFP_KERNEL | 音频流、视频流、实时传感器数据 |
4.5 典型代码模式
以下代码展示了批量传输场景下 URB 的完整生命周期管理:
#include <linux/usb.h>
static struct urb *bulk_urb;
static char *bulk_buffer;
static int setup_bulk_urb(struct usb_device *udev, unsigned int pipe)
{
int ret;
/* 1. 分配 URB(批量传输不需要 ISO 包) */
bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!bulk_urb) {
printk(KERN_ERR "Failed to allocate URB\n");
return -ENOMEM;
}
/* 2. 分配传输缓冲区 */
bulk_buffer = kmalloc(512, GFP_KERNEL);
if (!bulk_buffer) {
usb_free_urb(bulk_urb);
return -ENOMEM;
}
/* 3. 填充 URB */
usb_fill_bulk_urb(bulk_urb, udev, pipe,
bulk_buffer, 512,
bulk_complete_callback, /* 完成回调 */
NULL); /* 上下文 */
/* 4. 提交 URB */
ret = usb_submit_urb(bulk_urb, GFP_KERNEL);
if (ret) {
printk(KERN_ERR "URB submission failed: %d\n", ret);
usb_free_urb(bulk_urb);
kfree(bulk_buffer);
return ret;
}
return 0;
}
static void cleanup_bulk_urb(void)
{
/* 5. 先取消,再释放 */
usb_kill_urb(bulk_urb);
usb_free_urb(bulk_urb);
kfree(bulk_buffer);
}
4.6 引用计数机制详解
URB 的引用计数由 struct kref 管理,确保在主机控制器处理期间不会被意外释放。下图展示了引用计数的变化流程:
4.7 常见陷阱与最佳实践
- 重复释放:
usb_free_urb内部通过引用计数防止双重释放,但驱动仍应避免在回调中再次释放已释放的 URB。 - 中断上下文分配:在中断处理函数或定时器回调中分配 URB 时,必须使用
GFP_ATOMIC,禁止使用GFP_KERNEL(可能睡眠)。 - URB 复用:通过
usb_submit_urb提交后,若需再次使用同一 URB,必须先调用usb_kill_urb或usb_unlink_urb确保其不再被主机控制器引用。 - 内存泄漏检测:在驱动卸载路径中,使用
usb_poison_urb强制终止所有待处理 URB,再逐个释放。
核心原则:
usb_alloc_urb 与 usb_free_urb 必须成对出现,且释放前确保 URB 已从主机控制器中安全移除。引用计数机制为并发安全提供了保障,但驱动开发者仍需遵循正确的生命周期管理顺序。