当前位置:网站首页>FreeRTOS个人笔记-信号量
FreeRTOS个人笔记-信号量
2022-07-26 00:03:00 【Couvrir洪荒猛兽】
根据个人的学习方向,学习FreeRTOS。由于野火小哥把FreeRTOS讲得比较含蓄,打算在本专栏尽量细化一点。作为个人笔记,仅供参考或查阅。
配套资料:FreeRTOS内核实现与应用开发实战指南、野火FreeRTOS配套视频源码、b站野火FreeRTOS视频。搭配来看更佳哟!!!
信号量
信号量
信号量(Semaphore)可以实现任务之间同步或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。
信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。其值的含义分两种情况:
0: 表示没有积累下来的释放信号量操作,且有可能有在此信号量上阻塞的任务。
正值:表示有一个或多个释放信号量操作。
二值信号量
二值信号量既可以用于临界资源访问也可以用于同步功能。
二值信号量和互斥信号量非常相似,但是有一些细微差别:
互斥信号量有优先级继承机制, 二值信号量则没有这个机制。
这使得二值信号量更偏向应用于同步功能(任务与任务间的同步或任务和中断间同步), 而互斥信号量更偏向应用于临界资源的访问。
用作同步时,信号量在创建后应被置为空,任务 1 获取信号量而进入阻塞,任务 2 在某种条件发生后,释放信号量,于是任务 1 获得信号量得以进入就绪态,如果任务 1 的优
先级是最高的,那么就会立即切换任务,从而达到了两个任务间的同步。同样的,在中断服务函数中释放信号量, 任务 1 也会得到信号量,从而达到任务与中断间的同步。
可以将二值信号量看作只有一个消息的队列, 因此这个队列只能为空或满(因此称为二值),我们在运用的时候只需要知道队列中是否有消息即可,而无需关注消息是什么。
计数信号量
二值信号量可以被认为是长度为 1 的队列,而计数信号量则可以被认为长度大于 1的队列,信号量使用者依然不必关心存储在队列中的消息,只需关心队列是否有消息即可。
计数信号量常用于事件计数与资源管理。每当某个事件发生时,任务或者中断将释放一个信号量(信号量计数值加 1),当处理被事件时(一般在任务中处理),
处理任务会取走该信号量(信号量计数值减 1),信号量的计数值则表示还有多少个事件没被处理。
此外,可以使用计数信号量进行资源管理,信号量的计数值表示系统中可用的资源数目,任务必须先获取到信号量才能获取资源访问权,当信号量的计数值为零时表示系统没有可用的资源,但是要注意,在使用完资源的时候必须归还信号量,否则当计数值为 0 的时候任务就无法访问该资源了。
计数型信号量允许多个任务对其进行操作,但限制了任务的数量。计数型信号量为 0 时,当我们释放了这个资源,后面的任务才能对这个资源进行访问。
互斥量
互斥信号量其实是特殊的二值信号量,由于其特有的优先级继承机制从而使它更适用于简单互锁,也就是保护临界资源。
用作互斥时,信号量创建后可用信号量个数应该是满的, 任务在需要使用临界资源时(临界资源是指任何时刻只能被一个任务访问的资源),
先获取互斥信号量,使其变空,这样其他任务需要使用临界资源时就会因为无法获取信号量而进入阻塞,从而保证了临界资源的安全。
在操作系统中,我们使用信号量的很多时候是为了给临界资源建立一个标志,信号量表示了该临界资源被占用情况。
这样,当一个任务在访问临界资源的时候,就会先对这个资源信息进行查询,从而在了解资源被占用的情况之后,再做处理,从而使得临界资源得到有效的保护。
递归信号量
递归信号量,每获取一次可用信号量个数就会减少一个,但是递归则不然,对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。
任务成功获取几次递归互斥量, 就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只有持有递归信号量的任务才能获取与释放。
应用场景
二值信号量应用场景
信号量使用最多的一般都是二值信号量与互斥信号量。
二值信号量:信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1 。
某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位,这样子很消耗 CPU资源并且妨碍其它任务执行,
更好的做法是任务的大部分时间处于阻塞状态(允许其它任务执行),直到某些事件发生该任务才被唤醒去执行。
可以使用二进制信号量实现这种同步, 当任务获取信号量时,因为此时尚未发生特定事件,信号量为空,任务会进入阻塞状态;
当事件的条件满足后,任务/中断便会释放信号量,告知任务这个事件发生了,任务取得信号量便被唤醒去执行对应的操作,任务执行完毕并不需要归还信号量,
这样子的 CPU 的效率可以大大提高, 而且实时响应也是最快的。
某个任务使用信号量在等中断标记的发生,在这之前任务已经进入了阻塞态,在等待着中断的发生,当在中断发生之后,释放一个信号量,也就是我们常说的标记。
当它退出中断之后,操作系统会进行任务的调度,如果这个任务能够运行,系统就会把等待这个任务运行起来,这样子就大大提高了我们的效率。
二值信号量在任务与任务中同步的应用场景:
假设我们有一个温湿度传感器,假设是 1s 采集一次数据,那么我们让他在液晶屏中显示数据出来,这个周期也是要 1s 一次的,
如果液晶屏刷新的周期是 100ms 更新一次,那么此时的温湿度的数据还没更新,液晶屏根本无需刷新,只需要在 1s 后温湿度数据更新的时候刷新即可,
否则 CPU 就是白白做了多次的无效数据更新, CPU 的资源就被刷新数据这个任务占用了大半,造成 CPU 资源浪费。
如果液晶屏刷新的周期是 10s 更新一次,那么温湿度的数据都变化了 10 次,液晶屏才来更新数据,那拿这个产品有啥用,根本就是不准确的,
所以,还是需要同步协调工作,在温湿度采集完毕之后,进行液晶屏数据的刷新,这样子,才是最准确的,并且不会浪费 CPU的资源。
二值信号量在任务与中断同步的应用场景:
我们在串口接收中,我们不知道啥时候有数据发送过来,有一个任务是做接收这些数据处理,总不能在任务中每时每刻都在任务查询有没有数据到来,那样会浪费 CPU 资源,
所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候, 任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量, 任务就立即从阻塞态中解除,进入就绪态,
然后运行的时候处理数据,这样子系统资源就会很好的被利用起来。
运作机制
二值信号量运作机制
创建信号量时, 系统会为创建的信号量对象分配内存,并把可用信号量初始化为用户自定义的个数, 二值信号量的最大可用信号量个数为 1。
二值信号量获取, 任何任务都可以从创建的二值信号量资源中获取一个二值信号量,获取成功则返回正确, 否则任务会根据用户指定的阻塞超时时间来等待其它任务/中断释放信号量。
在等待这段时间,系统将任务变成阻塞态, 任务将被挂到该信号量的阻塞等待列表中。
假如某个时间中断/任务释放了信号量,由于获取无效信号量而进入阻塞态的任务将获得信号量并且恢复为就绪态。
计数信号量运作机制
计数信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但会限制任务的最大数目。访问的任务数达到可支持的最大数目时,会阻塞其他试图获取该信号量的任务,直到有任务释放了信号量。
这就是计数型信号量的运作机制,虽然计数信号量允许多个任务访问同一个资源,但是也有限定,比如某个资源限定只能有 3 个任务访问,那么第 4 个任务访问的时候,会因为获取不到信号量而进入阻塞,
等到有任务(比如任务 1)释放掉该资源的时候,第 4 个任务才能获取到信号量从而进行资源的访问。
信号量结构体
如果使用信号量或者互斥量,需要包含 semphr.h 头文件。
FreeRTOS 的信号量控制块结构体与消息队列结构体是一模一样的, 只不过结构体中某些成员变量代表的含义不一样。
typedef struct QueueDefinition
{
int8_t *pcHead; //队列头指针
int8_t *pcTail; //队列尾指针
int8_t *pcWriteTo; //指向队列消息存储区下一个可用消息空间
//pcReadFrom 与 uxRecursiveCallCount 是一对互斥变量, 使用联合体用来确保两个互斥的结构体成员不会同时出现。
//当结构体用于队列时,pcReadFrom 指向出队消息空间的最后一个,即读取消息时候是从 pcReadFrom 指向的空间读取消息内容
//当结构体用于互斥量时,uxRecursiveCallCount 用于计数,记录递归互斥量被“调用” 的次数。
union
{
int8_t *pcReadFrom;
UBaseType_t uxRecursiveCallCount;
}u;
//发送消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列已满,想要发送消息的任务无法发送消息。
//获取消息阻塞列表,用于保存阻塞在此队列的任务,任务按照优先级进行排序,由于队列是空的,想要获取消息的任务无法获取到消息。
List_t xTasksWaitingToSend;
List_t xTasksWaitingToReceive;
//uxMessagesWaiting用于消息队列,记录当前消息队列的消息个数
//uxMessagesWaiting用于信号量,表示有效信号量的个数
//uxLength用于消息队列,表示队列长度,即能存放多少消息
//uxLength用于信号量,表示最大的信号量可用个数
//uxItemSize用于消息队列,表示单个消息的大小
//uxItemSize用于信号量,则无需存储空间,为 0 即可。
volatile UBaseType_t uxMessagesWaiting;
UBaseType_t uxLength;
UBaseType_t uxItemSize;
//这两个成员变量为 queueUNLOCKED 时,表示队列未上锁;当这两个成员变量为 queueLOCKED_UNMODIFIED 时,表示队列上锁。
//队列上锁后,储存从队列收到的列表项数目,也就是出队的数量,如果队列没有上锁,设置为 queueUNLOCKED
//队列上锁后,储存发送到队列的列表项数目,也就是入队的数量,如果队列没有上锁,设置为 queueUNLOCKED。
volatile int8_t cRxLock;
volatile int8_t cTxLock;
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated;
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
typedef xQUEUE Queue_t;uxMessagesWaiting作为信号量有以下两种情况:
如果信号量是二值信号量、互斥信号量,这个值是 1 则表示有可用信号量,如果是 0 则表示没有可用信号量。
如果信号量是计数信号量,这个值表示可用的信号量个数,在创建计数信号量的时候会被初始化一个可用信号量个数 uxInitialCount,最大不允许超过创建信号量的初始值 uxMaxCount。
uxLength作为信号量有以下两种情况:
如果信号量是二值信号量、互斥信号量,这个值最大为1,因为信号量要么是有效的,要么是无效的。
如果信号量是计数信号量,这个值表示最大的信号量个数,在创建计数信号量的时候将由用户指定这个值 uxMaxCount。
二值信号量的释放和获取都是通过操作队列结控制块构体成员 uxMessageWaiting 来实现的,它表示信号量中当前可用的信号量个数。
在信号量创建之后,变量 uxMessageWaiting 的值为 0,这说明当前信号量处于无效状态, 此时的信号量是无法被获取的, 在获取信号之前,应先释放一个信号量。
创建信号量
创建二值信号量
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endifxSemaphoreCreateBinary()用于创建一个二值信号量, 并返回一个句柄。 其实二值信号量和互斥量都共同使用一个类型 SemaphoreHandle_t 的句柄。
使用该函数创建的二值信号量是空的 ,在使用函数 xSemaphoreTake()获取之前必须先调用函数 xSemaphoreGive()释放后才可以获取。
如果是使用老式的函数 vSemaphoreCreateBinary()创建的二值信号量,则为 1, 在使用之前不用先释放。
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define vSemaphoreCreateBinary( xSemaphore ) \
{ \
( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE ); \
if( ( xSemaphore ) != NULL ) \
{ \
( void ) xSemaphoreGive( ( xSemaphore ) ); \
} \
}
#endif不管是常用的还是老式的创建二值信号量函数,都是在xQueueGenericCreate()下扩展的宏。
#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 )
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ( ( uint8_t ) 0U )
QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType );uxQueueLength为1时,表示创建的队列长度为1。
uxItemSize为0时,表示创建的消息空间(队列项)大小为0。
因为这个所谓的“消息队列”其实并不是用于存储消息的,而是被用作二值信号量,因为我们根本无需关注消息内容是什么,只要知道有没有信号量就行了。
ucQueueType 表示的是创建消息队列的类型。
创建二值信号量实例
SemaphoreHandle_t xSemaphore = NULL;
void vATask( void * pvParameters )
{
/* 尝试创建一个信号量 */
xSemaphore = xSemaphoreCreateBinary();
if ( xSemaphore == NULL )
{
/* 内存不足,创建失败 */
} else
{
/* 信号量现在可以使用,句柄存在变量 xSemaphore 中。这个时候还不能调用函数 xSemaphoreTake()来获取信号量。
因为使用 xSemaphoreCreateBinary()函数创建的信号量是空的,在第一次获取之前必须先调用函数 xSemaphoreGive()先提交*/
}
}创建计数信号量
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endifuxMaxCount为计数信号量的最大值,当达到这个值时,信号量不能再被释放。
uxInitialCount为创建计数信号量的初始值。
xSemaphoreCreateCounting()函数是在xQueueCreateCountingSemaphore()下扩展的宏。
xQueueCreateCountingSemaphore()函数如下。
#if( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
traceCREATE_COUNTING_SEMAPHORE();
}
else
{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
return xHandle;
}
#endif /* ( ( configUSE_COUNTING_SEMAPHORES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) ) */如果创建成功,会将消息队列控制块中的 uxMessagesWaiting 成员变量赋值为用户指定的初始可用信号量个数 uxInitialCount,
如果这个值大于 0,则表示此时有 uxInitialCount 个计数信号量是可用的,这点与二值信号量的创建不一样,二值信号量在创建成功的时候是无效的。
创建计数信号量实例
void vATask( void * pvParameters )
{
SemaphoreHandle_t xSemaphore;
/* 创建一个计数信号量, 用于事件计数 */
xSemaphore = xSemaphoreCreateCounting( 5, 5 );
if ( xSemaphore != NULL )
{
/* 计数信号量创建成功 */
}
}删除信号量
vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。 如果有任务阻塞在该信号量上,那么不要删除该信号量。
删除信号量过程其实就是删除消息队列过程, 因为信号量其实就是消息队列, 只不过是无法存储消息的队列而已。
#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )信号量释放函数
与消息队列的操作一样,信号量的释放可以在任务、中断中使用, 所以需要有不一样的 API 函数在不一样的上下文环境中调用。
xSemaphoreGive()是一个用于释放信号量的宏,不用于中断保护,真正的实现过程是调用消息队列通用发送函数。 该函数不能在中断中使用。
释放的信号量对象必须是已经被创建的, 可以用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量。
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )释放信号量实际上是一次入队操作,并且是不允许入队阻塞,因为阻塞时间为 semGIVE_BLOCK_TIME,该宏的值为 0。
如果信号量未满, 控制块结构体成员 uxMessageWaiting 就会加 1, 然后判断是否有阻塞的任务。
如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS);如果信号量已满, 则返回错误代码(err_QUEUE_FULL)。
xSemaphoreGive()函数实例
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON ) /* K1 被按下 */
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if ( xReturn == pdTRUE )
printf("BinarySem_Handle 二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle 二值信号量释放失败!\r\n");
}
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON ) /* K2 被按下 */
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if ( xReturn == pdTRUE )
printf("BinarySem_Handle 二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle 二值信号量释放失败!\r\n");
}
vTaskDelay(20);
}
}xSemaphoreGiveFromISR()是xSemaphoreGive()的中断保护版本。被释放的信号量可以是二进制信号量和计数信号量。
和普通版本的释放信号量 API 函数有些许不同,它不能释放互斥量,这是因为互斥量不可以在中断中使用, 互斥量的优先级继承机制只能在任务中起作用,而在中断中毫无意义。
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )如果可用信号量未满, 控制块结构体成员 uxMessageWaiting 就会加 1,然后判断是否有阻塞的任务。
如果有的话就会恢复阻塞的任务,然后返回成功信息(pdPASS),如果恢复的任务优先级比当前任务优先级高,那么在退出中断要进行任务切换一次;
如果信号量满,则返回错误代码(err_QUEUE_FULL),表示信号量满。
一个或者多个任务有可能阻塞在同一个信号量上,调用函数 xSemaphoreGiveFromISR()可能会唤醒阻塞在该信号量上的任务,
如果被唤醒的任务的优先级大于当前任务的优先级,那么形参 pxHigherPriorityTaskWoken 就会被设置为 pdTRUE, 然后在中断退出前执行一次上下文切换。
从 FreeRTOS V7.3.0 版本开始, pxHigherPriorityTaskWoken 是一个可选的参数,可以设置为 NULL。
xSemaphoreGiveFromISR()函数实例
void vTestISR( void )
{
BaseType_t pxHigherPriorityTaskWoken;
uint32_t ulReturn;
/* 进入临界段,临界段可以嵌套 */
ulReturn = taskENTER_CRITICAL_FROM_ISR();
/* 判断是否产生中断 */
{
/* 如果产生中断,清除中断标志位 */
//释放二值信号量,发送接收到新数据标志,供前台程序查询
xSemaphoreGiveFromISR(BinarySem_Handle,&pxHigherPriorityTaskWoken);
//如果需要的话进行一次任务切换,系统会判断是否需要进行切换
portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
}
/* 退出临界段 */
taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}信号量获取函数
与消息队列的操作一样,信号量的获取可以在任务、中断(中断中使用并不常见)中使用,所以需要有不一样的 API 函数在不一样的上下文环境中调用。
当信号量有效的时候, 任务才能获取信号量,当任务获取了某个信号量的时候,该信号量的可用个数就减一,当它减到 0 的时候, 任务就无法再获取了,
并且获取的任务会进入阻塞态(假如用户指定了阻塞超时时间的话)。 如果某个信号量中当前拥有 1 个可用的信号量的话,被获取一次就变得无效了,
那么此时另外一个任务获取该信号量的时候,就会无法获取成功,该任务便会进入阻塞态,阻塞时间由用户指定。
xSemaphoreTake()函数用于获取信号量,不带中断保护。获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。
xBlockTime为等待信号量可用的最大超时时间,单位为 tick(即系统节拍周期)。获取成功则返回 pdTRUE , 在指定的超时时间中没有获取成功则返回errQUEUE_EMPTY。
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueSemaphoreTake( ( xSemaphore ), ( xBlockTime ) )释放信号量实际上是一次消息出队操作, 阻塞时间由用户指定xBlockTime,当有任务试图获取信号量的时候,当且仅当信号量有效的时候,任务才能读获取到信号量。
如果信号量无效,在用户指定的阻塞超时时间中,该任务将保持阻塞状态以等待信号量有效。当其它任务或中断释放了有效的信号量,该任务将自动由阻塞态转移为就绪态。
当任务等待的时间超过了指定的阻塞时间,即使信号量中还是没有可用信号量,任务也会自动从阻塞态转移为就绪态。
如果有可用信号量, 控制块结构体成员 uxMessageWaiting 就会减 1,然后返回获取成功信息(pdPASS);如果信号量无效并且阻塞时间为 0, 则返回错误代(errQUEUE_EMPTY);
如果信号量无效并且用户指定了阻塞时间, 则任务会因为等待信号量而进入阻塞状态,任务会被挂接到延时列表中。
xSemaphoreTake()函数实例
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY);
if (pdTRUE == xReturn)
printf("BinarySem_Handle 二值信号量获取成功!\n\n");
LED1_TOGGLE;
}
}xSemaphoreTakeFromISR()是 xSemaphoreTake()的中断版本,用于获取信号量,是一个不带阻塞机制获取信号量的函数,获取对象必须是已经创建的信号量,
信号量类型可以是二值信号量和计数信号量,它与 xSemaphoreTake()函数不同,它不能用于获取互斥量,因为互斥量不可以在中断中使用,并且互斥量特有的优先级继承机制只能在任务中起作用, 而在中断中毫无意义。
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )一个或者多个任务有可能阻塞在同一个信号量上,调用函数 xSemaphoreTakeFromISR()会唤醒阻塞在该信号量上优先级最高的信号量入队任务。
如果被唤醒的任务的优先级大于或者等于被中断的任务的优先级,那么形参pxHigherPriorityTaskWoken 就会被设置为 pdTRUE, 然后在中断退出前执行一次上下文切换,中断退出后则直接返回刚刚被唤醒的高优先级的任务。
从 FreeRTOS V7.3.0版本开始,pxHigherPriorityTaskWoken 是一个可选的参数,可以设置为 NULL。
信号量实验
二值信号量同步实验
信号量同步实验是在 FreeRTOS 中创建了两个任务,一个是获取信号量任务,一个是释放互斥量任务,两个任务独立运行。
获取信号量任务是一直在等待信号量,其等待时间是 portMAX_DELAY,等到获取到信号量之后, 任务开始执行任务代码,如此反复等待另外任务释放的信号量。
释放信号量任务在检测按键是否按下,如果按下则释放信号量,此时释放信号量会唤醒获取任务,获取任务开始运行,然后形成两个任务间的同步,
因为如果没按下按键,那么信号量就不会释放,只有当信号量释放的时候,获取信号量的任务才会被唤醒,如此一来就达到任务与任务的同步, 同时程序的运行会在串口打印出相关信息。
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t Receive_Task_Handle = NULL; /* LED 任务句柄 */
static TaskHandle_t Send_Task_Handle = NULL; /* KEY 任务句柄 */
SemaphoreHandle_t BinarySem_Handle =NULL;
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("按下 KEY1 或者 KEY2 进行任务与任务间的同步\n");
/* 创建 AppTaskCreate 任务 */
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t*)&AppTaskCreate_Handle); /* 任务控制块指针 */
/* 启动任务调度 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建二值信号量 */
BinarySem_Handle = xSemaphoreCreateBinary();
if (NULL != BinarySem_Handle)
printf("BinarySem_Handle 二值信号量创建成功!\r\n");
/* 创建 Receive_Task 任务 */
xReturn = xTaskCreate( (TaskFunction_t )Receive_Task, /* 任务入口函数 */
(const char* )"Receive_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&Receive_Task_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Receive_Task 任务成功!\r\n");
/* 创建 Send_Task 任务 */
xReturn = xTaskCreate( (TaskFunction_t )Send_Task, /* 任务入口函数 */
(const char* )"Send_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&Send_Task_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Send_Task 任务成功!\n\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void Receive_Task(void* parameter)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,portMAX_DELAY);
if (pdTRUE == xReturn)
printf("BinarySem_Handle 二值信号量获取成功!\n\n");
LED1_TOGGLE;
}
}
static void Send_Task(void* parameter)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
while (1)
{
/* KEY1 被按下 */
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
{
xReturn = xSemaphoreGive( BinarySem_Handle ); //给出二值信号量
if ( xReturn == pdTRUE )
printf("BinarySem_Handle 二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle 二值信号量释放失败!\r\n");
}
/* KEY2 被按下 */
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if ( xReturn == pdTRUE )
printf("BinarySem_Handle 二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle 二值信号量释放失败!\r\n");
}
vTaskDelay(20);
}
}计数信号量同步实验
模拟停车场工作运行。在创建信号量的时候初始化 5 个可用的信号量,并且创建了两个任务:一个是获取信号量任务,一个是释放信号量任务,两个任务0独立运行。
获取信号量任务是通过按下 KEY1 按键进行信号量的获取,模拟停车场停车操作,其等待时间是 0,在串口调试助手输出相应信息。
释放信号量任务是通过按下 KEY2 按键进行信号量的释放,模拟停车场取车操作,在串口调试助手输出相应信息。
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL; /* Take_Task 任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL; /* Give_Task 任务句柄 */
SemaphoreHandle_t CountSem_Handle =NULL;
int main(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
BSP_Init();
printf("车位默认值为 5 个,按下 KEY1 申请车位,按下 KEY2 释放车位! \n\n");
/* 创建 AppTaskCreate 任务 */
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t*)&AppTaskCreate_Handle); /* 任务控制块指针 */
/* 启动任务调度 */
if (pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
while (1); /* 正常不会执行到这里 */
}
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS; /* 定义一个创建信息返回值,默认为 pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建 CountSem */
CountSem_Handle = xSemaphoreCreateCounting(5,5);
if (NULL != CountSem_Handle)
printf("CountSem_Handle 计数信号量创建成功!\r\n");
/* 创建 Take_Task 任务 */
xReturn = xTaskCreate( (TaskFunction_t )Take_Task, /* 任务入口函数 */
(const char* )"Take_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&Take_Task_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Take_Task 任务成功!\r\n");
/* 创建 Give_Task 任务 */
xReturn = xTaskCreate( (TaskFunction_t )Give_Task, /* 任务入口函数 */
(const char* )"Give_Task", /* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&Give_Task_Handle); /* 任务控制块指针 */
if (pdPASS == xReturn)
printf("创建 Give_Task 任务成功!\n\n");
vTaskDelete(AppTaskCreate_Handle); //删除 AppTaskCreate 任务
taskEXIT_CRITICAL(); //退出临界区
}
static void Take_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
/* 任务都是一个无限循环,不能返回 */
while (1)
{
//如果 KEY1 被按下
if ( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON )
{
/* 获取一个计数信号量 */
xReturn = xSemaphoreTake(CountSem_Handle, 0); //等待时间为0
if ( pdTRUE == xReturn )
printf( "KEY1 被按下, 成功申请到停车位。 \n" );
else
printf( "KEY1 被按下, 不好意思,现在停车场已满! \n" );
}
vTaskDelay(20); //每 20ms 扫描一次
}
}
static void Give_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为 pdPASS */
/* 任务都是一个无限循环,不能返回 */
while (1)
{
//如果 KEY2 被按下
if ( Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON )
{
/* 获取一个计数信号量 */
xReturn = xSemaphoreGive(CountSem_Handle);//给出计数信号量
if ( pdTRUE == xReturn )
printf( "KEY2 被按下, 释放 1 个停车位。 \n" );
else
printf( "KEY2 被按下, 但已无车位可以释放! \n" );
}
vTaskDelay(20); //每 20ms 扫描一次
}
}至此,信号量内容就已经结束,多数函数都已经被官方封装好,我们直接用就行。
xSemaphoreCreateBinary() 创建二值信号量
vSemaphoreCreateBinary( xSemaphore ) 创建二值信号量
xSemaphoreCreateCounting() 创建计数信号量
vSemaphoreDelete( xSemaphore ) 删除信号量,包括二值信号量,计数信号量,互斥量和递归互斥量
xSemaphoreGive( xSemaphore ) 释放信号量,可用于二值信号量、计数信号量、互斥量的释放,但不能释放由函数xSemaphoreCreateRecursiveMutex()创建的递归互斥量
xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xSemaphoreGive()函数的中断保护版本,释放信号量,被释放的信号量可以是二进制信号量和计数信号量。
xSemaphoreTake( xSemaphore, xBlockTime ) 获取信号量,获取的信号量对象可以是二值信号量、计数信号量和互斥量,但是递归互斥量并不能使用这个 API 函数获取。
xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xSemaphoreTake()函数的中断保护版本,获取信号量,信号量类型可以是二值信号量和计数信号量。
边栏推荐
- 服务器如何搭建虚拟主机?
- 复盘:推荐系统—— 负采样策略
- How to use yolov5 as an intelligent transportation system for red light running monitoring (1)
- Security document archiving software
- 牛客/洛谷——[NOIP2003 普及组]栈
- Find the intermediate node of the linked list
- SHIB(柴犬币)一月涨幅数百倍,百倍币需具备哪些核心要素?2021-05-09
- Leetcode question brushing series -- 931. Minimum sum of descent path
- 二叉树——111. 二叉树的最小深度
- Leetcode107-二叉树的层序遍历II详解
猜你喜欢

