2. V4L2设备注册与注销:video_register_device/video_unregister_device的调用流程、主设备号与次设备号的分配机制
在V4L2框架中,设备节点的注册与注销是驱动与用户空间交互的桥梁。本章深入剖析video_register_device与video_unregister_device的完整调用流程,以及内核如何为主/次设备号进行分配与管理。
2.1 V4L2设备注册核心流程
video_register_device是驱动向系统注册一个视频设备的入口函数。其原型定义在include/media/v4l2-dev.h中:
int video_register_device(struct video_device *vdev,
enum vfl_type type,
int nr);
参数说明:
- vdev:已初始化的
video_device结构体指针,包含fops、ioctl_ops、release回调等。 - type:设备类型,决定次设备号的分配范围,可选值包括
VFL_TYPE_VIDEO、VFL_TYPE_VBI、VFL_TYPE_RADIO等。 - nr:期望的次设备号编号,传入
-1表示由内核动态分配。
关键点:驱动通常传入
nr = -1,让V4L2核心根据设备类型自动分配次设备号,避免手动管理带来的冲突风险。
注册流程分为以下几个阶段:
- 设备号分配:调用
__video_register_device内部函数,根据type从预定义的设备号范围中获取主设备号(固定为81)和次设备号。 - cdev注册:通过
cdev_add将字符设备添加到内核,建立设备号与file_operations的映射。 - 设备创建:调用
device_create在/dev下生成设备节点,并关联struct device与video_device。 - 媒体设备关联:如果驱动使用了
media_device框架,则自动建立实体与接口的拓扑关系。 - 通知用户空间:通过
kobject_uevent发送ADD事件,触发udev等工具自动创建设备节点。
实践建议:在注册前务必设置
vdev->v4l2_dev和vdev->queue字段,否则注册可能失败或导致内核警告。
2.2 主设备号与次设备号的分配机制
V4L2设备的主设备号固定为81,所有视频设备共享同一个主设备号。次设备号则根据设备类型进行分区管理,确保不同类型设备不会发生冲突。
次设备号分配范围定义在drivers/media/v4l2-core/v4l2-dev.c中:
| 设备类型 | 宏定义 | 次设备号范围 | 最大设备数 |
|---|---|---|---|
| 视频采集/输出 | VFL_TYPE_VIDEO |
0 – 63 | 64 |
| VBI(垂直消隐期) | VFL_TYPE_VBI |
224 – 255 | 32 |
| 收音机 | VFL_TYPE_RADIO |
64 – 127 | 64 |
| 软件定义无线电 | VFL_TYPE_SDR |
128 – 159 | 32 |
| 触摸传感器 | VFL_TYPE_TOUCH |
160 – 191 | 32 |
| 辅助设备 | VFL_TYPE_SUBDEV |
192 – 223 | 32 |
注意:次设备号范围是硬编码的,如果系统中有超过64个视频设备,注册第65个
VFL_TYPE_VIDEO设备时会返回-ENOSPC错误。
动态分配算法采用位图(bitmap)管理:内核为每种设备类型维护一个unsigned long位图,每一位代表一个次设备号是否被占用。注册时调用ida_alloc_range或find_next_zero_bit查找空闲位,注销时调用ida_free释放对应位。
2.3 设备注销流程
video_unregister_device是注册的逆操作,负责清理内核资源并通知用户空间设备已移除。其调用链如下:
void video_unregister_device(struct video_device *vdev)
{
// 1. 设置 V4L2_FL_REGISTERED 标志清除
clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
// 2. 通过 device_destroy 移除 /dev 节点
device_destroy(vdev->dev.parent, video_device_node(vdev));
// 3. 通过 cdev_del 删除字符设备
cdev_del(&vdev->cdev);
// 4. 释放次设备号(位图置0)
video_device_release_minor(vdev);
// 5. 发送 REMOVE 事件
kobject_uevent(&vdev->dev.kobj, KOBJ_REMOVE);
// 6. 如果设置了 release 回调,最终释放 video_device 内存
}
重要:注销后驱动必须确保没有正在进行的IO操作(如
mmap、streamon),否则可能导致内核崩溃。建议在注销前调用vb2_queue_release停止所有DMA传输。
2.4 设备号分配实战示例
以下代码展示了一个典型的V4L2驱动中设备注册与注销的完整模式:
static int my_probe(struct platform_device *pdev)
{
struct video_device *vdev;
int ret;
vdev = video_device_alloc();
if (!vdev)
return -ENOMEM;
vdev->fops = &my_fops;
vdev->ioctl_ops = &my_ioctl_ops;
vdev->release = video_device_release;
vdev->v4l2_dev = &my_v4l2_dev;
vdev->queue = &my_queue;
strscpy(vdev->name, "my_video_device", sizeof(vdev->name));
// 注册视频设备,类型为 VFL_TYPE_VIDEO,次设备号动态分配
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret) {
video_device_release(vdev);
return ret;
}
platform_set_drvdata(pdev, vdev);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
struct video_device *vdev = platform_get_drvdata(pdev);
// 注销视频设备
video_unregister_device(vdev);
return 0;
}
2.5 常见问题与避坑指南
| 问题场景 | 原因 | 解决方案 |
|---|---|---|
注册返回-ENOSPC |
该类型的次设备号已耗尽 | 检查系统已有设备数量,或更换设备类型 |
注册返回-EINVAL |
vdev->v4l2_dev或vdev->queue为NULL |
确保在注册前正确初始化这两个字段 |
| 注销后设备节点仍存在 | device_destroy未正确执行 |
检查vdev->dev.parent是否与注册时一致 |
| 次设备号冲突 | 手动指定了已被占用的nr |
始终使用-1让内核自动分配 |
调试技巧:使用
cat /proc/devices查看主设备号81是否注册;使用ls -l /dev/video*查看次设备号分配情况。内核启动日志中的dmesg也会打印设备注册信息。