FreeRTOS 第15章 消息队列

队列用来通信的,注意和列表的区别。

队列就像一辆火车,前面车头是一个控制室(队列TCB),后面一节一节的车厢就是消息空间(消息空间是单个消息块的大小)。它们在连续的内存中。在控制块中有表示这个队列的消息。其每个消息空间可以存储任意类型的数据,但是大小不能超过消息空间的大小。

不管是出队(读取消息)还是入队(发送消息),都要提前判断这个队列满足条件不,比如是否有数据可读和是否有空间可存。如果满足当然最好,如果不满足就会进入阻塞列表,这个列表会被放到相应的等待读列表和等待写列表,如果用户指定了阻塞的时间,那么在这段时间用户阻塞在相应的列表中,CPU去做其它的事情,在该期间内,条件满足,比如有消息了或者有空间了,就马上得到消息解除阻塞进入就绪队列,但是不一定马上执行,其优先级不一定是最高的,如果超过这段时间还没有满足条件,也直接从阻塞列表移除并加入就绪列表,也不一定马上执行,并返回的是errQUEUE,假如多个任务阻塞在同一个队列中,会按照恩威的优先级排序,优先级的高的先获得消息。

消息控制块

typedef struct QueueDefinition
{
	int8_t *pcHead;			//指向第一个消息空间		
	int8_t *pcTail;			//最后一个消息空间		
	int8_t *pcWriteTo;	    //写入时候的位置	

	union					
	{
		int8_t *pcReadFrom;			//联合体,当用于队列的时候,指向读的消息的指针	
		UBaseType_t uxRecursiveCallCount; //当用于互斥量时,用于计数
	} u;

	List_t xTasksWaitingToSend;		//发送消息阻塞列表
	List_t xTasksWaitingToReceive;	//写消息阻塞列表
	volatile UBaseType_t uxMessagesWaiting;   //总的消息个数以及用于信号量表示有效信号
	UBaseType_t uxLength;			//队列长度
	UBaseType_t uxItemSize;			//消息空间大小

	volatile int8_t cRxLock;	  
	volatile int8_t cTxLock;	//队列没锁,则可以解除任务阻塞,而锁了,则不能,该变量用来记录一个值,表示上锁期间消息入队的个数以及表示解除阻塞任务的个数,在队列解锁函数中使它们解除阻塞。

} xQUEUE;

消息TCB中最常用的是这些。

队列的创建函数xQueueCreate()。其是一个动态创建函数,也是一个宏定义

#define xQueueCreate( uxQueueLength, uxItemSize ) 
        xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

实际调用

 xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ),(queueQUEUE_TYPE_BASE ) )

在这个函数末尾多了一个queueQUEUE_TYPE_BASE,就是表示创建一个消息队列, 

xQueueGenericCreate函数是一个通用函数,后面的几章中一些创建函数通过一个宏定义最后都会调用这个函数。看看这个函数的实际执行部分可知:其首先确定队列TCB+总的消息空间,也就是确定内存,然后向系统申请一块连续的区域,并且把这片内存空间的首地址给pxNewQueue,这就表示队列的TCB是放在这片空间的最前面,然后计算消息存储的位置,也就是该空间的起始地址加上一个队列TCB的地址就是存储首地址。这说明队列的存储是低地址到高地址,而在栈中,是遵循满递减,是从高地址开始存入。然后调用一个新建队列初始化函数。在该函数中,先判断消息空间的尺寸,这是为之后的信号量做准备,当之前创建信号量的时候,就会执行到这里,而如果不是0,说明是创建消息队列,就把TCB中的队列头指针指向刚刚计算出的队列消息空间首地址,然后初始化其它信息。值得注意的是,在初始化其它消息的时候,不管是消息队列还是信号量,其它信息的初始化都会被执行,而信号量消息空间的长度为1,这是默认的,这说明后面几章中创建信号量时一定会通过宏定义隐藏掉关于长度的参数,而是直接传入一个固定的参数,例如

