信号量类型:
二值信号量:
状态只有0和1,表示是否有信号,类似钥匙,用于事件通知和简单同步。
计数信号量:
带有计数器,可递增或递减,用于控制特定资源访问数量。
互斥信号量:
用于保护共享资源,同一时刻仅一个任务能访问,支持优先级继承。
二值与互斥信号量异同:
相同点:都用于任务同步和资源访问控制,有可用、不可用两种状态,获取后需释放下一个任务才能获取。
不同点:
设计目的:互斥信号量保护共享资源,解决优先级翻转;二值信号量用于事件通知。
语义用法:互斥信号量谁获取谁释放;二值信号量任何任务或中断可释放。
使用场景:互斥信号量不能用于中断;二值信号量有带ISR结尾API可用于中断。
内部实现:互斥信号量是特殊二值信号量,创建时有优先级继承机制,支持递归获取;二值信号量不支持。
互斥锁(Mutex)的作用:
操作流程:任务访问共享资源前先获取互斥锁,操作完成后释放,确保同一时刻只有一个任务能访问共享资源,避免竞争条件。
输出完整性:保证共享变量操作及输出的完整性,一个任务执行时,其他任务等待。
二值信号量:
状态与初始化:用于任务同步,有take和give两个状态,默认初始化处于释放状态(队列中有信号量),创建OS信号量时初始为空队列,需先放入信号量才能获取。
应用场景:常用于任务与中断之间的同步。
互斥信号量与二值信号量区别:
初始状态:互斥信号量初始状态就存在一个信号量,任务可立刻获取,无需额外添加。(xSemaphoreTake(xMutex,portMAX_DELAY))
任务行为:任务调用获取信号量,若已持有则挂起,简化任务同步步骤。
应用场景:专门用于保护共享资源,通过优先级继承机制避免优先级反转问题。
互斥锁代码示例:
创建3个任务,Task1,Task2,Task3,共同使用同一资源sharedCounter,在任务中对其自增,在main 函数里面创建并调用Task1,Task2,Task3,如果不使用互斥就会导致资源不会按照预期进行自增长。使用互斥信号量之后,sharedCounter就会按照时间片任务逐次增加
#include "FreeRTOS.h"
#include "semphr.h"
int sharedCounter = 0;
SemaphoreHandle_t xMutex; //创建互斥信号量的句柄
void Task1(void *pvParameter)
{
while(1)
{
if(pdTRUE == xSemaphoreTake(xMutex,portMAX_DELAY))
{
sharedCounter++;
printf("Task1:sharedCounter = %d \r\n",sharedCounter);
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void Task2(void *pvParameter)
{
while(1)
{
if(pdTRUE == xSemaphoreTake(xMutex,portMAX_DELAY))
{
sharedCounter++;
printf("Task2:sharedCounter = %d \r\n",sharedCounter);
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void Task3(void *pvParameter)
{
while(1)
{
if(pdTRUE == xSemaphoreTake(xMutex,portMAX_DELAY))
{
sharedCounter++;
printf("Task3:sharedCounter = %d \r\n",sharedCounter);
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USART1_UART_Init();
printf("hello world");
/* USER CODE BEGIN 2 */
xMutex = xSemaphoreCreateMutex();
xTaskCreate(Task1,"task1",100,NULL,1,NULL);
xTaskCreate(Task2,"task2",100,NULL,1,NULL);
xTaskCreate(Task3,"task3",100,NULL,1,NULL);
vTaskStartScheduler();
/* USER CODE END 2 */
/* Init scheduler */
osKernelInitialize(); /* Call init function for freertos objects (in freertos.c) */
MX_FREERTOS_Init();
/* Start scheduler */
osKernelStart();
/* We should never get here as control is now taken by the scheduler */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
如果不使用互斥锁,sharecounter不会自增1的打印,而是有可能是
Task1:sharedCounter = 1
Task2:sharedCounter = 4
Task3:sharedCounter = 7
Task1:sharedCounter = 10
Task2:sharedCounter = 13
Task3:sharedCounter = 16
Task1:sharedCounter = 19
Task2:sharedCounter = 22
Task3:sharedCounter = 25
\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
这里是分界线
首先先看看这个,对理解下面的问题有帮助。
互斥量
特点:
所有权机制:获取互斥量的任务必须是释放它的任务。
优先级继承:大多数RTOS支持优先级继承,避免优先级反转问题。
嵌套锁定:同一任务可以多次获取统一互斥量(需相应释放),避免自死锁。
二值信号量:
特点:二进制计数器:值只能是0(不可用)或1(可用)
无所有权:任何任务都可以释放信号量,即使不是获取者
用途灵活:可用于互斥访问,也可用于任务同步(如时间标志)
今天遇到一个问题:
感觉有点意思,我先把代码给贴出来。
事情是这样子的,我现在用Ymodem协议在做OTA升级,过程中呢有点问题
在Ymodem协议中有这么一段代码。
else
{
packet_data+=PACKET_HEADER;
//xQueueSend(Queue_AppDataBuffer, &(packet_data+PACKET_HEADER), 0); //这样写是错的
xQueueSend(Queue_AppDataBuffer, &packet_data, 0); //发送净荷数据
xSemaphoreTake(Semaphore_ExtFlashState,portMAX_DELAY);
xSemaphoreGive(Semaphore_ExtFlashState);
if(packet_data == buf1)
{
packet_data = buf2;
}
else
{
packet_data = buf1;
}
Send_Byte(ACK);
}
packets_received ++;
session_begin = 1;
}
问题就是这个互斥量,先拿到互斥量,然后立马就给释放了?这不是做无用功?这是为啥?
首先,要知道互斥量也是队列啊!他拿到互斥量然后再释放,吃多了?
并不是,他在等!等这个其他线程中的互斥量给我释放,我才能干下面的事儿。为啥?因为下面有一个buffer转换的过程,在我这个Ymodem协议中,我设置了两个缓冲buffer,buffer1以及buffer2。那么另外一个互斥量被谁拿了?就是我这个下载数据的线程里面,再仔细点就是在这个while(1)循环里面。
void DownloadAppData_task_runnable(void *argument)
{
uint8_t * pu8_data = NULL;
int32_t * pu32_size = NULL;
//第一帧数据一定是数据的长度
xQueueReceive(Queue_AppDataBuffer,&pu32_size,portMAX_DELAY);
//需要擦除掉flash size 所对应的扇区
//TODO:
//xSemaphoreGive(Semaphore_ExtFlashState);//这里没必要,因为互斥量在刚开始创建的时候,队列里有值
while(1)
{
xQueueReceive(Queue_AppDataBuffer,&pu8_data,portMAX_DELAY);
xSemaphoreTake(Semaphore_ExtFlashState,0);
if(pu8_data == NULL)
{
continue;
}
//执行写入W25q的逻辑
//TODO:
xSemaphoreGive(Semaphore_ExtFlashState);
}
/* USER CODE END DowanloadAppData_task_runnable */
}
当在Ymodem协议发送完数据后,DownloadAppData_task_runnable这个函数中,Queue_AppDataBuffer就接受到数据了,这里有两个接受数据的队列,第一个是接收数据长度,第二个是接收净荷数据(就是纯数据,没有数据长度、命令啥的),在这个while(1)循环中,当我接收到数据之后,就要把数据放入buffer中,拿到互斥量之后,就开始等这个buffer 是不是为空指针,不是空指针没说明数据已经拿到了,释放互斥量。当释放完毕之后,Ymodemx协议中这个才能拿到这个互斥量,拿到了互斥量就说明下载函数中中的活了已经干完了,Ymodem继续。。。。