linux v4l2学习之-消息机制

linux v4l2学习之-消息机制

感兴趣可以加QQ群85486140,大家一起交流相互学习下!


v4l2的消息同大部分的消息组织形式类似,可以理解成都是以队列的形式,有人往里面push,有人去get。看了大半天才把消息队列的机制了解清楚。在了解之前需要重温一下下面几个结构体。
kernel版本:kernel3.4

一、v4l2消息队列理解准备条件

v4l2消息结构体了解之前,仍然需要了解几个结构体。结构体中的数据域最好结合代码分析一下作用。

1.1 struct v4l2_event

v4l2的单个消息用下面结构体来表述的。

struct v4l2_event {
	__u32				type;
	union {
		struct v4l2_event_vsync		vsync;
		struct v4l2_event_ctrl		ctrl;
		struct v4l2_event_frame_sync	frame_sync;
		__u8				data[64];
	} u;
	__u32				pending;
	__u32				sequence;
	struct timespec			timestamp;
	__u32				id;
	__u32				reserved[8];
};

这里主要说明下面几个数据域,其它用得少,暂时不记录。

  • type:毋庸置疑这是消息的类型,目前发现v4l2原生的类型包含下面这些,这些用户可以根据需要自己定制,最后一个私有的V4L2_EVENT_PRIVATE_START就是为扩展做准备的。
#define V4L2_EVENT_ALL				0
#define V4L2_EVENT_VSYNC			1
#define V4L2_EVENT_EOS				2
#define V4L2_EVENT_CTRL				3
#define V4L2_EVENT_FRAME_SYNC			4
#define V4L2_EVENT_PRIVATE_START		0x08000000
  • data[64]:这里用户可以根据实际情况需要,用这64个字节定义自己的消息实体。

  • sequence:这个用来记录该消息的序列号(并不是index),其和struct v4l2_fh中的sequence是一致的,后面会结合代码分析。

  • timestamp:消息发送的时间戳,代码中也没发现具体使用的地方,应该是用来判断消息生命周期准备吧。暂且不关心

  • pending:用来记录消息队列中尚未处理的消息数量。

  • id:命令id.

1.2 struct v4l2_event_subscription

订阅消息发起时,订阅的消息用此结构体描述。此订阅发起者既可以是kenle模块,也可以是用户空间的管理者。这里第一次情调一下消息只有订阅后,别的模块才能把消息放到模块的消息队列中,没有定义的消息是无法queue进去的

struct v4l2_event_subscription {
	__u32				type;
	__u32				id;
	__u32				flags;
	__u32				reserved[5];
};

1.3 struct v4l2_fh

消息队列是用此结构体描述的。

struct v4l2_fh {
	struct list_head	list;
	struct video_device	*vdev;
	struct v4l2_ctrl_handler *ctrl_handler;
	enum v4l2_priority	prio;

	/* Events */
	wait_queue_head_t	wait;
	struct list_head	subscribed; /* Subscribed events */
	struct list_head	available; /* Dequeueable event */
	unsigned int		navailable;
	u32			sequence;
};
  • list:链表入口,因为一个设备可能有好几条消息队列。(一般情况下打开一次设备会创建一个消息队列,不过一般请看下只会打开一次)
  • vdev:拥有此消息队列的设备
  • ctrl_handler:这其中也牵扯很多其它结构体,只需要了解这与控制元素相关就可以了。
  • prio:消息队列优先级。
  • wait:等待队列,由于可能有几个线程同时访问消息队列,所有等待消息队列可用的线程都会记录在这个等待列表中。
  • subscribed:这个是订阅消息链表。只有订阅的消息才能够queue到消息队列中,反之是无法queue到队列中的。
  • available:消息队列,可用的消息都以链表的形式保存在这里。
  • navailable:消息队列中可用消息的数量。
  • sequence:序列号,此数据域会一直自加下去,直到内核对象消亡。

写到这里消息队列、订阅消息、就绪消息的存在形式和依赖关系应该初步了解了,他们的组织结构大体如下所示:
在这里插入图片描述
注意:

  • available表示的是还没处理的消息,依然挂在链表中。
  • subscribed表示的是订阅的消息,单个订阅消息仍然会保存几个子消息(毕竟一个消息可以发送多次)。这里要特别注意消息queue进来时,先会保存到subscribed对应的订阅消息列表,然后才会链接到available链表上,上面蓝色和橙色之间的箭头不一定是一一对应的。具体为什么会保存多个自消息,可以继续了解一下订阅消息结构体。

1.4 struct v4l2_subscribed_event

订阅消息是以struct v4l2_subscribed_event结构存在,其成员详细分布如下所示:

