同步与互斥(二)

发布时间:2023年12月22日

一、谁上锁就由谁解锁?

????????互斥量、互斥锁,本来的概念确实是:谁上锁就得由谁解锁。
????????但是FreeRTOS并没有实现这点,只是要求程序员按照这样的惯例写代码。
????????main函数创建了2个任务:
????????任务1:高优先级,一开始就获得互斥锁,永远不释放。
????????任务2:任务1阻塞时它开始执行,它先尝试获得互斥量,失败的话就监守自盗(释放互斥量、开锁),然后再上锁

代码如下:

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;
}

两个任务的代码和执行流程如下图所示:
A:任务1的优先级高,先运行,立刻上锁
B:任务1阻塞
C:任务2开始执行,尝试获得互斥量(上锁),超时时间设为0。根据返回值打印出:上锁失败

因为A流程那里没有解锁,所以会上锁失败,而且超时时间设置为0,不会等待,获取失败就直接返回BaseType_t,成功就返回的是pdTURE。
D:任务2监守自盗,开锁,成功!
E:任务2成功获得互斥量
F:任务2阻塞
????????可见,任务1上的锁,被任务2解开了。所以,FreeRTOS并没有实现"谁上锁就得由谁开锁"的功能。

二、优先级反转

假设任务A、B都想使用串口,A优先级比较低:
????????任务A获得了串口的互斥量
????????任务B也想使用串口,它将会阻塞、等待A释放互斥量
高优先级的任务,被低优先级的任务延迟,这被称为"优先级反转"(priority inversion)
如果涉及3个任务,可以让"优先级反转"的后果更加恶劣。
????????互斥量可以通过"优先级继承",可以很大程度解决"优先级反转"的问题,这也是FreeRTOS中互斥量和二级制信号量的差别。
????????程序使用二级制信号量来演示"优先级反转"的恶劣后果。
????????main函数创建了3个任务:LPTask/MPTask/HPTask(低/中/高优先级任务),代码如下:

/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;
int main( void )
{
    prvSetupHardware();
    /* 创建互斥量/二进制信号量 */
    xLock = xSemaphoreCreateBinary( );
    if( xLock != NULL )
    {
        /* 创建3个任务: LP,MP,HP(低/中/高优先级任务)
        */
        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;
}

LPTask/MPTask/HPTask三个任务的代码和运行过程如下图所示:
A:HPTask优先级最高,它最先运行。在这里故意打印,这样才可以观察到flagHPTaskRun的脉
冲。
B:MPTask开始运行。在这里故意打印,这样才可以观察到flagMPTaskRun的脉冲。
C:LPTask开始运行,获得二进制信号量,然后故意打印很多字符
D:HP Delay时间到,HPTask恢复运行,它无法获得二进制信号量,一直阻塞等待

//?#define portMAX_DELAY ? ? ? ? ( TickType_t ) 0xffffffffUL

查看串口打印情况更加直观:


E:MP Delay时间到,MPTask恢复运行,它比LPTask优先级高,一直运行。导致LPTask无法运
行,自然无法释放二进制信号量,于是HPTask无法运行。
总结:
????????LPTask先持有二进制信号量,但是MPTask抢占LPTask,使得LPTask一直无法运行也就无法释放信号量,导致HPTask任务无法运行,优先级最高的HPTask竟然一直无法运行!

static void vHPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 10UL );	

	flagLPTaskRun = 0;
	flagMPTaskRun = 0;
	flagHPTaskRun = 1;

	printf("HPTask start\r\n");
	
	/* 让LPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		printf("HPTask wait for Lock\r\n");
		
		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
	}
}

static void vMPTask( void *pvParameters )
{
	const TickType_t xTicksToWait = pdMS_TO_TICKS( 30UL );	

	flagLPTaskRun = 0;
	flagMPTaskRun = 1;
	flagHPTaskRun = 0;

	printf("MPTask start\r\n");
	
	/* 让LPTask、HPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 1;
		flagHPTaskRun = 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");
		for (i = 0; i < 26; i++) 
		{
			flagLPTaskRun = 1;
			flagMPTaskRun = 0;
			flagHPTaskRun = 0;
			printf("%c", c + i);
		}
		printf("\r\n");
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
		
		vTaskDelay(xTicksToWait);
	}
}

优先级继承

/* 互斥量/二进制信号量句柄 */
SemaphoreHandle_t xLock;

int main( void )
{
	prvSetupHardware();
	
    /* 创建互斥量/二进制信号量 */
    //xLock = xSemaphoreCreateBinary( );
	xLock = xSemaphoreCreateMutex( );


	if( xLock != NULL )
	{
		/* 创建3个任务: LP,MP,HP(低/中/高优先级任务)
		 */
		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");
		for (i = 0; i < 26; i++) 
		{
			flagLPTaskRun = 1;
			flagMPTaskRun = 0;
			flagHPTaskRun = 0;
			printf("%c", c + i);
		}
		printf("\r\n");
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
		printf("task_low\r\n");
		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");
	
	/* 让LPTask、HPTask先运行 */	
	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");
	
	/* 让LPTask先运行 */	
	vTaskDelay(xTicksToWait);
	
	/* 无限循环 */
	for( ;; )
	{	
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		printf("HPTask wait for Lock\r\n");
		
		/* 获得互斥量/二进制信号量 */
		xSemaphoreTake(xLock, portMAX_DELAY);
		printf("task_high\r\n");
		flagLPTaskRun = 0;
		flagMPTaskRun = 0;
		flagHPTaskRun = 1;
		
		/* 释放互斥量/二进制信号量 */
		xSemaphoreGive(xLock);
		printf("task\r\n");
	}
}

?唯一的改变就是:

运行时序图如下图所示:
A:HPTask执行xSemaphoreTake(xLock, portMAX_DELAY); ,它的优先级被LPTask继承
B:LPTask抢占MPTask,运行
C:LPTask执行xSemaphoreGive(xLock); ,它的优先级恢复为原来值
D:HPTask得到互斥锁,开始运行
互斥锁的"优先级继承",可以减小"优先级反转"的影响

?

文章来源:https://blog.csdn.net/weixin_57604904/article/details/135140512
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。