V4L2框架-control的数据结构

Posted 嵌入式Max

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了V4L2框架-control的数据结构相关的知识,希望对你有一定的参考价值。

本篇文章写一下 V4L2 里面的众多 control 的组织方式,也就是它的数据结构。主要就是新建的 control 是如何存放的,以及在需要用到的时候如何查找。里面用到了类似于「桶」的概念,没错就是「桶排序」里面的那个桶,这种比较特殊的小优化为查找速度提供了不少的帮助。

话不多说,直接进入正题,本文章是基于 linux-4.4.138 内核来探讨的(太长不看直接拉文末)。

当前浏览器不支持播放音乐或语音,请在微信或其他浏览器中播放 V4L2框架-control的数据结构

几个结构体之间的关系

  • struct v4l2_ctrl:control 的结构体抽象,一个 control 就用一个实例化的 v4l2_ctrl 变量来表示。

  • struct v4l2_ctrl_ref:一个实例化的 v4l2_ctrl 的引用,可以看到该结构体里面包含了一个 struct v4l2_ctrl * 类型的指针变量成员,该指针成员指向的就是与之一一对应的 v4l2_ctrl 实例化对象。

  • struct v4l2_ctrl_handler:control 的集合,就比如一个设备它有很多个 control,这些众多的 contorl 被实例化为一个个的 v4l2_ctrl 变量,然后一一对应一个 v4l2_ctrl_ref 实例化对象,最后所有的 v4l2_ctrl_ref 都归属到同一个 v4l2_ctrl_handler 实例化对象中,并且受到 v4l2 框架与设备驱动的管理。

    V4L2框架-control的数据结构


    结构体之间的关系


图里面的关系是众多关系中比较简单的一种,其实在 v4l2_ctrl_ref 数组内部还有其它「乱七八糟」的关系,这些后面说到。

control 集合的初始化

control 合的实例化表示就是 struct v4l2_ctrl_handler,之前的文章里面有说过,就不再详述了,该实例一般需要调用一个函数进行初始化,其名曰:v4l2_ctrl_handler_init。这个函数在代码里面是一个宏定义,我就选取宏定义的实体 v4l2_ctrl_handler_init_class 函数来进行说明。

该函数的参数有这么几个:

  • hdlstruct v4l2_ctrl_handler * 类型,指向将要初始化的 control 集合的实例化对象。

  • nr_of_controls_hint:预设的 control 的数量,由用户传入,一般来说,某个模块需要的 control 对于驱动编写者来说都是事先知道的,这个值就是事先规划好的该模块应该有的 control 数量。

  • keystruct lock_class_key * 类型,是内核用来实现死锁检测机制的关联结构体之一,具体的原理没有去深究过,但是可以在代码中看到它与 hdl->lock 进行了某种关联,也就是说它是为了检测 hdl-lock 的死锁而服务的。

  • name:只读字符串,表示该死锁检测的名字。

一般情况下,后面两个参数都为空,函数实体:

/* Initialize the handler */
int v4l2_ctrl_handler_init_class(struct v4l2_ctrl_handler *hdl,
                 unsigned nr_of_controls_hint,
                 struct lock_class_key *key, const char *name)
{
    hdl->lock = &hdl->_lock;
    mutex_init(hdl->lock);
    lockdep_set_class_and_name(hdl->lock, key, name);
    INIT_LIST_HEAD(&hdl->ctrls);
    INIT_LIST_HEAD(&hdl->ctrl_refs);
    hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8;
    hdl->buckets = kcalloc(hdl->nr_of_buckets, sizeof(hdl->buckets[0]),
                   GFP_KERNEL);
    hdl->error = hdl->buckets ? 0 : -ENOMEM;
    return hdl->error;
}

最后两个参数就略过不表,因为一般情况也没用到,跟本的主题无关。该函数里面有一个语句:hdl->nr_of_buckets = 1 + nr_of_controls_hint / 8; 这表明「桶」的数量 nr_of_buckets 是 1 加上计划的 control 数量除以 8,这里就可以猜测到一个「桶」里面将来最多可以放置 8 个 controls。

V4L2框架-control的数据结构


control 的 buckets 排列

从前面可以得出,每一个桶里面最多存放 8 个成员(control),至于为什么是 8,我估计是根据内核定义的总的 conrol 值理论计算加上实际实践的结果得到的,我也没有去验证它是不是最优的数量,毕竟如果用户自定义了一大堆的 control 的话,这个 8 还真不一定是最优解。