Binary tree 101. Symmetric binary tree

Compile live555 with vs2019 in win10

二叉树——101. 对称二叉树

C语言实战之猜拳游戏

【一库】mapbox-gl!一款开箱即用的地图引擎

老旧笔记本电脑变服务器(笔记本电脑+内网穿透)

Binary tree - 110. Balanced binary tree

Js理解之路:Object.call与Object.create()实现继承的原理

07_ue4进阶_发射火球扣mp值和攻击扣血机制

J9 number theory: what is Dao mode? Obstacles to the development of Dao
随机推荐
Vscode format JSON file
What is inode? It will help you understand inode and what help inode provides when creating and deleting files.
Binary tree -- 257. All paths of binary tree
Leetcode question brushing series -- 931. Minimum sum of descent path
C language (high level) program environment and preprocessing
Observer model of behavioral model
Compile live555 with vs2019 in win10
Unity -- Euler angle, quaternion
Typescript TS basic knowledge and so on
Song of statistics lyrics
QT smart pointer error prone point
Binary tree - 404. Sum of left leaves
Stack and queue - 239. Sliding window maximum
Getting started with Servlet
Binary tree - 530. Minimum absolute difference of binary search tree
二叉树——257. 二叉树的所有路径
SHIB(柴犬币)一月涨幅数百倍,百倍币需具备哪些核心要素?2021-05-09
二叉树相关知识
YoloV4-tiny网络结构
Js理解之路:什么是原型链