消息队列是信号量的底层实现逻辑。
信号量是通信机制。信号量可以代表资源,不为0则有资源,就可以提出申请,申请了资源后资源量(消息量)就会减1,释放了信号量,资源量(消息量)就会加1。当没有资源(消息)的时候,就阻塞,当资源为满的时候,当然可以申请,因为资源量一开始就是定义好的,而且一个任务获取了资源后还必须要释放资源操作,所以一个资源量定义为5个,当它当地可用资源为5个时,就不可能有任务还会释放资源,毕竟它没有获取资源。
二值信号量可以看作只有一个消息空间的消息队列,要么空要么满。
二值信号可用于同步功能或者临界资源访问,创建二值信号量设置为空。同步是指,假设需要任务2在任务1之后马上执行,那么就可以在任务1执行完了特定目标之后释放信号量(往消息队列发送一个消息,二值信号量(消息)变为1,可以获取信号量(消息),则可以解除等待读任务),而只需要在任务2函数中加个获取二值信号量函数而阻塞,当任务1释放信号量,其就会马上得到信号量被加入就序列表,如果优先级高,则直接运行。也可以实现中断后同步任务,中断只需要释放信号量,任务就获取这个信号量。
计数信号量看作长度等于总的信号量个数的消息队列。互斥信号量是特殊的二值信号量,创建是满的,也就是有1个信号。递归信号量是可以重复调用的信号量。
二值信号量创建。
因为信号量是依赖消息队列的,所以它们的控制块是一样的,只是个别成员意义不同。
//在消息队列中表示消息的个数,在信号量表示有效信号量的个数
volatile UBaseType_t uxMessagesWaiting;
//用作消息时表示队列的长度,信号量表示最大的信号量的可用个数
UBaseType_t uxLength;
//用作消息队列表示消息空间的大小,信号量时为0,无须分配空间
UBaseType_t uxItemSize; //消息空间大小
创建二值信号量的动态创建函数 xSemaphoreCreateBinary(); 其返回一个BinarySem_Handle 句柄,它是通过一个宏展开的,
#define xSemaphoreCreateBinary()
xQueueGenericCreate( ( UBaseType_t ) 1,
semSEMAPHORE_QUEUE_ITEM_LENGTH,
queueQUEUE_TYPE_BINARY_SEMAPHORE )
其实就是调用消息队列的创建函数,传入的第一个参数表示是队列长度1,第二个是消息空间大小0,第三个表示是创建二值信号量。以下是一些宏定义以在队列通用创建函数第三个参数使用。
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) //消息队列
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U )
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) //互斥
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) //计数
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) //二值
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) //递归
在二值信号量创建后,消息空间的大小是传入的0,就相当于消息队列只有一个消息队列TCB,所以消息是通过 uxMessagesWaiting这个来实现的。因为其默认为0,因此在别的任务获取信号量之前应该释放信号量,把uxMessagesWaiting加1。
计数信号的创建
xSemaphoreCreateCounting( uxMaxCount,uxInitaialCount)
在创建时指定最大的信号量个数以及初始化信号量个数,传入的比如是5,5,表示最多容纳五个信号量,初始值信号也是5个。
查看源码可知,调用这个这个函数的时候,是把uxMaxCount作为通用队列创建的第一个参数也就是长度,第二个是0,第三个是表示创建计数信号量。该函数执行完后uxMessagesWaiting为0,因为其在消息队列创建是代表消息的个数,刚刚创建当然为0,然后把uxInitaialCount初始计数值赋给这个uxMessagesWaiting。可见,二值和计数信号量的底层实现都是依靠uxMessagesWaiting来计数的。注意,二值信号量和计数信号量是使用同一个句柄SemaphoreHandle_t,不能搞混了,句柄感觉都是空指针。
信号量删除函数
vSemaphoreDelete(),可用来删除互斥,二值,递归,计数信号量,通用的,假如有任务阻塞在列表中就不要删除了。其本质就是队列删除函数 vQueueDelete()
信号量释放函数
xSemaphoreGive(),其本质就是调用的消息队列的发送函数
#define xSemaphoreGive( xSemaphore )
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),
NULL,
semGIVE_BLOCK_TIME,
queueSEND_TO_BACK )
在函数中只需要传入二值量的句柄就可以,而且阻塞时间是semGIVE_BLOCK_TIME,为0. 所以当释放信号时,如果uxMessagesWaiting为0,则其加一,并返回pdPASS,如果uxMessagesWaiting为1,表示队列已经满了,并且阻塞时间为0,就直接返回err_QUEUE_FULL。
当然也有在中断中使用的中断版本xSemaphoreGiveFromISR()
在中断中可释放二值信号量,计数信号量,不能释放互斥量。
该函数的本质是调用xQueueGiveFromISR(),消息队列的发送函数的中断版本。当然调用时其参数不止一个信号量句柄,当然还有老朋友pxHigherPriorityTaskWoken,当释信号量时,一些阻塞的任务就有可能被加入就绪,而且很有可能是一个高优先级的,所以在这个函数执行完之后把pxHigherPriorityTaskWoken设置为pdtrue,则表示要切换任务。
信号量的获取函数 xSemaphoreTake(),可获取二值量,计数信号量,互斥量
就是调用通用队列接收函数,传入参数,信号量的句柄和阻塞时间。获取到了则返回pdtrue,不能获取则加入阻塞列表,在阻塞时间内有信号量入队,则获得信号量,解除阻塞,如果这段时间过了还没有得到数据,也直接进入就绪列表,返回errQUEUE_EMPTY,如果阻塞时间设置为port_MAX_DELAY,就会一直阻塞,直到该信号量有效。
信号量的获取函数的中断版本 xSemaphoreTakeFromISR(),不怎么常用,毕竟中断常作为生产者。假如使用,它也需要传入信号量句柄和老朋友pxHigherPriorityTaskWoken。
注意:在信号量这块,可看到只有获取信号量函数xSemaphoreTake加了第二个参数,即阻塞时间,而信号量释放函数是默认阻塞时间为0,也就是如果信号量满了且没有其它任务获取信号量,是不能一直释放的,会马上返回err_QUEUE_FULL.