struct v4l2_ctrl_handler 结构体类型内部还有一个 cached 成员,其类型是 struct v4l2_ctrl_ref *,这是一个非常小的优化点,它里面存放的是最后一次使用到的 contorl,就是在用户调用 ioctl 的时候首先找下这个里面存放的 control 值是不是用户想要用的值,如果是的话直接拿来用就行,是非常简单的一个优化。

新增一个 control

现在看下在 V4L2 框架内部的代码里面是如何新增一个 control 的,拿其中一个函数来举例,就是它了:v4l2_ctrl_new_std,但是不论是什么菜单类型的,自定义菜单类型还是啥的,最终都会调到一个地方,那就是 handler_new_ref 函数,过程就不说了,很单追踪下就好。这个函数在 v4l2_ctrls.c 文件里面,去找找吧。

函数的最开始有这么几行小字:

u32 id = ctrl->id;
u32 class_ctrl = V4L2_CTRL_ID2CLASS(id) | 1;
int bucket = id % hdl->nr_of_buckets;    /* which bucket to use */

第一个就不说了,第二个是把 ctrl 转换为一个 class 值,按照 V4L2 的说法,每 0xFFFF 个 control 当中就有一个 class,它应该就是一个分类吧,凡是 0xNNNNN0001(N就是任意值) 的 ID 都是一个 class,比如 V4L2_CID_USER_CLASS 就是 0x00980001。最后一个是根据 contorl id 的值找到对应的「桶」,分别放在不同的「桶」里面方便查找,注意这个计算的方式是取模运算,而不是除法运算,按照通常的理解应该是除法,这其实是一个优化。

假设说现在有八个 id 值是从 1~8 的 contorl,如果是采用除法来进「桶」查找的话,这八个 contorl 都会被放到第一个「桶」里面(下标为0),这样子就失去了「桶」的优化作用(想象一下桶排序的原则),而如果是取模的话这八个 contorl 就会均匀分布在八个「桶」里面,这就有点桶排序的意思了,查找的时候也非常快。但是也有一种情况,那就是八个 contorl id 值分别为 1,9,17,25,33,41,49,57,那就会全部被放到第二个「桶」里面(下标为1),不过实际使用当中更倾向于连续的 contorl 更加常见,这就是 contorl 类的意义,类使得关联性很强(功能类似)的 contorl 连续分布在一个 id 值段,这样高概率出现连续 id 值的 contorl 被用户使用。

不关心的先略过,下面有一个插入的代码:

/* 如果 ctrl_refs 为空或者新的 contorl 的 id 值比 ctrl_refs 链表尾部的 contorl
* id 值还要大,那就把新的 [v4l2_ctrl_ref] 实例化对象 [new_ref] 插入到 [ctrl_refs] 链表结尾。
*/
if (list_empty(&hdl->ctrl_refs) || id > node2id(hdl->ctrl_refs.prev)) {
    list_add_tail(&new_ref->node, &hdl->ctrl_refs);
    goto insert_in_hash;
}

/* 否则遍历 [ctrl_refs] 链表,直到找到第一个 id 值比将要插入的 contorl 的 id 大的
* [v4l2_ctrl_ref] 实例化对象 [ref],然后把新的 [new_ref] 插入到这个 [ref] 前面。
*/
list_for_each_entry(ref, &hdl->ctrl_refs, node) {
    if (ref->ctrl->id < id)
        continue;
    /* Don't add duplicates */
    if (ref->ctrl->id == id) {
        kfree(new_ref);
        goto unlock;
    }
    list_add(&new_ref->node, ref->node.prev);
    break;
}

由此可见,ctrl_refs 链表中的实例化对象 new_ref (也就是代表了一个 contorl)都是按照 id 值升序排列的。完成了 v4l2_ctrl_handler->ctrl_refs 链表的插入动作之,还有最后一步,那就是把新的 ctrl_ref 放入到一个哈希表中,也就是前面新建的多个「桶」,代码如下:

insert_in_hash:
    /* Insert the control node in the hash */
    new_ref->next = hdl->buckets[bucket];
    hdl->buckets[bucket] = new_ref;


插入 contorl