struct v4l2_subscribed_event {
	struct list_head	list;
	u32			type;
	u32			id;
	u32			flags;
	struct v4l2_fh		*fh;
	struct list_head	node;
	void			(*replace)(struct v4l2_event *old,
					   const struct v4l2_event *new);
	void			(*merge)(const struct v4l2_event *old,
					 struct v4l2_event *new);
	unsigned		elems;
	unsigned		first;
	unsigned		in_use;
	struct v4l2_kevent	events[];
};

这了简单介绍几个重要变量

  • list:将订阅消息插入到消息队列里的链接指针
  • flags:一些标志位记录
  • fh:可以理解成,该消息挂在哪个消息队列之上。
  • replace:替换消息的方法,当订阅消息队列只能存放1个消息时,才会触发此替换方法。(此方法在订阅消息队列满以及使用struct v4l2_ctrl控制
  • merge:当订阅消息队列有多余2个容量时,会触发merge方法(此方法在订阅消息队列满以及使用struct v4l2_ctrl控制
  • elems:订阅消息队列中最多能存放消息的数量,此变量在订阅消息下发过来时确定,然后会由kernel分配对应的内存。
  • first:这个可以理解成游标指针,记录当前订阅消息队列中入队时间最久且需要处理的消息
  • in_use:入队的消息数量
  • events:这里存放的只是一个指针,其大小由发起订阅时确定。

小结:此订阅消息,可以理解成一类消息的组合

二、Enqueue消息

static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev,
		const struct timespec *ts)
{
	struct v4l2_subscribed_event *sev;
	struct v4l2_kevent *kev;
	bool copy_payload = true;

	/* Are we subscribed? */
	//这里特别要注意了,前面说过消息必须先订阅,才能queue进来,这了可以看到
	//当检测到订阅消息列表中,没有当前消息,则直接return.
	sev = v4l2_event_subscribed(fh, ev->type, ev->id);
	if (sev == NULL)
		return;

	/* Increase event sequence number on fh. */
	//消息序列号自动+1.
	fh->sequence++;

	/* Do we have any free events? */
	//显然当in_use和elems相等时,此时消息数组已经满员了,此时需要将最旧的
	//没有处理的消息移除订阅消息队列。
	if (sev->in_use == sev->elems) {
		/* no, remove the oldest one */
		//队列中已经慢了,将第一个消息移除队列,腾出空间。
		kev = sev->events + sev_pos(sev, 0);
		list_del(&kev->list);
		sev->in_use--; //如队列消息数目-1.
		sev->first = sev_pos(sev, 1); //将可用指针指向第二个消息实体
		fh->navailable--;//消息队列消息数量-1.
		if (sev->elems == 1) {
		//如果子消息数量只有1个,则替换该消息,注意由于都是同类消息,只需要改变状态即可。
			if (sev->replace) {
				sev->replace(&kev->event, ev);
				copy_payload = false;
			}
		} else if (sev->merge) {
			struct v4l2_kevent *second_oldest =
				sev->events + sev_pos(sev, 0);
			sev->merge(&kev->event, &second_oldest->event);
		}
	}

	/* Take one and fill it. */
	//这了取出in_use所对应的消息实体,来存放具体的消息内容。
	kev = sev->events + sev_pos(sev, sev->in_use);
	kev->event.type = ev->type;
	if (copy_payload)//拷贝数据内容
		kev->event.u = ev->u;
	kev->event.id = ev->id;
	kev->event.timestamp = *ts;
	kev->event.sequence = fh->sequence;
	sev->in_use++;//可用消息索引往后移动,相当于记录指针。
	//上面已经把消息添加到订阅消息子消息列表中了,种类把消息再次插入具体设备的消息队列中。
	list_add_tail(&kev->list, &fh->available);

	fh->navailable++;//需处理消息数量+1.

	wake_up_all(&fh->wait);//唤醒所有等待消息的线程
}

这里就是如队列消息的最重要的函数,此函数大概有下面几个流程。

  • 1.先检查消息是否存在订阅消息列表中,不存在的话不允许该消息入队列,反之允许消息入队列。
  • 2.当该消息对应的订阅消息对象子消息满员之后,则将子消息队列中最老的那个替换掉,以留给新的消息。
  • 3.将新消息挂接到v4l2_fh可用链表中,以进行后续其它处理

注意:下图是struct v4l2_subscribed_event结构体中的struct v4l2_kevent子消息在订阅消息结构体中的存放形态。
在这里插入图片描述
上面子消息队列中已经入队了2个消息,此时in_use=2,而此时记录指针指向1,则first=1,剩下的红色都是空闲子消息缓存。子消息的结构如下所示:

struct v4l2_kevent {
	struct list_head	list;
	struct v4l2_subscribed_event *sev;
	struct v4l2_event	event;
};

三、Dequeue消息

Dequeue操作就是取消息的操作(类似于取buffer时用dequeue_buffer),相比较消息的enqueue操作,dequeue操作简单多了。这里仍然根据代码流程来分析。

//从参数中可以看到,v4l2_fh即为消息队列,该队列被一个设备对象所拥有
static int __v4l2_event_dequeue(struct v4l2_fh *fh, struct v4l2_event *event)
{
	struct v4l2_kevent *kev;
	unsigned long flags;

	spin_lock_irqsave(&fh->vdev->fh_lock, flags);
	//判断队列中是否有可用的消息,没有消息直接返回。
	if (list_empty(&fh->available)) {
		spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);
		return -ENOENT;
	}

	WARN_ON(fh->navailable == 0);
	//从可用列表中取出第一个消息的入口,并将该消息从消息列表中删除。
	kev = list_first_entry(&fh->available, struct v4l2_kevent, list);
	list_del(&kev->list);
	fh->navailable--;//消息数量-1,
	//将当前还需处理的消息列表,复制给刚才取出来的消息,以便它做其它操作。
	kev->event.pending = fh->navailable;
	*event = kev->event;
	//将消息对应的订阅消息对象中的记录指针想后移动1个
	kev->sev->first = sev_pos(kev->sev, 1);
	kev->sev->in_use--;//入队列的消息数量-1.

	spin_unlock_irqrestore(&fh->vdev->fh_lock, flags);

	return 0;
}

