FreeRTOS-信号量/互斥量

目录

信号量

创建信号量

删除信号量

give/take

应用场景:使用二进制信号量来同步

应用场景:防止数据丢失

应用场景:使用计数型信号量

互斥量

创建互斥量

删除互斥量

give释放/take获取

应用场景:互斥量基本使用

应用场景:谁上锁就由谁解锁?

应用场景:优先级反转

应用场景:优先级继承

递归锁


信号量

信号:起通知作用。

量:表示资源的数量。

        当量没有限制时,就是计数型信号量。

        当量只有0、1时,就是二进制信号量。

支持的动作:give给出资源,计数值+1;take获得资源,计数值-1。

队列信号量

可以容纳多个数据,

创建队列时有2部分内存:队列结构体、存储数据的空间

只有计数值,无法容纳其他数据。

创建信号量时,只需要分配信号量结构体

生产者:没有空间存入数据时可以阻塞生产者:用于不阻塞,计数值已经达到最大时返回失败
消费者:没有数据时可以阻塞消费者:没有数据时可以阻塞
二进制信号量计数型信号量
被创建时初始值为0被初始化时初始值可以设定
其他操作是一样的其他操作是一样的

创建信号量

二进制信号量计数型信号量
动态创建xSemaphoreCreateBinary计数值初始值为0xSemaphoreCreateCounting
vSemaphoreCreateBinary计数值初始值为1(过时了)
静态创建xSemaphoreCreateBinaryStaticxSemaphoreCreateCountingStatic
/* 创建一个二进制信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinary( void );

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );

删除信号量

对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。

vSemaphoreDelete可以用来删除二进制信号量、计数型信号量。

/* xSemaphore: 信号量句柄,你要删除哪个信号量 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

give/take

二进制信号量、计数型信号量的give、take操作函数是一样的。也分两个版本:给任务使用、给ISR使用。

在任务中使用在ISR中使用
givexSemaphoreGivexSemaphoreGiveFromISR
takexSemaphoreTakexSemaphoreTakeFromISR
// xSemaphore:信号量句柄
// 返回值:
//		pdTRUE:成功
//		失败:如果二进制信号量的计数值已经是1,再次调用此函数时返回失败
//		失败:如果计数型信号量的计数值已经是最大值,再次调用此函数时返回失败
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

// xSemaphore:信号量句柄
// pxHigherPriorityTaskWoken:如果释放信号量导致更高优先级的任务变成了就绪态,则*pxHigherPriorityTaskWoken = pdTRUE
// 返回值:
//		pdTRUE:成功
//		失败:如果二进制信号量的计数值已经是1,再次调用此函数时返回失败
//		失败:如果计数型信号量的计数值已经是最大值,再次调用此函数时返回失败
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );

// xSemaphore:信号量句柄
// xTicksToWait:如果无法马上获得信号量,则阻塞一会。0表示不阻塞,马上返回。portMAX_DELAY表示一直阻塞直到成功。
// 返回值:
//		pdTRUE:成功
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

// xSemaphore:信号量句柄
// pxHigherPriorityTaskWoken:如果获取信号量导致更高优先级的任务变成了就绪态,则*pxHigherPriorityTaskWoken = pdTRUE
// 返回值:
//		pdTRUE:成功
BaseType_t xSemaphoreTakeFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );

应用场景:使用二进制信号量来同步

创建一个二进制信号量,创建两个任务(一个用于释放信号量、另一个用于获取信号量)。

/* 二进制信号量句柄 */
SemaphoreHandle_t xBinarySemaphore;

