信号量
信号量
信号量用于对和,前者信号量代表可用资源量,后者信号量表示一个启动标志
共享资源访问
信号量对共享资源的访问控制就相当于一个上锁机制,上了锁之后这个资源就会被禁止访问。什么时候上锁呢?举个例子,停车场有100个位置,每进一辆车,信号量++,当信号量==100后,这个停车场就不能再进车了,外界不能访问,相当于上锁了。(计数信号量)
公用电话亭也是,没人占用电话亭,信号量为1,外界可以访问;有人占用电话亭,信号量为0,外界禁止访问,相当于上锁了。(二值信号量)
任务同步
在裸机中,为了让两个任务同步(即一个任务发出通知后另一个任务才开始执行),我是用全局变量来实现的。
在RTOS里面,现在可以用信号量。一个任务在执行的时候,释放出一个信号量,另一个任务就接收,接收到了该任务就被唤醒,然后等待下一次释放
创建二值信号量API
二值信号就是一个队列,给一个二值信号量就相当于往这个队列里面发送一个NULL,
xSemaphoreCreateBinary()
返回值是创建的二值信号量句柄,信号量所需要的RAM是由FreeRTOS来动态恢复的,创建好的二值信号量默认是空,获取API不能拿走
调用xQueueGenericCreate()创建一个长度为1,队列项长度为0的队列
没有看到xSemaphoreGive()函数,所以take不到信号量
,//创建二值信号量
BinarySemaphore=xSemaphoreCreateBinary();
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
#endif
//xQueueGenericCreate参数为队列长度,队列项长度, 队列类型释放信号量API
xSemaphoreGiveFromISR(BinarySemaphore,&xHigherPriorityTaskWoken); //释放二值信号量
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换相当于往这个信号队列xSemaphore里面发送一个NULL值
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )中断里函数要对pxHigherPriorityTaskWoken进行赋值,判断中断完了要不要进行任务切换
释放互斥量不能用这个函数,因为互斥量涉及到任务优先级继承,中断里没有
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )获取信号量
err=xSemaphoreTake(BinarySemaphore,portMAX_DELAY); //获取信号量传入信号量队列和阻塞等待时间
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )第二个参数是接收数据缓冲区的指针,最后一个参数是来决定读出信号量后是否从队列中删除,否则可以一直拿取信号量
| 参数 | 含义 |
|---|---|
pvBuffer == &value | 普通队列,要取出数据放进变量 |
pvBuffer == NULL | 信号量,不取数据,只消耗一个令牌(计数减 1) |
if( pxQueue->uxItemSize == 0 )
{
// 信号量模式,不需要拷贝数据
}
else
{
// 普通队列,需要把队列头的数据拷贝到 pvBuffer
}
当 uxItemSize == 0(信号量)时,
FreeRTOS 只会做“队列计数减一”这类操作,
不会访问 pvBuffer,所以 NULL 没问题。如果获取失败,在这个函数里面就会进行任务调度,不会进行这个函数下面的内容
计数型信号量
常用于资源管理
二值信号相当于长度为1的队列,计数信号量就是长度大于1的队列
事件计数():每次事件发生了,就在事件处理函数里面释放信号量,其他任务会获得信号量,这样的话信号量--;这种情况下初始计数值为0。
资源管理:这种情况下表示当前资源的可用数量,一个任务想要使用一个资源,就要先获得信号量,即信号量--;使用完毕要及时释放信号,即信号量++;
创建计数型信号量API
两个参数,一个是最大计数值,一个是初始值,创建成功会返回这个信号量的句柄
CountSemaphore=xSemaphoreCreateCounting(255,0);
*/
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
#endif
QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
{
QueueHandle_t xHandle;
configASSERT( uxMaxCount != 0 );
configASSERT( uxInitialCount <= uxMaxCount );
xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );
if( xHandle != NULL )
{
( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;
traceCREATE_COUNTING_SEMAPHORE();
}
else
{
traceCREATE_COUNTING_SEMAPHORE_FAILED();
}
return xHandle;
}还是会调用最基本的xQueueGenericCreate
优先级反转
问题发生在访问普通共享资源并用普通二值信号量,而不是临界资源;普通资源拿到信号量就能访问,但是访问过程中没有保护,可以被中断切换任务,又是普通二值信号量,从而引出连锁问题;访问临界资源要进入临界区,临界区是禁止了中断的,自然没有任务切换
前置关键知识:任务调用API拿取信号失败后,会将自己变为阻塞态并加入等待信号队列,除非有信号被释放,否则不会占用CPU
异常关键就在于,这是所谓优先级反转
有一个低优先级的任务,它获得了一个二值信号量(0,1),来访问一个资源;这段时间里,有一个高优先级任务也要访问这个资源,这个时候低优先级任务被挂起,开始执行高优先级任务,但是因为它没有获得信号量(这个信号量被低优先级任务占有),这个高优先级任务在的同时会触发任务调度,将自己变为阻塞态加入等待信号列表,直到有信号被释放。所以此时会执行一个优先级小于高优先级的任务(中优先级任务,如果中优先级任务存在的话),疑问:任务调度会调度这个低优先级任务吗(只有2个任务的极端情况下)?如果会,那么能让这个低优先级任务执行完释放信号吗?
两个都会的。但是如果有多个中优先级的任务并且很忙,
由此引出互斥信号量->优先级继承
互斥信号量
互斥信号量是一个有优先级继承机制的二值信号量
优先级继承:当一个互斥信号量被一个低优先级任务使用,此时有个高优先级任务也尝试获取这个互斥量的话就会阻塞,但是在阻塞之前这个任务会将低优先级任务的优先级提升到和自己相同的优先级,这样低优先级任务很快就被执行,就减少了高优先级任务阻塞等待的事件。
二值信号量适合需要任务同步的场景,互斥信号量适合需要资源管理的场景(拿了信号量才能访问,访问完了释放信号量)。
中断中只能去释放(Give)二值信号量或计数信号量,并且必须调用带 FromISR 后缀的专属 API
常用API
1️⃣ 创建互斥信号量
SemaphoreHandle_t xSemaphoreCreateMutex(void);功能:创建一个标准的互斥信号量(初始状态为“可用”)。
返回值:成功返回句柄(SemaphoreHandle_t),失败返回 NULL。
特点:支持优先级继承机制(防止优先级反转)
2️⃣ 获取互斥信号量
BaseType_t xSemaphoreTake(SemaphoreHandle_t xMutex, TickType_t xTicksToWait);- 功能:尝试获取信号量(上锁)。
- 参数:
xMutex:信号量句柄;xTicksToWait:等待时间(portMAX_DELAY表示无限等待)。
- 返回值:
pdTRUE:成功获得;pdFALSE:超时或失败。
- 作用:任务执行到此处如果信号量不可用,会进入阻塞队列。
🔓 3️⃣ 释放互斥信号量
BaseType_t xSemaphoreGive(SemaphoreHandle_t xMutex);- 功能:释放信号量(解锁)。
- 返回值:
pdTRUE:成功释放;pdFALSE:失败(如释放者不是持有者)。
- 注意:只有“持有锁”的任务才能释放。
🧹 4️⃣ 删除互斥信号量
void vSemaphoreDelete(SemaphoreHandle_t xMutex);- 功能:删除互斥信号量,释放其占用的内存。
- 注意:删除前应确保没有任务在等待该信号量。
具体代码例子见正点原子例程
递归互斥信号量
xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY)
xSemaphoreGiveRecursive(xRecursiveMutex);
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
SemaphoreHandle_t xRecursiveMutex;
void FunctionB(void)
{
printf("FunctionB: 想再次获取递归互斥信号量...\n");
if(xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE)
{
printf("FunctionB: 再次获得成功!执行任务...\n");
vTaskDelay(pdMS_TO_TICKS(200));
xSemaphoreGiveRecursive(xRecursiveMutex);
printf("FunctionB: 释放递归互斥信号量(第2层)\n");
}
}
void TaskA(void *pvParameters)
{
while(1)
{
if(xSemaphoreTakeRecursive(xRecursiveMutex, portMAX_DELAY) == pdTRUE)
{
printf("\nTaskA: 第1次获得递归互斥信号量\n");
FunctionB(); // 在此函数中又获得一次同一个锁
vTaskDelay(pdMS_TO_TICKS(200));
xSemaphoreGiveRecursive(xRecursiveMutex);
printf("TaskA: 释放递归互斥信号量(第1层)\n");
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void)
{
xRecursiveMutex = xSemaphoreCreateRecursiveMutex();
if(xRecursiveMutex != NULL)
{
xTaskCreate(TaskA, "TaskA", 256, NULL, 2, NULL);
vTaskStartScheduler();
}
for(;;);
}在函数内部加锁获得信号是为了让函数又有独立性,能自己保护自己,万一外层没加锁没得到信号,也不会出错
| 特性 | 普通二值信号量 | 递归互斥量 |
|---|---|---|
| 初始状态 | 空(不可用) | 可用(已释放) |
| 创建后 | 需要先释放才能获取 | 可以直接获取 |
| 用途 | 同步、事件通知 | 临界资源保护 |

