目录
1)创建二值信号量 xSemaphoreCreateBinary()编辑
2)创建计数信号量 xSemaphoreCreateCounting()
2)2. xSemaphoreGiveFromISR()(中断保护版本)
1)xSemaphoreTake( xSemaphore, xBlockTime )
1)创建互斥量xSemaphoreCreateMutex()
2)互斥量获取xSemaphoreTake(xSemaphore, xBlockTime)
4)递归互斥量获取函数 xSemaphoreTakeRecursive()
5)递归互斥量释放函数 xSemaphoreGiveRecursive()
1、信号量简介
、信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资
源的互斥访问, 常用于协助一组相互竞争的任务来访问临界资源。在多任务系统中,各任
务之间需要同步或互斥实现临界资源的保护,信号量功能可以为用户提供这方面的支持。
抽象的来讲,信号量是一个非负整数,所有获取它的任务都会将该整数减一(获取它
当然是为了使用资源),当该整数值为零时,所有试图获取它的任务都将处于阻塞状态。
通常一个信号量的计数值用于对应有效的资源数,表示剩下的可被占用的互斥资源数。
freeRTOS信号量的实现是借助消息队列实现的,即复用了freeRTOS消息队列的一系列函数。
2、信号量定义
信号量控制块与消息队列的控制块完全相同。常用的信号量有计数信号量和二值信号量。
3、信号量创建
1)创建二值信号量 xSemaphoreCreateBinary()
![](https://img-blog.csdnimg.cn/299f45e563ef4dab9bdc09d2782570c2.png)
其实就是创建了一个长度为1的消息队列,并把消息大小设为0,即存储消息的空间为0,只有控制块。即第二个参数宏为0。
案例:
2)创建计数信号量 xSemaphoreCreateCounting()
实现如下:最终调用的是创建消息队列的函数xQueueGenericCreate(),只不过将队列长度作为信号量最大值,并把初始信号量值初始的消息个数(uxMessagesWaiting),作为信号量使用的消息队列的消息元素大小的参数依然指定为0(宏queueSEMAPHORE_QUEUE_ITEM_LENGTH)
创建完成的信号量结构如下图:
使用示例:
3)互斥量的创建
4、信号量删除函数 vSemaphoreDelete()
实质上就是删除一个消息队列。
5、信号量释放函数
1).xSemaphoreGive()(在任务中使用)
实现如下:实质是调用的消息发送到消息队列的函数xQueueGenericSend(),其中因为只是使用了消息队列的uxMessageWaiting成员,并没有真的发送消息,所以消息的指针设为NULL,并且由于作为信号量的消息队列消息元素大小为0,所以函数不会真的将消息拷贝到消息队列中,仅仅只是将uxMessageWaiting的值加1。这样可以利用消息队列的结构实现信号量为0和到达最大值时的阻塞机制,因为与消息队列的阻塞机制相同。
2)2. xSemaphoreGiveFromISR()(中断保护版本)
调用xQueueGiveFromISR,内部实现与xQueueGenericSendFromISR一致,只是没有了延迟阻塞机制。
调用函数 xSemaphoreGiveFromISR()
可能会唤醒阻塞在该信号量上的任务,如果被唤醒的任务的优先级大于当前任务的优先级,
那么形参 pxHigherPriorityTaskWoken 就会被设置为 pdTRUE, 然后在中断退出前执行一次
上下文切换。 从 FreeRTOS V7.3.0 版本开始, pxHigherPriorityTaskWoken 是一个可选的参
数,可以设置为 NULL。
6、信号量获取函数
1)xSemaphoreTake( xSemaphore, xBlockTime )
本质是调用读取消息队列的函数xQueueGenericReceive(),只是第二个参数接收消息的内存为NULL。可以设定阻塞时间xBlockTime(与释放信号量不同,调用 xSemaphoreGive()释放信号量阻塞时间只能为0)。
信号量使用示例:
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/**************************** 任务句柄 ********************************/
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t Take_Task_Handle = NULL;/* Take_Task任务句柄 */
static TaskHandle_t Give_Task_Handle = NULL;/* Give_Task任务句柄 */
static void AppTaskCreate(void);/* 用于创建任务 */
static void Take_Task(void* pvParameters);/* Take_Task任务实现 */
static void Give_Task(void* pvParameters);/* Give_Task任务实现 */
static void BSP_Init(void);/* 用于初始化板载相关资源 */
/*****************************************************************
* @brief 主函数
* @param 无
* @retval 无
* @note 第一步:开发板硬件初始化
第二步:创建APP应用任务
第三步:启动FreeRTOS,开始多任务调度
****************************************************************/
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
/* 开发板硬件初始化 */
BSP_Init();
printf("这是一个[野火]-STM32全系列开发板-FreeRTOS计数信号量实验!\n");
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); /* 正常不会执行到这里 */
}
/***********************************************************************
* @ 函数名 : AppTaskCreate
* @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
* @ 参数 : 无
* @ 返回值 : 无
**********************************************************************/
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建Test_Queue */
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(); //退出临界区
}
/**********************************************************************
* @ 函数名 : Take_Task
* @ 功能说明: Take_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
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扫描一次
}
}
/**********************************************************************
* @ 函数名 : Give_Task
* @ 功能说明: Give_Task任务主体
* @ 参数 :
* @ 返回值 : 无
********************************************************************/
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扫描一次
}
}
互斥量
互斥量本质就是一个二值信号量,也就是一个长度为1的消息列表,但是消息列表作为互斥量的时候其中两个成员head和tail会被用作pxMutexHolder和uxQueueType。pxMutexHolder指向的是占有这个互斥量的任务,用于实现优先级继承,这也是互斥量和二值信号量的区别,uxQueueType的值为queueQUEUE_IS_MUTEX。
1)创建互斥量xSemaphoreCreateMutex()
最终还是调用xQueuegenericcreate()创建了一个长度为1,元素大小为0的消息队列,并将pxMutexHolder(即原head)初始化为NULL,由于互斥量无任务占有时为1,内部调用了一次xQueueGenericSend()
2)互斥量获取xSemaphoreTake(xSemaphore, xBlockTime)
本质就是调用了xQueueGenericReceive()函数和读取消息列表一样,只不过不会真的拷贝消息,只是操作了消息列表的xMessageWaiting成员变量和借用了消息列表的阻塞机制。作为互斥量的时候,如果获取成功,则将互斥量的pxMutexHolder指向获取该互斥量的任务,同时任务的互斥量持有数加一。源码实现如下图:
重点:若互斥量获取失败,则除了和消息列表一样的阻塞机制,还多了一步优先级继承,即将互斥量持有任务的优先级暂时提升至互斥量申请者的优先级,当然若本来就比申请者高则不用提升。源码实现如下:
在代码(2)处,判断了一下EventlistItem的值最高位是否为1,不为一说明任务没有在等事件,那么就要根据提升后的优先级来调整EventlistItem的值。如果在等事件则不用调,因为EventlistItem的值被暂时用于事件等待列表作为判断标志了。
3)互斥量释放函数 xSemaphoreGive()
实质是调用xQueueGenericSend(),不同的是作为互斥量释放时,需要恢复原持有者的优先级(互斥量持有者的优先级可能在持有期间被提升过)源码实现如下:
4)递归互斥量获取函数 xSemaphoreTakeRecursive()
本质最终调用的是xQueueGerenicReceive(),只是在调用前进行了判断,若获取者为占有者本身,则只对成员uxRecursiveCallCount加1,不实际调用获取函数。
5)递归互斥量释放函数 xSemaphoreGiveRecursive()
本质最终调用的是xQueueGerenicReceive(),只是在调用前进行了判断,若释放者不为占有者本身,则返回pdFAiL,不实际调用xQueueGerenicReceive(),否则对成员uxRecursiveCallCount减1,直到减至0才实际调用xQueueGerenicReceive()进行释放。