底层实现
队列传输数据的底层实现,是通过memcpy函数实现的内存拷贝。
队列创建函数(动态):
QueueHandle_t xQueueCreate(UBaseType_t uxQueueLength, UBaseType_t uxItemSize )其中,uxQueueLength是这个队列最大能容纳的元素数量;uxItemSize是每个元素的大小。
队列发送数据函数(尾插):
xQueueSend(QueueHandle_t xQueue,const void *const pvItemToQueue, TickType_t xTicksToWait )其中,xQueue是所使用的队列的句柄,pvItemToQueue是所要传输的数据的起始地址,xTicksToWait则是允许阻塞的最长时间。
xQueueSend的底层实现,便是从pvItemToQueue所指向的地址开始,使用memcpy函数拷贝uxItemSize长度字节的内存到xQueue的队列环形缓冲区的pcWriteTo所指向的内存,实现数据的拷贝。
了解了上述内容,再来看接下来的三种数据拷贝方式。
三种拷贝
三种拷贝方式分别是:深拷贝、浅拷贝、零拷贝。
由其名称也能看出,三种拷贝真正所拷贝的数据的大小越来越小,直到零拷贝完全未对任何数据进行拷贝。
传值(Queue by Value,可近似理解为深拷贝)
深拷贝,即将所有要拷贝的数据原原本本的拷贝到目标内存中。
这种拷贝方式最耗费内存和时间,但是对要拷贝的变量的存储方式没什么要求(也就是可以对局部变量进行拷贝)。
在使用队列发送数据时,如果使用的的队列句柄所指向的队列在创建时的
uxItemSize是sizeof(data_t),也就是数据块本身的大小,那此时所使用的方式就是深拷贝。
传引用(Queue by Reference,可近似理解为浅拷贝)
浅拷贝,即只将要拷贝的数据的地址拷贝到目标内存,只拷贝一个指针变量的大小。
这种拷贝方式耗费时间近乎于 0 。但是由于数据接收方想要访问这块数据,前提是传来的指针最终指向的数据必须能在原函数结束之后继续存在,也就是不能存储于函数的栈帧内(不能是局部变量)。这就要求这个指针要指向通过动态分配的内存,或者是一个全局变量。
在使用队列发送数据时,如果使用的的队列句柄所指向的队列在创建时的
uxItemSize是sizeof(void *),也就是指针的大小,并且传入的pvItemToQueue是指向数据的指针的地址,那此时所使用的方式就是浅拷贝。
基于共享内存的零拷贝(Zero Copy)
零拷贝,顾名思义,就是不对任何内存进行拷贝。
零拷贝是利用共享内存进行实现的,所以此时数据的发送、接收方实际上需要的仅是共享内存写入 / 读取完毕的通知。
这种拷贝方式完全不耗费任何拷贝时间,只需要考虑到通知对方的时间。
由于完全不需要拷贝,这种方式其实可以脱离队列进行实现,需要的只是一块共享内存以及消息通知机制。这个消息通知机制可以是二进制信号量、互斥量、事件组、任务通知(效率最高)等等。