提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
FreeRTOS中的信号量和互斥量
前言
我可以通过队列来实现同步和异步,为什么还需要一个信号量呢?使用信号量的优点在哪呢,为什么需要互斥量呢?互斥量的优点在哪呢?
一、信号量
本文主要讲述二值信号量。
为什么需要信号量来实现同步
1、简单性和效率:本文所说的二值信号的机制简单,只需要一个计数器就可以表示资源是否可用,信号量的获取和释放不需要像队列一样创建、拷贝、存储和删除等操作,效率更高。
2、资源的管理:非常适合用于表示有限资源的数量,如资源池中有多少资源可用。而对于那些不需要传递任何数据,只是单纯想同步任务执行顺序的情况,信号量是一个很好的选择。
什么是二值信号量
二值信号量是一种基础的同步机制,它是一种特殊的信号量,其计数值只能为0或1。二值信号量主要用于表示一个特定的资源是否可用,或者某个条件是否满足。当二值信号量的值为1时,表示资源可用或某个条件为真;当其值为0时,则表示资源不可用或条件未满足。
在利用队列的时候我们通过给队列发送,当任务结束之后再释放。同样的道理。在信号量中我们也要获取(Take)二值信号量和给予(give)二值信号量
**
- xSemaphoreTake() 的函数尝试获取二值信号量时,如果信号量的值为1,则将其值设置为0,并允许任务继续执行;如果信号量的值为0,则任务将被阻塞,直到其他任务释放该信号量。
- xSemaphoreGive() 的函数时,信号量的值从0变为1,表示资源现在可用。如果有其他任务因为等待这个信号量而被阻塞,那么系统会根据调度策略解阻塞其中一个等待的任务。
如何通过信号量来实现同步
创建两个任务和一个句柄
static SemaphoreHandle_t xSemCalc;
void Task1Function(void * param)
{
volatile int i = 0;
while (1)
{
for (i = 0; i < 10000000; i++)
sum++;
//printf("1");
xSemaphoreGive(xSemCalc);
vTaskDelete(NULL);
}
}
void Task2Function(void * param)
{
while (1)
{
//if (flagCalcEnd)
flagCalcEnd = 0;
xSemaphoreTake(xSemCalc, portMAX_DELAY);
flagCalcEnd = 1;
printf("sum = %d\r\n", sum);
}
}
主函数
xSemCalc = xSemaphoreCreateCounting(10, 0);
xTaskCreate(Task1Function, "Task1", 100, NULL, 1, &xHandleTask1);
xTaskCreate(Task2Function, "Task2", 100, NULL, 1, NULL);
可以看到变量flagCalcEnd变1的时间和利用队列来实现同步所用时间差不多。
如何通过二值信号量来实现互斥
首先编写一段任务函数、定义一个UART句柄
static SemaphoreHandle_t xSemUART;
void TaskGenericFunction(void * param)
{
while (1)
{
xSemaphoreTake(xSemUART, portMAX_DELAY);
printf("%s\r\n", (char *)param);
xSemaphoreGive(xSemUART);
vTaskDelay(1);
}
}
主函数
xSemUART = xSemaphoreCreateBinary();
xSemaphoreGive(xSemUART);
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
我们来看看结果,Task4和Task3是否实现互斥
实验现象和利用队列实现同步一样。
二、互斥量
为什么需要互斥量来实现互斥
在利用队列来实现互斥的时候我们是需要在初始化的时候手动给队列初始值,但是如果使用互斥量我们无需初始值
可以看到在内部已经实现队列的初始化,所以我们只需要创建就可以。
调用 xSemaphoreCreateMutex() 函数时,系统会创建一个新的互斥量实例,并初始化为未锁定状态。其他任务可以通过调用
- xSemaphoreTake() 函数来获取(锁定)此互斥量,获取成功的任务可以访问受保护的资源;
- xSemaphoreGive() 函数来释放(解锁)互斥量,以便其他任务可以获取并访问资源。
实现程序及现象
任务函数
void TaskGenericFunction(void * param)
{
while (1)
{
xSemaphoreTake(xSemUART, portMAX_DELAY);
printf("%s\r\n", (char *)param);
xSemaphoreGive(xSemUART);
vTaskDelay(1);
}
}
主函数
xSemUART = xSemaphoreCreateMutex();
xTaskCreate(TaskGenericFunction, "Task3", 100, "Task 3 is running", 1, NULL);
xTaskCreate(TaskGenericFunction, "Task4", 100, "Task 4 is running", 1, NULL);
实验现象
使用互斥量的好处
我们通过一个具体的例子来了解使用互斥量的好处,假设我们创建了三个任务,三个优先级分别是高、中、低,程序从高任务开始执行,高任务执行之后故意Delay一段时间,让中优先级任务开始工作,中优先级任务做完delay一段时间,让低优先级的任务做,这个时候高任务唤醒执行后继续delay,此时低任务开始运行,但是当中优先级任务delay结束,抢夺资源那么以后得CPU资源就会一直被中优先级的任务所占有。这就是优先级反转。
优先级继承
那么使用互斥量的好处就来了,互斥量中我们可以优先级继承
在上述想象中,如果我们引入互斥量,当高任务执行完之后会将优先级继承给低任务,当高任务唤醒后开始执行那么就不会受到中优先级任务的影响。
递归锁
我们通过一个具体的例子来了解使用互斥量的好处,假设我们有三个任务,任务3任务4正常打印,任务5捣乱强行解锁,那么会发生什么现象?
void TaskGenericFunction(void * param)
{
int i;
while (1)
{
xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);
printf("%s\r\n", (char *)param);
for (i = 0; i < 10; i++)
{
xSemaphoreTakeRecursive(xSemUART, portMAX_DELAY);
printf("%s in loop %d\r\n", (char *)param, i);
xSemaphoreGiveRecursive(xSemUART);
}
xSemaphoreGiveRecursive(xSemUART);
vTaskDelay(1);
}
}
void Task5Function(void * param)
{
vTaskDelay(10);
while (1)
{
printf("%s\r\n", (char *)param);
xSemaphoreGiveRecursive(xSemUART);
vTaskDelay(1);
}
}
可以看到任务5的捣乱影响了任务3和任务4的正常运行。
当我们利用递归锁之后就可以看到程序正常执行。
void Task5Function(void * param)
{
vTaskDelay(10);
while (1)
{
while (1)
{
if (xSemaphoreTakeRecursive(xSemUART, 0) != pdTRUE)
{
xSemaphoreGiveRecursive(xSemUART);
}
else
{
break;
}
}
printf("%s\r\n", (char *)param);
xSemaphoreGiveRecursive(xSemUART);
vTaskDelay(1);
}
}
总结
以上就是FreeRTOS中的信号量和互斥量的基本使用,只是简单的运用,源码是如何实现还需要花费大量的时间去理解。