取消息相对简单多了,大概就下面几步

  • 1.检查消息列表是否是为空,不为空则从中取出子消息。
  • 2.将上面子消息,从上面的消息列表中删除,并减少消息队列中消息的数量
  • 3.将消息对应的订阅消息对象的记录指针后移,以及如队列消息-1.

四、案例学习

  • 上层
    这里以高通daemon进程起来后,server就向video0 node中订阅了NEW_SESSION、DEL_SESSION等消息。
  subscribe.type = MSM_CAMERA_V4L2_EVENT_TYPE;
  for (i = MSM_CAMERA_EVENT_MIN + 1; i < MSM_CAMERA_EVENT_MAX; i++) {
    subscribe.id = i;
    if (ioctl(hal_fd->fd[0], VIDIOC_SUBSCRIBE_EVENT, &subscribe) < 0)
      goto subscribe_failed;
  }
  其中子消息的类型:
#define MSM_CAMERA_EVENT_MIN    0
#define MSM_CAMERA_NEW_SESSION  (MSM_CAMERA_EVENT_MIN + 1)
#define MSM_CAMERA_DEL_SESSION  (MSM_CAMERA_EVENT_MIN + 2)
#define MSM_CAMERA_SET_PARM     (MSM_CAMERA_EVENT_MIN + 3)
#define MSM_CAMERA_GET_PARM     (MSM_CAMERA_EVENT_MIN + 4)
#define MSM_CAMERA_MAPPING_CFG  (MSM_CAMERA_EVENT_MIN + 5)
#define MSM_CAMERA_MAPPING_SES  (MSM_CAMERA_EVENT_MIN + 6)
#define MSM_CAMERA_MSM_NOTIFY   (MSM_CAMERA_EVENT_MIN + 7)
#define MSM_CAMERA_EVENT_MAX    (MSM_CAMERA_EVENT_MIN + 8)

上面是上层对应的代码,ioctl会调用到对应设备驱动实现的vidioc_subscribe_event,这里kernel已经实现了对应的接口。

代码:v4l2-ioctl.c
	case VIDIOC_SUBSCRIBE_EVENT:
	{
		struct v4l2_event_subscription *sub = arg;

		if (!ops->vidioc_subscribe_event)
			break;

		ret = ops->vidioc_subscribe_event(fh, sub);
		if (ret < 0) {
			dbgarg(cmd, "failed, ret=%ld", ret);
			break;
		}
		dbgarg(cmd, "type=0x%8.8x", sub->type);
		break;
	}

上面执行完成后,订阅消息NEW_SESSION、DEL_SESSION等消息会挂接到设备的fh->subscribed订阅链表上。下一个操作则从订阅列表上查找入队列的消息,存在的话允许如队列,反之不允许。

static void __v4l2_event_queue_fh(struct v4l2_fh *fh, const struct v4l2_event *ev,
		const struct timespec *ts)
{
	struct v4l2_subscribed_event *sev;
	struct v4l2_kevent *kev;
	bool copy_payload = true;

	/* Are we subscribed? */
	sev = v4l2_event_subscribed(fh, ev->type, ev->id);
	if (sev == NULL)//如果订阅过的话,这里就不会进来
		return;

五、总结

看了半天代码就总结一句话“消息必须先订阅,才允许入队列”。同时也需要知道下面几点。

  • .订阅消息对象默认元素数量为1,如果设置的话就按设置的来分配内存。
  • 14
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值