本次分享的内容是FreeRtos中信号量相关的知识点,会从将用的API函数进行介绍,到二值信号量,计数型信号量、翻转信号量、以及互斥信号进行实验演示。
信号量常常用于控制对共享资源的访问和任务同步。举一个很常见的例子,某个停车场有100 个停车位,这 100 个停车位大家都可以用,对于大家来说这 100 个停车位就是共享资源。
假设现在这个停车场正常运行,你要把车停到这个这个停车场肯定要先看一下现在停了多少车了?还有没有停车位?当前停车数量就是一个信号量,具体的停车数量就是这个信号量值,当这个值到 100 的时候说明停车场满了。停车场满的时你可以等一会看看有没有其他的车开出停车场,当有车开出停车场的时候停车数量就会减一,也就是说信号量减一,此时你就可以把车停进去了,你把车停进去以后停车数量就会加一,也就是信号量加一。这就是一个典型的使用信号量进行共享资源管理的案例,在这个案例中使用的就是计数型信号量。再看另外一个案例:
使用公共电话,我们知道一次只能一个人使用电话,这个时候公共电话就只可能有两个状态:
使用或未使用,如果用电话的这两个状态作为信号量的话,那么这个就是二值信号量。
信号量的另一个重要的应用场合就是任务
同步,用于任务与任务或中断与任务之间的同步。在执行中断服务函数的时候可以通过向任务发送信号量来通知任务它所期待的事件发生了,当退出中断服务函数以后在任务调度器的调度下同步的任务就会执行。在编写中断服务函数的时候我们都知道一定要快进快出,中断服务函数里面不能放太多的代码,否则的话会影响的中断的实时性。裸机编写中断服务函数的时候一般都只是在中断服务函数中打个标记,然后在其他的地方根据标记来做具体的处理过程。在使用 RTOS 系统的时候我们就可以借助信号量完成此功能,当中断发生的时候就释放信号量,中断服务函数不做具体的处理。具体的处理过程做成一个任务,这个任务会获取信号量,如果获取到信号量就说明中断发生了,那么就开始完成相应的处理,这样做的好处就是中断执行时间非常短。这个例子就是中断与任务之间使用信号量来完成同步,当然了,任务与任务之间也可以使用信号量来完成同步。
二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级继承。因此二值信号另更适合用于同步(任务与任务或任务与中断的同步),而互斥信号量适合用于简单的互斥访问。
和队列一样,信号量 API 函数允许设置一个阻塞时间,阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。如果多个任务同时阻塞在同一一个信号量上的话那么优先级最高的哪个任务优先获得信号量,这样当信号量有效的时候高优先级的任务就会解除阻塞状态。
二值信号量其实就是一个只有一个队列项的队列
同队列一样,要想使用二值信号量就必须先创建二值信号量
信号量相关的API函数在semphr.h 中有定义
1、函数 vSemaphoreCreateBinary ()
此函数是老版本 FreeRTOS 中的创建二值信号量函数,新版本已经不再使用了,新版本的FreeRTOS 使用 xSemaphoreCreateBinary()来替代此函数,这里还保留这个函数是为了兼容那些基于老版本 FreeRTOS 而做的应用层代码。此函数是个宏, 具体创建过程是由函数xQueueGenericCreate()来完成的。
void vSemaphoreCreateBinary( SemaphoreHandle_t xSemaphore )
参数:
xSemaphore:保存创建成功的二值信号量句柄。
返回值:
NULL: 二值信号量创建失败。
其他值: 二值信号量创建成功。
2、函数 xSemaphoreCreateBinary()
此函数是 vSemaphoreCreateBinary()的新版本,新版本的 FreeRTOS 中统一用此函数来创建二值信号量。使用此函数创建二值信号量的话信号量所需要的 RAM 是由 FreeRTOS 的内存管理部分来动态分配的。此函数创建好的二值信号量默认是空的,也就是说刚创建好的二值信号量使用函数 xSemaphoreTake()是获取不到的,此函数也是个宏,具体创建过程是由函数xQueueGenericCreate()来完成的。
SemaphoreHandle_t xSemaphoreCreateBinary( void )
参数:无
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量的句柄。
3、函数 xSemaphoreCreateBinaryStatic()
一般情况下不使用这个函数一般是用动态创建
此函数也是创建二值信号量的,只不过使用此函数创建二值信号量的话信号量所需要的RAM 需要由用户来分配,此函数是个宏,具体创建过程是通过函数 xQueueGenericCreateStatic()来完成的。
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer )
参数:
pxSemaphoreBuffer:此参数指向一个 StaticSemaphore_t 类型的变量,用来保存信号量结构体。
返回值:
NULL: 二值信号量创建失败。
其他值: 创建成功的二值信号量句柄。
同队列一样,释放信号量也分为任务级和中断级。还有!不管是二值信号量、计数型信号量还是互斥信号量都是由上述两个函数进行信号的释放,递归互斥信号量有专门的释放函数。
1、函数 xSemaphoreGive()
此函数用于释放二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正释放信号量的过程是由函数 xQueueGenericSend()来完成的。
BaseType_t xSemaphoreGive( xSemaphore )
参数:
xSemaphore:要释放的信号量句柄。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败。
2、函数 xSemaphoreGiveFromISR()
此函数用于在中断中释放信号量,此函数只能用来释放二值信号量和计数型信号量,绝对不能用来在中断服务函数中释放互斥信号量!此函数是一个宏,真正执行的是函数xQueueGiveFromISR()。
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
参数:
xSemaphore: 要释放的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 释放信号量成功。
errQUEUE_FULL: 释放信号量失败
同释放信号量的 API 函数一样,不管是二值信号量、计数型信号量还是互斥信号量,它们都是使用如下这两个函数。
1、函数 xSemaphoreTake()
此函数用于获取二值信号量、计数型信号量或互斥信号量,此函数是一个宏,真正获取信号量的过程是由函数 xQueueGenericReceive ()来完成的。
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore, TickType_t xBlockTime)
xSemaphore:要获取的信号量句柄。
xBlockTime: 阻塞时间。
返回值:
pdTRUE: 获取信号量成功。
pdFALSE: 超时,获取信号量失败。
2、函数 xSemaphoreTakeFromISR ()
此函数用于在中断服务函数中获取信号量,此函数用于获取二值信号量和计数型信号量,绝 对 不 能 使 用 此 函 数 来 获 取 互 斥 信 号 量 ! 此 函 数 是 一 个 宏 , 真 正 执 行 的 是 函 数xQueueReceiveFromISR ()。
BaseType_t xSemaphoreTakeFromISR(SemaphoreHandle_t xSemaphore,
BaseType_t * pxHigherPriorityTaskWoken)
参数:
xSemaphore: 要获取的信号量句柄。
pxHigherPriorityTaskWoken: 标记退出此函数以后是否进行任务切换,这个变量的值由这三个函数来设置的,用户不用进行设置,用户只需要提供一个变量来保存这个值就行了。当此值为 pdTRUE 的时候在退出中断服务函数之前一定要进行一次任务切换。
返回值:
pdPASS: 获取信号量成功。
pdFALSE: 获取信号量失败
注意:
在中断中获取信号量真正使用的是函数 xQueueReceiveFromISR (),这个函数就是中断级出队函数!当队列不为空的时候就拷贝队列中的数据(用于信号量的时候不需要这一步),然后将队列结构体中的成员变量 uxMessagesWaiting 减一,如果有任务因为入队而阻塞的话就解除阻塞态,当解除阻塞的任务拥有更高优先级的话就将参数 pxHigherPriorityTaskWoken 设置为pdTRUE,最后返回 pdPASS 表示出队成功。如果队列为空的话就直接返回 pdFAIL 表示出队失败!
实验内容为分别用按键进行信号量的获取和串口中断接收后再响应。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED0_TASK_PRIO 4
//任务堆栈大小
#define LED0_STK_SIZE 50
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//按键任务
#define DATAPROCESS_TASK_PRIO 2
//任务堆栈大小
#define DATAPROCESS_STK_SIZE 50
//任务句柄
TaskHandle_t DATAPROCESSTask_Handler;
//任务函数
void dataprocess_task(void *pvParameters);
//定义二值信号量句柄
SemaphoreHandle_t BinarySemaphore;
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_GPIO_INIT(); //按键初始化
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建信号量
BinarySemaphore = xSemaphoreCreateBinary();
if(NULL == BinarySemaphore)
{
//创建失败
printf("BinarySemaphore failed\r\n");
}
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
//创建按键任务
xTaskCreate((TaskFunction_t )dataprocess_task,
(const char* )"dataprocess_task",
(uint16_t )DATAPROCESS_STK_SIZE,
(void* )NULL,
(UBaseType_t )DATAPROCESS_TASK_PRIO,
(TaskHandle_t* )&DATAPROCESSTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0任务函数
void led0_task(void *pvParameters)
{
u8 key;
while(1)
{
key = KEY_Scan();
if(key == KEY0_Value) //PB9
{
if(BinarySemaphore != NULL)
{
//发送信号量
xSemaphoreGive(BinarySemaphore);
}
}
LED0=~LED0;
vTaskDelay(10);
//printf("led1 is running\r\n");
}
}
//按键任务函数
void dataprocess_task(void *pvParameters)
{
// u8 count = 0;
// while(1)
// {
// count++;
// if(BinarySemaphore != NULL)
// {
// xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量 死等
// printf("dataprocess_task run time = %d\r\n",count);
// }
// else
// {
// vTaskDelay(10); //延时10Ms 也就是10 个节拍
// }
//
// }
while(1)
{
if(BinarySemaphore != NULL)
{
xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量 死等
printf("dataprocess_task data = %s\r\n",USART_RX_BUF);
memset(USART_RX_BUF,0,USART_REC_LEN);
USART_RX_STA = 0;
}
else
{
vTaskDelay(20);
}
}
}
实验结果
实验结果与实际有点不相符合,不知道为什么每次串口接收到数据后会打印两次。
计数型信号量叫做数值信号量,二值信号量相当于长度为 1 的队列,那么计数型信号量就是长度大于 1 的队列。同二值信号量一样,用户不需要关心队列中存储了什么数据,只需要关心队列是否为空即可。
1、事件计数
在这个场合中,每次事件发生的时候就在事件处理函数中释放信号量(增加信号量的计数值),其他任务会获取信号量(信号量计数值减一,信号量值就是队列结构体成员变量
uxMessagesWaiting)来处理事件。在这种场合中创建的计数型信号量初始计数为 0。
2、资源管理
在这个场合中,信号量值代表当前资源的可用数量,比如停车场当前剩余的停车位数量。一个任务要想获得资源的使用权,首先必须获取信号量,信号量获取成功以后信号量值就会减一。当信号量值为 0 的时候说明没有资源了。当一个任务使用完资源以后一定要释放信号量,释放信号量以后信号量值会加一。在这个场合中创建的计数型信号量初始值应该是资源的数量,比如停车场一共有 100 个停车位,那么创建信号量的时候信号量值就应该初始化为 100
1、函数 xSemaphoreCreateCounting()
此函数用于创建一个计数型信号量,所需要的内存通过动态内存管理方法分配,
真正完成信号量创建的是函数 xQueueCreateCountingSemaphore()
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount )
参数:
uxMaxCount: 计数信号量最大计数值,当信号量值等于此值的时候释放信号量就会失败。
uxInitialCount: 计数信号量初始值。
返回值:
NULL: 计数型信号量创建失败。
其他值: 计数型信号量创建成功,返回计数型信号量句柄。
静态创建在这里不做介绍,一般都是使用的动态创建
实验结果因为按键每次只按一次,进行加一后内部函数机制又会自减所以一直是0
按键按快了就会看见值有明显的变化。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED0_TASK_PRIO 4
//任务堆栈大小
#define LED0_STK_SIZE 50
//任务句柄
TaskHandle_t LED0Task_Handler;
//任务函数
void led0_task(void *pvParameters);
//按键任务
#define DATAPROCESS_TASK_PRIO 2
//任务堆栈大小
#define DATAPROCESS_STK_SIZE 50
//任务句柄
TaskHandle_t DATAPROCESSTask_Handler;
//任务函数
void dataprocess_task(void *pvParameters);
//定义二值信号量句柄
SemaphoreHandle_t CountSemaphore; //计数值信号量句柄
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_GPIO_INIT(); //按键初始化
printf("------------------计数型信号量测试-----------------\r\n");
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建信号量
CountSemaphore = xQueueCreateCountingSemaphore(255,0);
if(NULL == CountSemaphore)
{
//创建失败
printf("BinarySemaphore failed\r\n");
}
//创建LED0任务
xTaskCreate((TaskFunction_t )led0_task,
(const char* )"led0_task",
(uint16_t )LED0_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED0_TASK_PRIO,
(TaskHandle_t* )&LED0Task_Handler);
//创建按键任务
xTaskCreate((TaskFunction_t )dataprocess_task,
(const char* )"dataprocess_task",
(uint16_t )DATAPROCESS_STK_SIZE,
(void* )NULL,
(UBaseType_t )DATAPROCESS_TASK_PRIO,
(TaskHandle_t* )&DATAPROCESSTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0任务函数
void led0_task(void *pvParameters)
{
u8 key;
while(1)
{
key = KEY_Scan();
if(key == KEY0_Value) //PB9
{
if(CountSemaphore != NULL)
{
//发送信号量
xSemaphoreGive(CountSemaphore);
}
}
LED0=~LED0;
vTaskDelay(10);
//printf("led1 is running\r\n");
}
}
//按键任务函数
void dataprocess_task(void *pvParameters)
{
UBaseType_t semvalue = 0;
while(1)
{
if(CountSemaphore != NULL)
{
xSemaphoreTake(CountSemaphore,portMAX_DELAY); //获取信号量 死等
semvalue = uxSemaphoreGetCount(CountSemaphore);
printf("semvalue = %d\r\n",(int)semvalue);
}
else
{
vTaskDelay(20);
}
}
}
在使用二值信号量的时候会遇到很常见的一个问题——优先级翻转,优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果。
如下图所示:
过程详解
(1) 任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。
(2) 某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。
(3) 任务 L 获得信号量并开始使用该共享资源。
(4) 由于任务 H 优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。
(5) 任务 H 开始运行。
(6) 任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务
L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。
(7) 任务 L 继续运行。
(8) 由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务
L 的 CPU 使用权。
(9) 任务 M 处理该处理的事。
(10) 任务 M 执行完毕后,将 CPU 使用权归还给任务 L。
(11) 任务 L 继续运行。
(12) 最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有个高
优先级的任务在等待这个信号量,故内核做任务切换。
(13) 任务 H 得到该信号量并接着运行。
在这种情况下,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直
等待直到任务 L 释放其占用的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于任务 H,导致优先级翻转。
start_task:用来创建其他 3 个任务。
high_task :高优先级任务,会获取二值信号量,获取成功以后会进行相应的处理,处理完
成以后就会释放二值信号量。
middle_task :中等优先级的任务,一个简单的应用任务。
low_task:低优先级任务,和高优先级任务一样,会获取二值信号量,获取成功以后会进行
相应的处理,不过不同之处在于低优先级的任务占用二值信号量的时间要久一点(软件模拟占用)。
实验中创建了一个二值信号量 BinarySemaphore,高优先级和低优先级这两个任务会使用这个二值信号量。
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//低优先级
#define LOW_TASK_PRIO 2
//任务堆栈大小
#define LOW_STK_SIZE 50
//任务句柄
TaskHandle_t LOWTask_Handler;
//任务函数
void low_task(void *pvParameters);
//中等优先级
#define MIDDLE_TASK_PRIO 3
//任务堆栈大小
#define MIDDLE_STK_SIZE 50
//任务句柄
TaskHandle_t MIDDLETask_Handler;
//任务函数
void middle_task(void *pvParameters);
//高优先级
#define HIGH_TASK_PRIO 4
//任务堆栈大小
#define HIGH_STK_SIZE 50
//任务句柄
TaskHandle_t HIGHTask_Handler;
//任务函数
void high_task(void *pvParameters);
//定义二值信号量句柄
SemaphoreHandle_t BinarySemaphore; //计数值信号量句柄
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_GPIO_INIT(); //按键初始化
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建信号量
BinarySemaphore = xSemaphoreCreateBinary(); //二值x信号量创建
if(NULL != BinarySemaphore)
{
//创建成功
xSemaphoreGive(BinarySemaphore); //释放二值信号量
}
else
{
printf("BinarySemaphore create failed\r\n");
}
//低任务
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&LOWTask_Handler);
//中等任务
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )MIDDLE_STK_SIZE,
(void* )NULL,
(UBaseType_t )MIDDLE_TASK_PRIO,
(TaskHandle_t* )&MIDDLETask_Handler);
//高优先级任务
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&HIGHTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0任务函数
void low_task(void *pvParameters)
{
static u32 times;
while(1)
{
//获取二值信号量
xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //死等
printf("low_task is running\r\n");
//模拟信号量 占用任务
for(times = 0;times < 2000000;times++)
{
taskYIELD(); // 使用任务切换
}
//释放信号量
xSemaphoreGive(BinarySemaphore);
vTaskDelay(1000);
}
}
//LED0任务函数
void middle_task(void *pvParameters)
{
while(1)
{
printf("middle_task is running\r\n");
vTaskDelay(1000);
}
}
//按键任务函数
void high_task(void *pvParameters)
{
while(1)
{
printf("high task request signal\r\n");
xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //死等
printf("high_task is running\r\n");
//释放信号量
xSemaphoreGive(BinarySemaphore);
vTaskDelay(1000);
}
}
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中(任务与任务或中断与任务之间的同步)二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中。在互斥访问中互斥信号量相当于一个钥匙,当任务想要使用资源的时候就必须先获得这个钥匙,当使用完资源以后就必须归还这个钥匙,这样其他的任务就可以拿着这个钥匙去使用资源。
互斥信号量使用和二值信号量相同的 API 操作函数,所以互斥信号量也可以设置阻塞时间,不同于二值信号量的是互斥信号量具有优先级继承的特性。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。优先级继承尽可能的降低了高优先级任务处于阻塞态的时间,并且将已经出现的“优先级翻转”的影响降到最低。
优先级继承并不能完全的消除优先级翻转,它只是尽可能的降低优先级翻转带来的影响。硬实时应用应该在设计之初就要避免优先级翻转的发生。互斥信号量不能用于中断服务函数中。
原因如下:
● 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
● 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
创建互斥量同样也有动态创建和静态创建,这里还是只介绍动态创建。
函数 xSemaphoreCreateMutex()
此函数用于创建一个互斥信号量,所需要的内存通过动态内存管理方法分配。此函数本质是一个宏,真正完成信号量创建的是函数 xQueueCreateMutex()。
SemaphoreHandle_t xSemaphoreCreateMutex( void )
参数:
无。
返回值:
NULL: 互斥信号量创建失败。
其他值: 创建成功的互斥信号量的句柄。
释 放 互 斥 信 号 量 的 时 候 和 二 值 信 号 量 、 计 数 型 信 号 量 一 样 , 都 是 用 的 函 数xSemaphoreGive()(实际上完成信号量释放的是函数 xQueueGenericSend())。不过由于互斥信号量涉及到优先级继承的问题,所以具体处理过程会有点区别。使用函数 xSemaphoreGive()释放信号 量 最 重 要 的 一 步 就 是 将 uxMessagesWaiting 加 一 , 而 这 一 步 就 是 通 过 函 数prvCopyDataToQueue() 来 完 成 的 , 释 放 信 号 量 的 函 数 xQueueGenericSend() 会调用prvCopyDataToQueue()。互斥信号量的优先级继承也是在函数 prvCopyDataToQueue()中完成的。
xSemaphoreGive( SemaphoreHandle_t xSemaphore );
//实际调用
BaseType_t xQueueGenericSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait,
const BaseType_t xCopyPosition )
传参:
信号量句柄
返回值:
pdTRUE 成功释放 pfFALSE 失败
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是
xSemaphoreTake()(实际执行信号量获取的函数是 xQueueGenericReceive()),获取互斥信号量的过程也需要处理优先级继承的问题,函数 xQueueGenericReceive()在文件 queue.c 中有定义。
* xSemaphoreTake(
* SemaphoreHandle_t xSemaphore,
* TickType_t xBlockTime
* );
//实际调用
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t
xTicksToWait, const BaseType_t xJustPeeking )
传参:
xSemaphore:信号量句柄
xBlockTime:阻塞时间
返回值:
pdTRUE 获取成功 pdFalse 失败
互斥实验的演示基础 优先级的翻转实验, 用互斥进行信号量的保护可以很好解决优先级问题。(将上面的二值信号量换成互斥信号量就行了)
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
#include "string.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//低优先级
#define LOW_TASK_PRIO 2
//任务堆栈大小
#define LOW_STK_SIZE 50
//任务句柄
TaskHandle_t LOWTask_Handler;
//任务函数
void low_task(void *pvParameters);
//中等优先级
#define MIDDLE_TASK_PRIO 3
//任务堆栈大小
#define MIDDLE_STK_SIZE 50
//任务句柄
TaskHandle_t MIDDLETask_Handler;
//任务函数
void middle_task(void *pvParameters);
//高优先级
#define HIGH_TASK_PRIO 4
//任务堆栈大小
#define HIGH_STK_SIZE 50
//任务句柄
TaskHandle_t HIGHTask_Handler;
//任务函数
void high_task(void *pvParameters);
//定义二值信号量句柄
SemaphoreHandle_t MutexSemaphore; //计数值信号量句柄
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化LED
KEY_GPIO_INIT(); //按键初始化
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建信号量
MutexSemaphore = xSemaphoreCreateMutex(); //互斥信号量创建
if(NULL == MutexSemaphore)
{
printf("BinarySemaphore create failed\r\n");
}
//低任务
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&LOWTask_Handler);
//中等任务
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )MIDDLE_STK_SIZE,
(void* )NULL,
(UBaseType_t )MIDDLE_TASK_PRIO,
(TaskHandle_t* )&MIDDLETask_Handler);
//高优先级任务
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&HIGHTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED0任务函数
void low_task(void *pvParameters)
{
static u32 times;
while(1)
{
//获取二值信号量
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //死等
printf("low_task is running\r\n");
//模拟信号量 占用任务
for(times = 0;times < 2000000;times++)
{
taskYIELD(); // 使用任务切换
}
//释放信号量
xSemaphoreGive(MutexSemaphore);
vTaskDelay(1000);
}
}
//LED0任务函数
void middle_task(void *pvParameters)
{
while(1)
{
printf("middle_task is running\r\n");
vTaskDelay(1000);
}
}
//按键任务函数
void high_task(void *pvParameters)
{
while(1)
{
printf("high task request signal\r\n");
xSemaphoreTake(MutexSemaphore,portMAX_DELAY); //死等
printf("high_task is running\r\n");
//释放信号量
xSemaphoreGive(MutexSemaphore);
vTaskDelay(1000);
}
}
实验结果
以上就是FreeRtos中信号量的基础学习,内容来自官方参考数据手册。