写在前面:
本文章如有错漏之处,敬请指正,另外本文为网络材料整理,侵删。
FreeRTOS信号量的基本使用&代码解析
一、信号量概述
信号量(Semaphore):常用于任务的同步,通过该信号,就能够控制某个任务的执行。
信号量分两种:二进制信号量和计数型信号量。
二、计数型信号量
计数型信号量可以用于资源管理,允许多个任务获取信号量访问共享资源,但会限制任务的最大数目,访问的任务数达到可支持的最大数目时,会阻塞其他试图获取该信号量的任务,直到有任务释放了信号量。
例如:某个资源限定只能有3个任务访问,那么第4个任务访问的时候,会因为获取不到信号量而进入阻塞,等到有任务(比如任务1)释放掉该资源的时候,第4个任务才能获取到信号量从而进行资源的访问。其运行机制具体见下图
再举个形象点的例子:
有三个停车位,我停车,停车位-1,我开车走了,停车位+1。申请资源就是P操作,释放资源就是V操作
三、二进制信号量
二进制信号量和计数型信号量的唯一区别就是计数值的最大值被限定为1。
要想访问资源需要先“take”信号量,让计数值减1,用完资源后,“give”信号量,让计数值加1
四、信号量函数API
1、创建信号量
使用信号量之前,要先创建,得到一个句柄,使用信号量时,要使用句柄来表明使用哪个信号量。
二进制信号量和计数型信号量创建函数不同
创建二进制信号量函数原型:
静态创建:
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );
动态创建:
SemaphoreHandle_t xSemaphoreCreateBinary( void );
创建计数型信号量函数原型:
静态创建:
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );
uxMaxCount: 最大计数值
uxInitialCount: 初始计数值
pxSemaphoreBuffer: StaticSemaphore_t 结构体指针
返回值: 返回句柄,非NULL表示成功
动态创建:
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount,
UBaseType_t uxInitialCount);
2、删除一个信号量
vSemaphoreDelete()用于删除一个信号量,包括二进制信号量、计数信号量、互斥量和递归互斥量。如果有任务阻塞在该信号量上,那么不要删除该信号量。
void vSemaphoreDelete(SemaphoreHandle_t xSemaphore)
xSemaphore——信号量句柄
3、信号量释放
xSemaphoreGive()是一个用于释放信号量的宏,真正的实现过程是调用消息队列通用发送函数。释放的信号量对象必须是已经被创建的。可以用于二进制信号量、计数型信号量、互斥信号量的释放。
BaseType_t xSemaphoreGive(SemaphoreHandle_t xSemaphore);
4、信号量获取
BaseType_t xSemaphoreTake(SemaphoreHandle_t xSemaphore,
TickType_t xTicksToWait);
xTicksToWait——如果无法马上获得信号量,阻塞一会。0不阻塞,马上返回
五、示例代码
1、使用二进制信号量来同步
创建两个任务,一个用于释放信号量,一个用于获取信号量。
#include "FreeRTOS.h"
#include "task.h"
#include "xsc_test.h"
#include <stdio.h>
#include <kernel/dpl/ClockP.h>
StackType_t xscStack1[1024] __attribute__((aligned(32)));
StackType_t xscStack2[1024] __attribute__((aligned(32)));
StaticTask_t xscTaskObj1;
StaticTask_t xscTaskObj2;
void vSendTask(void *pvParameters)
{
int cnt_ok = 0;
int cnt_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS(10UL);
while (1)
{
for (int i = 0; i < 3; i++)
{
if (xSemaphoreGive(xBinarySemaphore) == pdTRUE)
{
DebugP_log("Give xBinarySemaphore %d time: OK\r\n", cnt_ok++);
}
else
{
DebugP_log("Give xBinarySemaphore %d time: ERR\r\n", cnt_err++);
}
}
vTaskDelay(xTicksToWait);
}
}
void vRecvTask(void *argc)
{
int cnt_ok = 0;
int cnt_err = 0;
while (1)
{
if (xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE)
{
DebugP_log("Get xBinarySemaphore OK:%d\r\n", cnt_ok++);
}
else
{
DebugP_log("Get xBinarySemaphore ERR:%d\r\n", cnt_err++);
}
}
}
//Xsc_TestInit这个函数我在main函数调用了,就相当于main函数就行
void Xsc_TestInit(void)
{
//创建二进制信号量
xBinarySemaphore = xSemaphoreCreateBinary();
if (xBinarySemaphore != NULL)
{
//释放信号量
xTaskCreateStatic(vSendTask, "vSendTask", 1024, NULL, 2, xscStack1, &xscTaskObj1);
//获取信号量
xTaskCreateStatic(vRecvTask, "vRecvTask", 1024, NULL, 1, xscStack2, &xscTaskObj2);
}
else
{
DebugP_log("xSemaphoreCreateBinary Failed!\r\n ");
}
}
代码解释:
连续三次释放信号量,只有第一次能成功,然后发送任务阻塞,接收任务执行,获得信号量阻塞。在vTaskDelay退出之前都是运行的空闲任务。发送任务再次执行,连续三次释放信号量,只有第一次能成功,发送任务再阻塞,接受任务唤醒,打印OK。
demo演示:
2、使用计数型信号量
创建了一个计数型信号量,最大计数值为3,初始值为0,然后创建两个任务,一个用于释放信号量,一个用于获取信号量。
#include "FreeRTOS.h"
#include "task.h"
#include "xsc_test.h"
#include <stdio.h>
#include <kernel/dpl/ClockP.h>
StackType_t xscStack1[1024] __attribute__((aligned(32)));
StackType_t xscStack2[1024] __attribute__((aligned(32)));
StaticTask_t xscTaskObj1;
StaticTask_t xscTaskObj2;
void vSendTask(void *pvParameters)
{
int cnt_ok = 0;
int cnt_err = 0;
const TickType_t xTicksToWait = pdMS_TO_TICKS(10UL);
while (1)
{
for (int i = 0; i < 4; i++)
{
if (xSemaphoreGive(xCountingSemaphore) == pdTRUE)
{
DebugP_log("Give xCountingSemaphore %d time: OK\r\n", cnt_ok++);
}
else
{
DebugP_log("Give xCountingSemaphore %d time: ERR\r\n", cnt_err++);
}
}
vTaskDelay(xTicksToWait);
}
}
void vRecvTask(void *argc)
{
int cnt_ok = 0;
int cnt_err = 0;
while (1)
{
if (xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE)
{
DebugP_log("Get xCountingSemaphore OK:%d\r\n", cnt_ok++);
}
else
{
DebugP_log("Get xCountingSemaphore ERR:%d\r\n", cnt_err++);
}
}
}
//Xsc_TestInit这个函数我在main函数调用了,就相当于main函数就行
void Xsc_TestInit(void)
{
//创建计数型信号量
xCountingSemaphore = xSemaphoreCreateCounting(3, 0);
if (xCountingSemaphore != NULL)
{
//释放信号量
xTaskCreateStatic(vSendTask, "vSendTask", 1024, NULL, 2, xscStack1, &xscTaskObj1);
//获取信号量
xTaskCreateStatic(vRecvTask, "vRecvTask", 1024, NULL, 1, xscStack2, &xscTaskObj2);
}
else
{
DebugP_log("xSemaphoreCreateBinary Failed!\r\n ");
}
}
代码解释:
发送任务连续释放4个信号量,只有前面3次成功,第4次失败,阻塞,接受任务唤醒,得到三个信号量,阻塞。发送任务再次执行。
demo演示: