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核心根据设备类型自动分配次设备号,避免手动管理带来的冲突风险。

注册流程分为以下几个阶段:

  1. 设备号分配:调用__video_register_device内部函数,根据type从预定义的设备号范围中获取主设备号(固定为81)和次设备号。
  2. cdev注册:通过cdev_add将字符设备添加到内核,建立设备号与file_operations的映射。
  3. 设备创建:调用device_create在/dev下生成设备节点,并关联struct device与video_device。
  4. 媒体设备关联:如果驱动使用了media_device框架,则自动建立实体与接口的拓扑关系。
  5. 通知用户空间:通过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释放对应位。

V4L2 设备号分配与注册流程 用户空间 /dev/videoX video_register_device() 类型检查 / 参数校验 nr == -1 ? 动态分配次设备号 nr != -1 ? 使用指定次设备号 位图查找 / ida_alloc_range cdev_add + device_create

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也会打印设备注册信息。