「桶」内部的 v4l2_ctrl_ref 实例化对象使用 v4l2_ctrl_ref 的 next 成员进行链接,它没有大小的顺序,只按照先来后到的顺序进行排列。

查找 control

查找的操作就简单很多了,代码如下(在 find_ref 函数内部截取):

bucket = id % hdl->nr_of_buckets;

/* Simple optimization: cache the last control found */
if (hdl->cached && hdl->cached->ctrl->id == id)
    return hdl->cached;

/* Not in cache, search the hash */
ref = hdl->buckets ? hdl->buckets[bucket] : NULL;
while (ref && ref->ctrl->id != id)
    ref = ref->next;

if (ref)
    hdl->cached = ref; /* cache it! */
return ref;
  1. 先根据需要查找的 control 的 id 来获取「桶」的索引号。

  2. cached 里面找一下,说不定一次性就找到了,如果找到的话就直接返回了。

  3. 否则的话到指定的「桶」里面从头到尾遍历一遍「桶」内部的 refs。

数据结构

一个考:从上面的整篇描述来看,其实这个非常像排序算法里面的「桶排序」,但是也有不少不同的地方。

  • 桶排序中桶内部的数据也是有序的,而 contorl 框架里面为了简化代码复杂度,桶内部没有进一步排序,况且也没必要进行桶内的排序。

  • 桶排序的内部数字是有重复的,或许有可能是负数,但是 contorl 框架限定了其 id 值是非负整数,并且不会重复,是全局唯一的。

  • 它们的目的是相同的,都是排序之后方便查找。

总之,contorl 框架里面的这个做法可以看作是一个简化版的「桶排序」,由于本身 contorl 的数量不太可能非常庞大,并且数据的连续性也比较强,所以一个没有那么“复杂”的简化版「桶排序」算法就能很好的满足插入与查找的速度和空间消耗之间的平衡。

从上面也可以看出在 contorl 框架里面,一个个的实例化 v4l2_ctrl 对象被按照不同的方式建立了多套索引链表或数组结构。比如:

  1. v4l2_ctrl_handler 里面的 ctrls 成员便将所有的实例化 control 对象按照先来后到的顺序串成一个双向链表,链表节点的类型就是 struct v4l2_ctrl 类型的指针。

  2. v4l2_ctrl_handler 里面的 ctrl_refs 成员将所有的实例化 struct v4l2_ctrl_ref 对象按照其 id 值从小到大串联在一起,链表节点的类型是 struct v4l2_ctrl_ref 指针。

  3. v4l2_ctrl_handler 里面的 buckets 成员将众多的 contorl 分成一个个的「桶」,通过对 contorl 的 id 进行取模运算来决定放在哪一个桶里面,桶内部的排序遵循先来后到原则。

End

继续分享极客时间的课程,由于一次分享一篇太慢,所以以后每次会分享三篇或者是一整个章节,方便连续阅读,此次更新内容如下:
[04 | 复杂度分析(下):浅析最好、最坏、平均、均摊时间复杂度]
[03 | 复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?]
[02 | 如何抓住重点,系统高效地学习数据结构与算法?]
[01 | 为什么要学习数据结构和算法?]
微信内部无法放外链,使用微信手机客户端点击【阅读原文】打开之后文章同样的位置会有链接,直接点进去看即可。):

如果看到最后了的话,本文还有一个小游戏,答案在本文中,提示:文字、支付宝、口令。时间限24个小时内了,不知道可以留言问咯。我不是没理由的弄得这么繁琐,到最后可能会让人感觉说:你狠无聊哎o(︶︿︶)o 。我弄这个呢有两个理由:一个是尝试拉近下读者与作者的距离;二个是增加一些文章的趣味性(才怪),顺便感谢下你愿意读完这冗长的文字。觉得没意思的话就在后台直接怼我就好啦,或者忽视也可,如果觉得还可以的话也欢迎提提意见,或者分享下文章,就酱。



想做的事情就去做吧



以上是关于V4L2框架-control的数据结构的主要内容,如果未能解决你的问题,请参考以下文章

深入理解linux内核v4l2框架之videobuf2

项目之利用 V4L2应用程序框架 进行视频录制

V4L2引入(含浅析UVC)

二十四V4L2框架分析和虚拟摄像头驱动编写

1. 摄像头V4L2驱动框架分析

V4L2框架概述