#define xSemaphoreCreateBinary() xQueueGenericCreate( 
( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

这个创建二值信号量的创建函数,通过一个宏定义把这些需要传入的参数都隐藏了。

而在新建队列初始化函数函数中还调用了重置消息队列函数(),在该函数中,也相当于做了队列的初始化,把TCB的尾指针指向内存的末尾,消息的数目为0,写入是从头指针写入,读是从尾指针尾指针指向的内存位置减一个消息大小处开始读入,都没上锁。这里很奇怪,为什么要加个重置函数,直接把那些变量在新建队列初始化函数中初始化不好吗?应该是为了在之后几章中使用,可以看到,在执行这个重置函数的时候,传入的是pdTRUE,在进入重置函数的时候,其只会执行初始化两个列表的事情。

消息队列删除函数()vQueueDelete(),只需要传入句柄就可以,其底层就是调用了一个vPortFree函数,动态释放内存,当然之后的信号量删除也是说到底调用这个函数,无非通过一个宏定义变成信号量的删除函数。

向消息队列发送函数()有多个,而且还要分中断版本和普通任务版本。

在普通版本中,需要传入参数,xQueue:消息队列的句柄,pvItemValue:一个指针指向被发送的消息,xTicksToWait:等待时间,也就是阻塞时间,如果阻塞时间为portMAX_DELAY,则不阻塞,直接挂起。

在中断保护版本中多了一个参数,pxHigherPriorityTaskWoken,还是因为在中断是不能执行任务切换的。比如说,在执行任务优先级为2时候,突然进入中断,并且中断函数执行了一个发送消息到队列的函数,中断常作为生产者,其向队列发送消息很普遍,而恰巧有个任务优先级为3的任务阻塞在等待读列表中,当队列有了消息,按道理它马上解除阻塞并且执行,但是中断中不能执行任务切换,必须退出中断才能切换,因此这个参数的功能是决定在中断退出后执行任务切换不,在使用的时候可以看到,例如237页,在中断中还有个判断,当这个pxHigherPriorityTaskWoken为真是才调用一个切换任务的中断保护版本,而在任务中发送到消息队列时,如果有更高优先级任务在等待读列表中阻塞,其应该会马上得到消息并且切换执行。

来看看xQueueGenericSend函数,其会根据最后一个参数的不同来执行不同的功能,因此在具体的代码中是有很大if判断。

首先判断队列满没有满或者是不是覆盖写入,二者满足一个就可以执行写入操作了。直接调用一个函数prvCopyDataToQueue(),把数据写入队列,该函数的最后一个参数就是写入的位置的选择,具体怎么写由该函数决定。进入其源码可以看到,四个判断分别使用了互斥量,从末尾写入,和从头写入,以及覆盖写入,可以看到其调用了一个叫memcpy的函数,内存复制函数,真正的把指定内容复制到指定的位置。而且还可以指定复制的个数,这个函数是C语言的库函数,在string.h头文件中定义。然后把阻塞在等待读列表和阻塞在事件列表中的任务中的任务解除阻塞并执行任务切换。任务切换这个过程一定会执行,但是切换不切换任务就看优先级了,queueYIELD_IF_USING_PREEMPTION()只是个宏定义函数,本质还是任务切换函数

如果队列满了,就查看xTicksToWait(),为0则直接返回错误信息,如果不为0,就需要阻塞了,并且查看这个阻塞超时的结构体变量初始化没有,这个是在任务延时那章使用的一个结构体。初始化好了就可以开始加入阻塞列表了,首先挂起调度器和队列上锁,然后检测一下是否已经超过规定的时间了。如果没有,并且此时消息空间还是满的,则加入到等待发送阻塞列表和延时列表,是两个列表之中,以及等待事件列表之中。如果还有消息空间,则允许入队,(在这段时间被取走了消息)。在这段时间中把队列上锁并且挂起调度器,不准任何任务操作列表而解除这个提出阻塞任务。

xQueueGenericSendFromISR是在中断中使用,没有阻塞机制。

读消息函数 xQueueReceive( xQueue, pvBuffer, xTicksToWait ) ,获取了消息并且从队列删除

#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) 
xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )

其实调用了 xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE ) 函数

xQueueReceiveFromISR()是中断版本

xQueuePeek()是获取了消息但是不删除消息,xQueuePeekFromISR()是中断版本.

在xQueueGenericReceive其实就做了这样一些事,看队列有有消息,有就要看是读取出队还是仅仅来瞧瞧,不删除消息,然后解除一些等待写任务并切换,如果是消息队列没有消息,就得初始化延时列表了,并切换任务。

在获取消息的时候,先定义一个存储消息的区域,用指针传递入消息获取函数中。当然指针也可以作为数据发送。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值