一、信号量的基本概念
FreeRtos信号量包括二值信号量、计数型信号量、互斥信号量和递归互斥信号量。 信号量用于实现任务间的同步或临界资源的互斥访问《相当于一个上锁机制,代码只有获得了这个锁的钥匙才能够执行。信号量类似于我们在裸机编写中断服务函数中给予标志位赋值的操作,然后根据标志位所对应的函数让其执行。当某个任务需要获得对应的指令才能开始执行时,因为主程序不可能一直等待对应指令的产生而不去执行其它的任务,这时信号量便可保证其它任务能够正常运行的同时也能兼顾到指令一旦发出对应程序能够立马执行。在使用Rtos系统时我们可以借助信号量来完成这个操作,当中断发生的时候就释放信号量,中断服务函数不作任何处理,当任务获取到信号量的时候,就会开始完成对相应任务的处理。这样做的好处是中断执行时间非常短且更节省内存。信号量的使用不仅使用与任务与中断之间的同步,也可以同时实现任务与任务之间的同步。
1.二值信号量
二值信号量(Binary Semaphore)是一种特殊的信号量,其值只能为0或1,0代表获取,1代表释放,因此也被称为互斥二进制信号量(尽管它与传统的互斥锁或互斥量在细节上有所不同)。二值信号量在操作系统、嵌入式系统以及多任务环境中被广泛应用,主要用于任务同步、中断同步以及控制对共享资源的访问。任务同步:信号量在刚被创建时置为空,任务一未获取到信号量而处于阻塞状态,当任务二在某种条件成立下而开始执行并释放信号量,此时任务一获取到信号量由阻塞态阻塞态转为就绪态。如果任务一的优先级是最高的,那么任务会立马执行,从而达到两个任务间的同步。同样的,在中断 服务函数中释放信号量,任务 1 也会得到信号量,从而达到任务与中断间的同步。在FreeRtos当中可以将二值信号量看作一个消息队列,无需关心消息的内容,只需查看队列内是否有消息即可。
二.计数信号量
计数信号量类似于公共停车场,该停车场有50个车位可以供车辆停车,停车场每停放一辆汽车,停车场便减少一个车位,直到停满50辆汽车,该停车场便无法容纳更多车辆。FreeRtos中的计数信号量亦是如此,一个个信号量视为一辆辆汽车,当任务执行时会取走对应的信号量,车辆驶出停车场时,原本的计数值对应减一,停车场的停车位就空出一个。当计数值为50代表着可容纳50个信号量,0个则代表着无位置可容纳更多的信号量,也就代表着系统没有更多的资源给任务去使用,停车场没有更多停车位给其他车辆停放。
三.互斥信号量
互斥信号量是特殊的二值信号量,其特有的优先级继承制机制从而使它更适用于简单的互锁,也就是保护临界资源。(临界资源是指任何时刻都只允许一个任务进行访问),当任务需要访问临界资源时,先获取互斥信号量,使其为空,这样可以使其它任务要使用临界资源时无法获取到信号量而进入阻塞,从而保证了临界资源的单一任务访问。当任务在访问临界资源时,就会给临界资源建立一个标志位,当其它任务想要使用临界资源时会先查询这个标志位,若该标志位不为空则代表临界资源正在被占用,其它任务则进入阻塞态,这样使临界资源得到有效的保护。
4.递归信号量
递归信号量,见文知义,递归嘛,就是可以重复获取调用的,本来按照信号量的特性, 每获取一次可用信号量个数就会减少一个,但是递归则不然,对于已经获取递归互斥量的任务可以重复获取该递归互斥量,该任务拥有递归信号量的所有权。任务成功获取几次递 归互斥量,就要返还几次,在此之前递归互斥量都处于无效状态,其他任务无法获取,只 有持有递归信号量的任务才能获取与释放。
二、二值信号量的运作机制
当任务获取想要信号量时,若获取的信号量无效,则说明对应的任务或中断未释放信号量,此时获取信号的任务就处于阻塞态,若对应的任务或中断释放信号量,则该任务获取到信号量便由阻塞态转为就绪态,若该任务的优先级最高则开始执行。
图1———信号量无效
图2———信号量有效
三、计数值信号量运作机制
计数信号量可以运用于资源管理,允许多个任务获取信号量来访问共享资源,但会限制最大任务访问数目。当任务访问的数目达到最大值,则会阻塞其它任务通过获取信号量来访问共享资源,直到正在访问资源的任务释放信号量结束对共享资源的访问,此时其它的任务才能通过获取信号量来访问共享资源。
四、常用信号量函数接口讲解
1.创建二值信号量 xSemaphoreCreateBinary()
(1)“1”代表信号量的长度
(2)semSEMAPHORE_QUEUE_ITEM_LENGTH代表创建信息空间(无需关注内容所以置为0)
(3) queueQUEUE_TYPE_BINARY_SEMAPHORE代表创建的信号量为二值信号量
2.创建计数信号量xSemaphoreCreateCounting()
3.信号量删除函数 vSemaphoreDelete()
可以删除二值信号量,互斥信号量,递归信号量和递归互斥信号等信号量。有任务阻塞时不应删除信号量!
4.信号量释放函数
两个API函数都用于信号量释放,前者用于任务当中,后者运用于中断当中。xSemaphoreGive()能对二值,计数与互斥信号量进行释放,无法对递归信号量进行释放,并无法在中断中使用。xSemaphoreGiveFromISR()在中断中可释放二值与计数信号量无法释放递归和互斥信号量。
运用于任务
运用于中断
5.信号量获取函数
用于二值信号量,计数信号量,互斥信号量的获取。当信号量有效的时候,任务才能获取 信号量,当任务获取了某个信号量的时候,该信号量的可用个数就减一,当它减到 0 的时 候,任务就无法再获取了,并且获取的任务会进入阻塞态(假如用户指定了阻塞超时时间 的话)。如果某个信号量中当前拥有 1 个可用的信号量的话,被获取一次就变得无效了, 那么此时另外一个任务获取该信号量的时候,就会无法获取成功,该任务便会进入阻塞态, 阻塞时间由用户指定。
xSemaphoreTake()函数使用实例
//接收任务函数
void receive_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while(1)
{
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("BinarySem_Handle二值信号量获取成功!\n\n");
LED2=!LED2;
}
}
五、二值信号量实验
在开始任务中创建两个任务,一个是接收任务,另一个发送任务。接收任务中有着获取信号量的API函数,等待发送函数释放信号量,获取信号量任务是一直在等待信号量,其等待时间 是 portMAX_DELAY,等到获取到信号量之后,任务开始执行任务代码此任务在未获取到信号量时处于阻塞状态,按键按下则会释放信号量,此时释放信号量会唤醒获取任务,获取任务开始运行,然后形成两个任务间的同步。
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define RECEIVE_TASK_PRIO 3
//任务堆栈大小
#define RECEIVE_STK_SIZE 50
//任务句柄
TaskHandle_t ReceiveTask_Handler;
//任务函数
void receive_task(void *pvParameters);
//任务优先级
#define SEND_TASK_PRIO 4
//任务堆栈大小
#define SEND_STK_SIZE 50
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void send_task(void *pvParameters);
SemaphoreHandle_t BinarySem_Handle =NULL;
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS二值信号量实验\r\n");
printf("按下KEY_UP或者KEY1进行任务与任务间的同步\r\n");
printf("Receive任务接收到消息在串口回显\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(); //进入临界区
/* 创建 BinarySem */
BinarySem_Handle = xSemaphoreCreateBinary();
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建接收任务
xTaskCreate((TaskFunction_t )receive_task,
(const char* )"receive_task",
(uint16_t )RECEIVE_STK_SIZE,
(void* )NULL,
(UBaseType_t )RECEIVE_TASK_PRIO,
(TaskHandle_t* )&ReceiveTask_Handler);
//创建发送任务
xTaskCreate((TaskFunction_t )send_task,
(const char* )"send_task",
(uint16_t )SEND_STK_SIZE,
(void* )NULL,
(UBaseType_t )SEND_TASK_PRIO,
(TaskHandle_t* )&SendTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//接收任务函数
void receive_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while(1)
{
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("BinarySem_Handle二值信号量获取成功!\n\n");
LED2=!LED2;
}
}
//发送任务函数
void send_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP_PRESS)
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if( xReturn == pdTRUE )
printf("BinarySem_Handle二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle二值信号量释放失败!\r\n");
}
else if(key==KEY1_PRESS)
{
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if( xReturn == pdTRUE )
printf("BinarySem_Handle二值信号量释放成功!\r\n");
else
printf("BinarySem_Handle二值信号量释放失败!\r\n");
}
vTaskDelay(20);
}
}
六、计数信号量实验
计数信号量实验是模拟停车场工作运行。在创建信号量是初始化5个可用的信号量,并且创造了两个任务,一个是获取信号量任务,一个是释放信号量任务,两个任务独立运行,获取信号量任务是通过按下KEY1键来获取信号量,另一按键KEY2按下来释放信号量。
#include "system.h"
#include "SysTick.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "semphr.h"
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define LED1_TASK_PRIO 2
//任务堆栈大小
#define LED1_STK_SIZE 50
//任务句柄
TaskHandle_t LED1Task_Handler;
//任务函数
void led1_task(void *pvParameters);
//任务优先级
#define RECEIVE_TASK_PRIO 3
//任务堆栈大小
#define RECEIVE_STK_SIZE 50
//任务句柄
TaskHandle_t ReceiveTask_Handler;
//任务函数
void receive_task(void *pvParameters);
//任务优先级
#define SEND_TASK_PRIO 4
//任务堆栈大小
#define SEND_STK_SIZE 50
//任务句柄
TaskHandle_t SendTask_Handler;
//任务函数
void send_task(void *pvParameters);
SemaphoreHandle_t CountSem_Handle =NULL;
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
LED_Init();
KEY_Init();
USART1_Init(115200);
printf("FreeRTOS计数信号量实验\r\n");
printf("车位默认值为5个,按下KEY_UP申请车位,按下KEY1释放车位\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(); //进入临界区
/* 创建 CountSem */
CountSem_Handle = xSemaphoreCreateCounting(5,5);
//创建LED1任务
xTaskCreate((TaskFunction_t )led1_task,
(const char* )"led1_task",
(uint16_t )LED1_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED1_TASK_PRIO,
(TaskHandle_t* )&LED1Task_Handler);
//创建接收任务
xTaskCreate((TaskFunction_t )receive_task,
(const char* )"receive_task",
(uint16_t )RECEIVE_STK_SIZE,
(void* )NULL,
(UBaseType_t )RECEIVE_TASK_PRIO,
(TaskHandle_t* )&ReceiveTask_Handler);
//创建发送任务
xTaskCreate((TaskFunction_t )send_task,
(const char* )"send_task",
(uint16_t )SEND_STK_SIZE,
(void* )NULL,
(UBaseType_t )SEND_TASK_PRIO,
(TaskHandle_t* )&SendTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//LED1任务函数
void led1_task(void *pvParameters)
{
while(1)
{
LED1=0;
vTaskDelay(200);
LED1=1;
vTaskDelay(800);
}
}
//接收任务函数
void receive_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY1_PRESS)
{
xReturn = xSemaphoreTake( CountSem_Handle,0 );//获取计数信号量
if( xReturn == pdTRUE )
printf( "KEY1被按下,释放1个停车位。\r\n" );
else
printf( "KEY1被按下,但已无车位可以释放!\r\n" );
}
vTaskDelay(20);
}
}
//发送任务函数
void send_task(void *pvParameters)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
u8 key=0;
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP_PRESS)
{
xReturn = xSemaphoreGive( CountSem_Handle );//给出计数信号量
if( xReturn == pdTRUE )
printf("KEY_UP被按下,成功申请到停车位。\r\n");
else
printf("KEY_UP被按下,不好意思,现在停车场已满!\r\n");
}
vTaskDelay(20);
}
}
七、实验现象
记得在FreeRTOSConfig.h中将该宏定义置为1,不然会一直报错