v4l2_open()浅析

这个函数主要做了一下事情 :
这里写图片描述

/**
    v4l2_open(): 这个函数主要做的是:
                                调用具体设备提供的open函数 

    应用程序调用open(/dev/videox)的时候,这个函数最终会被调用
    1 : 为什么? 
            video_register_device
            {
                __video_register_device(...)
                {
                    vdev->cdev = cdev_alloc();
                    if (vdev->cdev == NULL) {
                        ret = -ENOMEM;
                        goto cleanup;
                    }
                    vdev->cdev->ops = &v4l2_fops;
                    vdev->cdev->owner = owner;
                    ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
                }
            }

        static const struct file_operations v4l2_fops = {
            .owner = THIS_MODULE,
            .read = v4l2_read,
            .write = v4l2_write,
            .open = v4l2_open,
            .get_unmapped_area = v4l2_get_unmapped_area,
            .mmap = v4l2_mmap,
            .unlocked_ioctl = v4l2_ioctl,
        #ifdef CONFIG_COMPAT
            .compat_ioctl = v4l2_compat_ioctl32,
        #endif
            .release = v4l2_release,
            .poll = v4l2_poll,
            .llseek = no_llseek,
        };
*/
static int v4l2_open(struct inode * inode,struct file * file)
{
    struct video_device * vdev;
    int ret = 0;

    mutex_lock(&videodev_lock);
    /**
        依据次设备号为索引值从video_devices[]中获取对应的video_device
        原理已经在 "video_register_device()浅析"博文中说过,下面简单列出来 方便阅读
        :

        那么:video_device是再什么时候被放入到video_device[]的呢?
        次设备号又是怎么构建的呢?

        i : video_device[]第一个空缺项的下标
        minor_offset : 根据传入的类型 选择得到
                        switch (type) {
                            case VFL_TYPE_GRABBER:
                                minor_offset = 0;
                                minor_cnt = 64;
                                break;
                            case VFL_TYPE_RADIO:
                                minor_offset = 64;
                                minor_cnt = 64;
                                break;
                            case VFL_TYPE_VBI:
                                minor_offset = 224;
                                minor_cnt = 32;
                                break;
                            default:
                                minor_offset = 128;
                                minor_cnt = 64;
                                break;
                            }
        vdev->minor = i + minor_offset;
        ...
        ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

        是video_register_device()来实现的。
        video_register_device()
        {
            return __video_register_device(vdev, type, nr, 1, vdev->fops->owner)
            {
                再将设置好的video_device放入到video_device[]之前,设置它的成员flags(unsigned long 类型)
                的第0位,表示这个video_device是注册过的了,
                再其他位置会调用video_is_registered()来判断,其依据还是flags的第0位的值 
                set_bit(V4L2_FL_REGISTERED, &vdev->flags);
                mutex_lock(&videodev_lock);
                依据次设备号为下标,将设置好的video_device放入到video_device[]中
                video_device[vdev->minor] = vdev;

                mutex_unlock(&videodev_lock);
            }
        }
    */
    vdev = video_devdata(file)
    /**
    {

            1 : 获取打开文件对应的索引节点对象inode
                struct inode * inode = file->f_path.dentry->d_inode
                获取次设备号
            2 : int minor = MINOR(inode->i_rdev);   
            3 : 根据minor获取在video_register_device()中注册的video_device

        return video_devices[iminor(file->f_path.dentry->d_inode)];
    }
    */
    /**
        static inline int video_is_registered(struct video_device *vdev)
        {
                返回flags的第0位,
                如果为1 表示已经注册过了,否则表明没有注册过。
                在注册的时候 ,就已经设置flags的第0位了。
                看注册的源码
                v4l2_dev.c : 
                video_register_device()
                {
                    __video_register_device()
                    {
                        1 : 设置flags的第0位,表示它已经被注册过了。 
                            set_bit(V4L2_FL_REGISTERED, &vdev->flags);

                        2 : 获取互斥锁 --- 访问临界区 ----- 加锁
                            mutex_lock(&videodev_lock);
                            //依据次设备号为索引值将设置好的video_device放到video_device[]中
                            video_device[vdev->minor] = vdev;
                            mutex_unlock(&videodev_lock);
                    }
                }
            return test_bit(V4L2_FL_REGISTERED, &vdev->flags);
        }
    */
    if (vdev == NULL || !video_is_registered(vdev))
    {
        mutex_unlock(&videodev_lock);
        return -ENODEV;
    }
    /**
        增加设备的引用计数
        底层是通过kobject_get()来实现
        kobject是通过内嵌的struct kref 来实现的。
        struct kref {  //kref是一个引用计数器,它被嵌套进其他的结构体中,记录所嵌套结构的引用计数
            atomic_t refcount;
        }
    */
    video_get(vdev);
    mutex_unlock(&videodev_lock);//释放互斥锁

    /**
        这里的fops是具体设备的v4l2_file_operations
        比如:vivi
        在vivi.c
            vivi_init()
            {
                vivi_create_instance()
                {
                    struct video_device *vfd;
                    .....
                    为video_device分配内存空间
                    vfd = video_device_alloc();
                    *vfd = vivi_template;
                    上面一行,video_device被设置成了vivi_template
                    这个vivi_template会依据次设备为下标放入到video_device[]中,
                    开头的几行代码依据次设备取出来的video_device在vivi中就是那个被放入的vivi_template
                    下面是vivi_template的代码
                    static struct video_device vivi_template = {
                        .name       = "vivi",
                        .fops           = &vivi_fops,
                        .ioctl_ops  = &vivi_ioctl_ops,
                        .release    = video_device_release,

                        .tvnorms              = V4L2_STD_525_60,
                        .current_norm         = V4L2_STD_NTSC_M,
                    };

                    static const struct v4l2_file_operations vivi_fops = {
                        .owner      = THIS_MODULE,
                        .open           = v4l2_fh_open,
                        .release        = vivi_close,
                        .read           = vivi_read,
                        .poll       = vivi_poll,
                        .unlocked_ioctl = video_ioctl2,  V4L2 ioctl handler  当应用程序调用ioctl,这个ioctl会被调用,根据cmd,会调用 vivi_ioctl_ops中的ioctl
                        .mmap           = vivi_mmap,
                    };

                    所以  vdev->fops 就是vivi_fops
                          vdev->fops->open就是v4l2_fh_open了
                }
            }

        如果具体设备的v4l2_file_operations提供了open函数
        就调用这个open函数:v4l2_fh_open.否则什么也不做

    */
    if (vdev->fops->open)
    {
        //这个lock是再v4l2_device_register()中初始化过了
        //这里主要是判断有没有进行初始化,如果初始化了,core层会帮你释放这个锁。具体是在这个函数的最后一段代码中。
        if(vdev->lock && mutex_lock_interruptible(vdev->lock))
        {
            ret = -ERESTARTSYS;
            goto err;
        }
        //如果设备已经是被注册的了,就调用它提供的open函数
        if (video_is_registered(vdev))
        {
            ret = vdev->fops->open(filp); //调用具体设备的fops的open函数
        }
        else{
            ret = -ENODEV;
        }           
        //如果使用v4l2_device_register()进行初始化了,就释放这把锁                 
        if (vdev->lock)
        {
            mutex_unlock(vdev->lock);
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值