int main( void )
{
	prvSetupHardware();
	
	/* 创建二进制信号量 */
	xBinarySemaphore = xSemaphoreCreateBinary( );
	if( xBinarySemaphore != NULL )
	{
		/* 创建1个任务用于释放信号量,优先级为2 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
		
		/* 创建1个任务用于获取信号量,优先级为1 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
	
		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建二进制信号量 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vSenderTask( void *pvParameters )
{
	int i;
	int cnt_ok = 0;
	int cnt_err = 0;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	
	/* 无限循环 */
	for( ;; )
	{
		for (i = 0; i < 3: i++)
		{
			// 给出资源,计数值+1
			if (xSemaphoreGive(xBinarySemaphore) == pdTRUE)
				printf("Give BinarySemaphore %d time: OK\r\n", cnt_ok++);
			else
				printf("Give Binarysemaphore %d time: ERR\r\n", cnt_err++);
		}
		
		vTaskDelay(xTicksToWait);
	}
}

static void vReceiverTask( void *pvParameters )
{
	int cnt_ok = 0;
	int cnt_err = 0;
	
	/* 无限循环 */
	for( ;; )
	{
		// 获得资源,计数值-1
		if( xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE )
		{
			/* 得到二进制信号量 */
			printf("Get	BinarySemaphore OK: %d\r\n", cnt_ok++);
		}else
		{
			/* 没有得到二进制信号量 */
			printf("Get	BinarySemaphore ERR: %d\r\n", cnt_err++);
		}
	}
}

实验现象:

Give BinarySemaphore 0 time: OK

Give BinarySemaphore 0 time: ERR

Give BinarySemaphore 1 time: ERR

Get BinarySemaphore OK: 0

Give BinarySemaphore 1 time: OK

Give BinarySemaphore 2 time: ERR

Give BinarySemaphore 3 time: ERR

Get BinarySemaphore OK: 1

Give BinarySemaphore 2 time: OK

Give BinarySemaphore 4 time: ERR

Give BinarySemaphore 5 time: ERR

...

应用场景:防止数据丢失

上个应用场景中,发送任务发出3次提醒,但是接收任务只接收到1次提醒,其中2次提醒丢失了。这种情况很常见,如每接收到一个串口字符,串口中断程序就给任务一次提醒,假设收到多个字符、发出多次提醒。当任务来处理时,它只能得到1次提醒。

这时可以使用其它方法来防止数据丢失,如:

        在串口中断中把数据放入缓冲区

        在任务中一次性把缓冲区的数据都读出

创建一个二进制信号量,然后创建2个任务(一个释放信号量,另一个获取信号量)。

/* 二进制信号量句柄 */
SemaphoreHandle_t xBinarySemaphore;

int main( void )
{
	prvSetupHardware();
	
	/* 创建二进制信号量 */
	xBinarySemaphore = xSemaphoreCreateBinary( );
	if( xBinarySemaphore != NULL )
	{
		/* 创建1个任务用于释放信号量,优先级为2 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
		
		/* 创建1个任务用于获取信号量,优先级为1 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
		
		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建二进制信号量 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vSenderTask( void *pvParameters )
{
	int i;
	int cnt_tx = 0;
	int cnt_ok = 0;
	int cnt_err = 0;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	
	/* 无限循环 */
	for( ;; )
	{
		for (i = 0; i < 3: i++)
		{
			txbuf_put('a' + cnt_tx);
			cnt_tx++;
			
			// 给出资源,计数值+1
			if (xSemaphoreGive(xBinarySemaphore) == pdTRUE)
				printf("Give BinarySemaphore %d time: OK\r\n", cnt_ok++);
			else
				printf("Give Binarysemaphore %d time: ERR\r\n", cnt_err++);
		}
		
		vTaskDelay(xTicksToWait);
	}
}

static void vReceiverTask( void *pvParameters )
{
	int cnt_ok = 0;
	int cnt_err = 0;
	uint8_t c;
	
	/* 无限循环 */
	for( ;; )
	{
		// 获得资源,计数值-1
		if( xSemaphoreTake(xBinarySemaphore, portMAX_DELAY) == pdTRUE )
		{
			/* 得到二进制信号量 */
			printf("Get	BinarySemaphore OK: %d, data: ", cnt_ok++);
			
			while(txbuf_get(&c) == 0)
			{
				printf("%c", c);
			}
			
			printf("\r\n");
		}else
		{
			/* 没有得到二进制信号量 */
			printf("Get	BinarySemaphore ERR: %d\r\n", cnt_err++);
		}
	}
}

实验现象:

Give BinarySemaphore 0 time: OK

Give BinarySemaphore 0 time: ERR

Give BinarySemaphore 1 time: ERR

Get BinarySemaphore OK: 0, data:abc

Give BinarySemaphore 1 time: OK

Give BinarySemaphore 2 time: ERR

Give BinarySemaphore 3 time: ERR

Get BinarySemaphore OK: 1, data:def

Give BinarySemaphore 2 time: OK, data:ghi

Give BinarySemaphore 4 time: ERR

Give BinarySemaphore 5 time: ERR

...

应用场景:使用计数型信号量

使用计数型信号量时,可以多次释放信号量。当信号量的计数值达到最大时再次释放信号量就会出错。

如果信号量计数值为n,就可以连续n次获取信号量,第n+1次获取信号量就会阻塞或失败。

创建了一个计数型信号量,最大计数值为3,初始值计数值为0。然后创建2个任务(一个用于释放信号量,另一个用于获取信号量)。

/* 计数型信号量句柄 */
SemaphoreHandle_t xCountingSemaphore;

int main( void )
{
	prvSetupHardware();
	
	/* 创建计数型信号量 */
	xCountingSemaphore = xSemaphoreCreateCounting(3, 0);
	if( xCountingSemaphore != NULL )
	{
		/* 创建1个任务用于释放信号量,优先级为2 */
		xTaskCreate( vSenderTask, "Sender", 1000, NULL, 2, NULL );
		
		/* 创建1个任务用于获取信号量,优先级为1 */
		xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
		
		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建信号量 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vSenderTask( void *pvParameters )
{
	int i;
	int cnt_ok = 0;
	int cnt_err = 0;
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	
	/* 无限循环 */
	for( ;; )
	{
		for (i = 0; i < 4: i++)
		{
			// 给出资源,计数值+1
			if (xSemaphoreGive(xCountingSemaphore) == pdTRUE)
				printf("Give xCountingSemaphore %d time: OK\r\n", cnt_ok++);
			else
				printf("Give xCountingSemaphore %d time: ERR\r\n", cnt_err++);
		}
		
		vTaskDelay(xTicksToWait);
	}
}

static void vReceiverTask( void *pvParameters )
{
	int cnt_ok = 0;
	int cnt_err = 0;
	
	/* 无限循环 */
	for( ;; )
	{
		// 获得资源,计数值-1
		if( xSemaphoreTake(xCountingSemaphore, portMAX_DELAY) == pdTRUE )
		{
			/* 得到二进制信号量 */
			printf("Get	xCountingSemaphore OK: %d\r\n", cnt_ok++);
		}else
		{
			/* 没有得到二进制信号量 */
			printf("Get	xCountingSemaphore ERR: %d\r\n", cnt_err++);
		}
	}
}

实验现象:

Give xCountingSemaphore 0 time: OK

Give xCountingSemaphore 1 time: OK

Give xCountingSemaphore 2 time: OK

Give xCountingSemaphore 0 time: ERR

Get    xCountingSemaphore OK: 0

Get    xCountingSemaphore OK: 1

Get    xCountingSemaphore OK: 2

Give xCountingSemaphore 3 time: OK

Give xCountingSemaphore 4 time: OK

Give xCountingSemaphore 5 time: OK

Give xCountingSemaphore 1 time: ERR

Get    xCountingSemaphore OK: 3

Get    xCountingSemaphore OK: 4

Get    xCountingSemaphore OK: 5

...

互斥量

在多任务系统中,任务A正在使用某个资源,还没用完的情况下任务B也来使用的话就有可能导致问题的出现。比如串口,任务A正使用它来打印,在打印过程中任务B也来打印,结果就是A、B的信息混在了一起。

互斥量(互斥锁)使用过程如下:

        互斥量初始值为1

        任务A想访问临界资源,先获得并占有互斥量,然后开始访问。

        任务B也想访问临界资源,也要先获得,但被别人占有互斥量,于是阻塞。

        任务A使用完毕,释放互斥量;任务B被唤醒,得到并占有互斥量,然后开始访问。

        任务B使用完毕,释放互斥量。

正常来说:在任务A占有互斥量的过程中,任务B、任务C等都无法释放互斥量。但在FreeRTOS中并未实现这点,即任务A占有互斥量的情况下,任务B也可释放互斥量。

创建互斥量

互斥量是一种特殊的二进制信号量。也分动态分配内存和静态分配内存。互斥量不能使用在ISR。

使用互斥量的过程:开始先创建、然后去获得、最后释放它。

想使用互斥量的前提是:需要在配置文件FreeRTOSConfig.h定义:#define configUSE_MUTEXES 1

/* 创建一个互斥量,返回它的句柄。
 * 此函数内部会分配互斥量结构体。
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

删除互斥量

/* xSemaphore: 信号量句柄,你要删除哪个信号量, 互斥量也是一种信号量 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

give释放/take获取

/* 释放 */
BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

/* 释放(ISR版本) */
BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );

/* 获得 */
BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

/* 获得(ISR版本) */
xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore, BaseType_t *pxHigherPriorityTaskWoken );

应用场景:互斥量基本使用

刚创建的互斥量可以被成功take。

take互斥量成功的任务被称为holder,只能由它give互斥量,别的任务give不成功。

在ISR中不能使用互斥量。

创建两个发送任务(故意发送大量的字符)。

两个实验:

        使用互斥量:可以看到任务1、任务2打印的字符串没有混杂在一起。

        不适应互斥量:任务1、任务2打印的字符串混杂在一起。

/* 互斥量句柄 */
SemaphoreHandle_t xMutex;

int main( void )
{
	prvSetupHardware();
	
	/* 创建互斥量 */
	xMutex = xSemaphoreCreateMutex( );
	if( xMutex != NULL )
	{
		/* 创建2个任务: 都是打印,优先级相同 */
		xTaskCreate( vSenderTask, "Sender1", 1000, (void *)1, 1, NULL );
		xTaskCreate( vSenderTask, "Sender2", 1000, (void *)2, 1, NULL );

		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建互斥量 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vSenderTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	int cnt = 0;
	int task = (int)pvParameters;
	int i;
	char c;
	
	/* 无限循环 */
	for( ;; )
	{
		/* 获得互斥量: 上锁 */
		xSemaphoreTake(xMutex, portMAX_DELAY);
		
		printf("Task %d use UART count: %d, ", task, cnt++);
		
		c = (task == 1 ) ? 'a' : 'A';
		
		for (i = 0; i < 26; i++)
			printf("%c", c + i);
		
		printf("\r\n");
		
		/* 释放互斥量: 开锁 */
		xSemaphoreGive(xMutex);
		
		vTaskDelay(xTicksToWait);
	}
}

实验现象:

使用take/give时,有序打印输出。

不使用take/give时,无序打印输出。

应用场景:谁上锁就由谁解锁?

创建2个任务:

        任务1:高优先级,先获得互斥锁,永远不释放。

        任务2:任务1阻塞后执行,先尝试获得互斥量,失败的话就释放互斥锁,然后再上锁。 

/* 互斥量句柄 */
SemaphoreHandle_t xMutex;

int main( void )
{
	prvSetupHardware();
	
	/* 创建互斥量 */
	xMutex = xSemaphoreCreateMutex( );
	if( xMutex != NULL )
	{
		/* 创建2个任务: 一个上锁,另一个开它锁后再上锁 */
		xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
		xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );

		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建互斥量 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	BaseType_t xStatus;
	
	/* 获得互斥量: 上锁 */
	xStatus = xSemaphoreTake(xMutex, portMAX_DELAY);
	printf("Task1 take the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
	
	/* 无限循环 */
	for( ;; )
	{
		vTaskDelay(xTicksToWait);
	}
}

static void vGiveAndTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	BaseType_t xStatus;
	
	/* 获得互斥量: 上锁 */
	xStatus = xSemaphoreTake(xMutex, 0);
	printf("Task2 take the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
	
	if(xStatus != pdTRUE)
	{
		/* 释放互斥量: 开锁 */
		xStatus = xSemaphoreGive(xMutex);
		printf("Task2 give the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");		
	}
	
	/* 获得互斥量: 上锁 */
	xStatus = xSemaphoreTake(xMutex, 0);
	printf("Task2 take the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
	
	/* 无限循环 */
	for( ;; )
	{
		vTaskDelay(xTicksToWait);
	}
}

实验现象:

Task1 take the Mutex Success

Task2 take the Mutex Failed

Task2 give the Mutex Success

Task2 take the Mutex Success

应用场景:优先级反转

优先级反转现象:

任务A优先级较低,但获得了临界资源的互斥量。

任务B也想使用临界资源,它将会阻塞去等待任务A释放互斥量。

所以现象为高优先级的任务,被低优先级的任务延迟。

互斥量可以通过优先级继承,可以很大程度解决优先级反转的问题,这也是FreeRTOS中互斥量和二进制信号链的区别。

/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
uint8_t flagLPTaskRun = 0;
uint8_t flagMPTaskRun = 0;
uint8_t flagHPTaskRun = 0;

int main( void )
{
	prvSetupHardware();
	
	/* 创建互斥量 */
	xLock = xSemaphoreCreateBinary( );
	if( xLock != NULL )
	{
		/* 创建3个任务: 低/中/高优先级 */
		xTaskCreate( vLPTask, "LPTask", 1000, NULL, 1, NULL );
		xTaskCreate( vMPTask, "MPTask", 1000, NULL, 2, NULL );
		xTaskCreate( vHPTask, "HPTask", 1000, NULL, 3, NULL );

		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建互斥量/二进制信号量 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vLPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	uint32_t i;
	char c = 'A';
	
	printf("LPTask start\r\n");
	
	/* 无限循环 */
	for( ;; )
	{
		flagLPTaskRun = 1;
		flagMPTaskRun = 0;
		flagHPTaskRun = 0;
		
		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		printf("LPTask take the Lock for long time\r\n");
		for(i = 0 ; i < 26 ; i++)
		{
			flagLPTaskRun = 1;
			flagMPTaskRun = 0;
			flagHPTaskRun = 0;
			printf("%c", c + i);	
		}
		printf("\r\n");
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
		
		vTaskDelay(xTicksToWait);
	}
}

static void vMPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );
	
	flagLPTaskRun = 0;
	flagMPTaskRun = 1;
	flagHPTaskRun = 0;
	
	printf("MPTask start\r\n");
	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{
		flagLPTaskRun = 0;
		flagMPTaskRun = 1;
		flagHPTaskRun = 0;
	}
}

static void vHPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	flagLPTaskRun = 0;
	flagMPTaskRun = 0;
	flagHPTaskRun = 1;
	
	printf("HPTask start\r\n");
	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		printf("HPTask wait for the Lock\r\n");
		
		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
	}
}

应用场景:优先级继承

上个应用场景问题在于:LPTask低优先级任务获得了锁,但是它优先级太低而无法运行。如果能提升LPTask任务的优先级,让它能尽快运行、释放锁,优先级反转的问题就可以解决。这种做法称为优先级继承。

优先级继承:

        假设持有互斥锁的是任务A,如果更高优先级的任务B也尝试获得这个锁,于是任务A就继承了任务B的优先级。

        等任务A释放互斥锁时,就恢复为原来的优先级。

        互斥锁内部就实现了优先级的提升、恢复。

在上个应用场景中把main函数的xLock = xSemaphoreCreateBinary( );替换为xLock = xSemaphoreCreateMutex( );就可。

递归锁

死锁情况一(双方):

任务A获得了互斥量M1,任务B获得了互斥量M2。

任务A还想要获得互斥量M2,任务B还想要获得互斥量M1。于是任务A、任务B都阻塞,都无法释放本身持有的互斥量。

死锁情况二(自身):

任务获得了互斥量M。任务调用了一个库函数,库函数想要获得同一个互斥量M,于是任务阻塞,无法释放本身持有的互斥量。

递归锁可解决死锁问题。特性如下:

        任务A获得递归锁M后,还可以多次去获得这个锁。

        take了N次,要giveN次,这个锁才会被释放。

递归锁互斥量
创建xSemaphoreCreateRecursiveMutexxSemaphoreCreateMutex
获得

xSemaphoreTakeRecursive

xSemaphoreTake
释放xSemaphoreGiveRecursivexSemaphoreGive
/* 创建一个递归锁,返回它的句柄。此函数内部会分配互斥量结构体。
 * 返回值: 返回句柄,非NULL表示成功
*/
SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

/* 释放 */
BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );

/* 获得 */
BaseType_t xSemaphoreTakeRecursive( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

应用场景:递归锁,谁上锁就由谁解锁

创建2个任务:

        任务1:高优先级,一开始就获得递归锁,然后故意等待很长时间,让任务2运行。

        任务2:低优先级,看看能否操作其它任务持有的锁。

/* 递归锁句柄 */
SemaphoreHandle_t xMutex;

int main( void )
{
	prvSetupHardware();
	
	/* 创建递归锁 */
	xMutex = xSemaphoreCreateRecursiveMutex( );
	if( xMutex != NULL )
	{
		/* 创建2个任务: 一个上锁, 另一个自己监守自盗(看看能否开别人的锁自己用) */
		xTaskCreate( vTakeTask, "Task1", 1000, NULL, 2, NULL );
		xTaskCreate( vGiveAndTakeTask, "Task2", 1000, NULL, 1, NULL );
		
		/* 启动调度器 */
		vTaskStartScheduler();
	} else
	{
		/* 无法创建递归锁 */
	} 
	
	/* 如果程序运行到了这里就表示出错了, 一般是内存不足 */
	return 0;
}

static void vTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	BaseType_t xStatus;
	int i;
	
	/* 无限循环 */
	for( ;; )
	{
		/* 获得互斥量: 上锁 */
		xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
		printf("Task1 take the Mutex in main loop %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
		
		vTaskDelay(xTicksToWait);
		
		for(i = 0 ; i < 10 ; i++)
		{
			/* 获得互斥量: 上锁 */
			xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
			printf("Task1 take the Mutex in main loop %s,for time %d\r\n", (xStatus == pdTRUE)? "Success" : "Failed", i);
			
			/* 释放互斥量: 开锁 */
			xStatus = xSemaphoreGiveRecursive(xMutex);
		}
		
		/* 释放互斥量: 开锁 */
		xStatus = xSemaphoreGiveRecursive(xMutex);
	}
}

static void vGiveAndTakeTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );
	BaseType_t xStatus;
	
	/* 获得互斥量: 上锁 */
	xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
	printf("Task2 take the Mutex in main loop %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
	
	if(xStatus != pdTRUE)
	{
		/* 释放互斥量: 开锁 */
		xStatus = xSemaphoreGiveRecursive(xMutex);
		printf("Task2 give the Mutex %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");		
	}
	
	/* 获得互斥量: 上锁 */
	xStatus = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
	printf("Task2 take the Mutex in main loop %s\r\n", (xStatus == pdTRUE)? "Success" : "Failed");
	
	/* 无限循环 */
	for( ;; )
	{
		vTaskDelay(xTicksToWait);
	}
}

实验现象:

Task1 take the Mutex in main loop Success

Task2 take the Mutex in main loop Failed

Task2 give the Mutex Failed        // 进入阻塞

Task1 take the Mutex in main loop Success,for time 0

...

Task1 take the Mutex in main loop Success,for time 9

Task1 take the Mutex in main loop Success

Task1 take the Mutex in main loop Success,for time 0

...

Task1 take the Mutex in main loop Success,for time 9

...

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS 中,二值信号量互斥是两种常用的同步机制。 1. 二值信号量(Binary Semaphore): 二值信号量是一种同步机制,它只有两个状态:0 和 1。当一个任务获取到二值信号量时,将其状态设置为 1,其他任务试图获取该信号量时将被阻塞,直到之前的任务释放该信号量并将其状态重新设置为 0。 在 FreeRTOS 中,可以使用 `xSemaphoreCreateBinary()` 函数创建一个二值信号量,并使用 `xSemaphoreTake()` 和 `xSemaphoreGive()` 函数来获取和释放该信号量。头文件为 `semphr.h`。 2. 互斥(Mutex): 互斥是一种用于资源保护的同步机制。它可以确保在同一时间只有一个任务能够访问被保护资源。当一个任务获取到互斥时,其他试图获取该互斥的任务将被阻塞,直到持有互斥的任务释放它。 在 FreeRTOS 中,可以使用 `xSemaphoreCreateMutex()` 函数创建一个互斥,并使用 `xSemaphoreTake()` 和 `xSemaphoreGive()` 函数来获取和释放该互斥。头文件为 `semphr.h`。 以下是一个简单的示例代码,演示了如何使用二值信号量互斥: ```cpp #include <FreeRTOS.h> #include <task.h> #include <semphr.h> // 二值信号量 SemaphoreHandle_t binarySemaphore; // 互斥 SemaphoreHandle_t mutex; // 任务1 void task1(void *pvParameters) { while (1) { // 获取二值信号量 xSemaphoreTake(binarySemaphore, portMAX_DELAY); // 执行任务1操作 // 释放二值信号量 xSemaphoreGive(binarySemaphore); // 延时或进行其他操作 vTaskDelay(pdMS_TO_TICKS(1000)); } } // 任务2 void task2(void *pvParameters) { while (1) { // 获取互斥 xSemaphoreTake(mutex, portMAX_DELAY); // 执行任务2操作 // 释放互斥 xSemaphoreGive(mutex); // 延时或进行其他操作 vTaskDelay(pdMS_TO_TICKS(1000)); } } int main() { // 创建二值信号量 binarySemaphore = xSemaphoreCreateBinary(); // 创建互斥 mutex = xSemaphoreCreateMutex(); // 创建任务1 xTaskCreate(task1, "Task 1", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 创建任务2 xTaskCreate(task2, "Task 2", configMINIMAL_STACK_SIZE, NULL, 1, NULL); // 启动 FreeRTOS 调度器 vTaskStartScheduler(); while (1) { // 主循环中可以进行其他操作或者延时 // ... } return 0; } ``` 在上述示例中,示范了如何使用二值信号量互斥来实现任务间的同步和资源保护。你可以根据需求在任务中适当地获取和释放这些同步机制。 希望这个示例对你有所帮助!如果你有更多问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值