FreeRtos内核源码分析(五)——信号量

     

目录

一、信号量简介

1.1 信号分类

1.2 信号量接口

二、二值信号

2.1 结构体

2.2 源码及功能分析

2.3 应用实例

2.3.1 同步功能(二值信号)

2.3.2 互斥功能(二值信号)

三、计数信号

3.1 结构体

3.2 源码及功能分析

3.3 应用实例

3.3.1 资源竞争 

3.3.2 多任务同步(或逻辑)

3.3.2 多任务同步(与逻辑)

四、互斥信号

4.1 结构体

4.2 源码及功能分析

4.3 优先级继承

4.3.1 优先级自动翻转

4.3.2 优先级继承机制

4.3.3 优先级继承源码分析

4.4 应用实例

五、递归互斥信号

5.1 结构体

5.2 源码及功能分析

5.3 优先级继承

5.4 应用实例

六、信号量使用常见问题

6.1 死锁问题

6.2 饥饿问题

6.3 优先级翻转问题

6.4 逻辑复杂问题


本章基于FreeRtos V9.0.0版本分析

         在学习本章节前需要先理解《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》章节,因为信号量的创建、获取、释放都是基于队列的功能实现的。

一、信号量简介

        信号量是一种任务间通信的机制,是一种特殊的队列(没有队员),主要实现任务之间同步、临界区的互斥访问。

1.1 信号分类

分类结构简介(参考Queue_t结构体)适用场合
计数信号

特殊队列(没有队员),

(1)最大长度uxLength表示最大资源数;

(2)队员大小uxItemSize为0;

(3)uxMessagesWaiting表示剩余资源数。

(1)多任务同步。

(2)多资源竞争。

二值信号

特殊计数信号(最大资源数固定1),

(1)最大长度uxLength为1;

(2)队员大小uxItemSize为0;

(3)uxMessagesWaiting表示二值状态,非0即1。

(1)互斥功能(可能会有优先级翻转)。

(2)同步功能。

互斥信号

特殊二值信号(支持优先级继承机制),

(1)最大长度uxLength为1;

(2)队员大小uxItemSize为0;

(3)uxMessagesWaiting表示解闭锁(1-解锁;0-闭锁);

(4)pxMutexHolder表示当前持有信号的任务。

互斥功能(具备优先级继承机制,不具备递归调用机制;强行递归调用会死锁)
递归互斥信号

特殊互斥信号(支持递归),

(1)最大长度uxLength为1;

(2)队员大小uxItemSize为0;

(3)uxMessagesWaiting表示解闭锁(1-解锁;0-闭锁);

(4)pxMutexHolder表示当前持有信号的任务;

(5)uxRecursiveCallCount表示递归次数。

互斥功能(具备优先级继承机制,具备递归调用机制)

1.2 信号量接口

        信号量接口统一通过宏定义设置,在文件semphr.h中设置。

        信号创建接口:信号创建有2种方式,静态创建和动态创建,与队列创建类似。静态创建即控制块通过全局变量存储,不能释放的;动态创建的控制块为动态内存,可以生灭;本章我们仅以动态创建为例进行分析。

/*---------------------------信号创建接口(动态)-----------------------------*/
// 创建二值信号(动态)
#define vSemaphoreCreateBinary( xSemaphore )																							
#define xSemaphoreCreateBinary()
// 创建计数信号(动态)
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) 
// 创建互斥信号(动态)
#define xSemaphoreCreateMutex()
// 创建递归互斥信号(动态)
#define xSemaphoreCreateRecursiveMutex() 

/*---------------------------信号创建接口(静态)-----------------------------*/
// 创建二值信号(静态)
#define xSemaphoreCreateBinaryStatic( pxStaticSemaphore )
// 创建计数信号(静态)
#define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxSemaphoreBuffer ) 
// 创建互斥信号(静态)
#define xSemaphoreCreateMutexStatic( pxMutexBuffer ) 
// 创建递归互斥信号(静态)
#define xSemaphoreCreateRecursiveMutexStatic( pxStaticSemaphore ) 

信号操作接口:信号创建完成,便可以通过操作接口进行互斥、和同步实现。

/*---------------------------获取信号接口-----------------------------*/
// 获取信号(线程)
#define xSemaphoreTake( xSemaphore, xBlockTime )
// 获取信号(中断)
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )
// 获取递归互斥信号
#define xSemaphoreTakeRecursive( xMutex, xBlockTime )

/*---------------------------释放信号接口-----------------------------*/
// 释放信号(线程)
#define xSemaphoreGive( xSemaphore )
// 释放递归互斥信号
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )
// 释放递归互斥信号
#define xSemaphoreGiveRecursive( xMutex )
/*--------------------------- 删除信号接口----------------------------*/
#define vSemaphoreDelete( xSemaphore )

/*--------------------------- 获取互斥信号拥有者----------------------*/
#define xSemaphoreGetMutexHolder( xSemaphore )

/*--------------------------- 获取信号个数----------------------------*/
#define uxSemaphoreGetCount( xSemaphore )

二、二值信号

2.1 结构体

二值信号是一种没有队员的队列,即仅有队列控制块,uxLength固定为1,控制块中的读写指针、存储指针均指向控制块本身,uxMessagesWaiting表示当前二值状态,非0即1。具备信号释放和获取的阻塞任务链表,但在实际使用中,只有信号获取会被阻塞。

2.2 源码及功能分析

信号创建:信号创建调用了队列创建函数xQueueGenericCreate,创建出的队列没有队员,最大长度为1,类型为queueQUEUE_TYPE_BINARY_SEMAPHORE 二值信号。针对互斥、同步分别设置接口。

#define vSemaphoreCreateBinary( xSemaphore ) :初始化并释放信号,uxMessagesWaiting=1,可用于互斥的场合。

#define vSemaphoreCreateBinary( ):仅初始化信号,uxMessagesWaiting=0,返回句柄,用于同步的场合。

关于函数xQueueGenericCreate的具体分析,请参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。

// 二值信号创建,状态初始化为1,用于互斥
#define vSemaphoreCreateBinary( xSemaphore )																							\
	{																																	\
	    ( xSemaphore ) = xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE );	\
	    if( ( xSemaphore ) != NULL )																									\
	    {																																\
		    ( void ) xSemaphoreGive( ( xSemaphore ) );																					\
	    }																																\
    }
// 二值信号创建,状态初始化为0,用于同步
#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

信号获取:信号获取支持线程和中断模式,通过调用队列接收函数xQueueGenericReceive和xQueueReceiveFromISR实现,接收队员为空;接收成功后uxMessagesWaiting减1,函数返回成功;

        任务获取接口,如果uxMessagesWaiting==0,任务阻塞,阻塞时间xBlockTime 可设;如果超期仍不成功,返回失败。

        中断获取接口,如果uxMessagesWaiting==0,无阻塞,返回失败。

        关于函数xQueueGenericSend和xQueueReceiveFromISR的具体分析,请参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。

// 获取信号(线程)
#define xSemaphoreTake( xSemaphore, xBlockTime ) xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
// 获取信号(中断)
#define xSemaphoreTakeFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueReceiveFromISR( ( QueueHandle_t ) ( xSemaphore ), NULL, ( pxHigherPriorityTaskWoken ) )

信号释放:信号释放支持线程和中断模式,通过调用队列发送函数xQueueGenericSend和xQueueGiveFromISR实现,发送方式固定为FIFO,队员为空,如果发送成功,uxMessagesWaiting增加为1。如果uxMessagesWaiting为0,不阻塞直接跳过。

        信号释放无需考虑阻塞的问题,一方面是使用过程中在获取信号(即uxMessagesWaiting==0)后才会调用信号释放xSemaphoreGive,另一方面,即便调用xSemaphoreGive出现失败也关系不大。

        函数xQueueGiveFromISR与xQueueGenericSendFromISR类似,只是xQueueGiveFromISR固定发送方式为FIFO,并且增加了互斥信号和信号条目大小的断言,其他逻辑完全一致。

        关于函数xQueueGenericSend和xQueueGenericSendFromISR的具体分析,参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。

// 释放信号(线程)
#define xSemaphoreGive( xSemaphore ) xQueueGenericSend((QueueHandle_t) (xSemaphore),NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
// 释放信号(中断)
#define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken )	xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )

2.3 应用实例

2.3.1 同步功能(二值信号)

例如:任务A完成全局数据更新后,任务B再进行全局数据解析,任务B优先级高于任务A。

// 同步信号
SemaphoreHandle_t semSyn;
// 数据
int data[100];
// 任务A对数据进行采集
void taskA()
{
	for(;;)
	{
		// 更新采集数据data[100]
		.......
		// 释放二值信号
		xSemaphoreGive( semSyn)
	}
}
// 任务B对数据进行解析
void taskB()
{
    for(;;)
	{
	    // 获取二值信号,获取不到阻塞100个时间片
	    if(xSemaphoreTake( semSyn,100 )==pdPASS)
	    {
		    // 解析采集数据data[100]
		    .......
	    }
    }

}
void main()
{
	// 新建二值信号,默认二值状态为0
	semSyn=vSemaphoreCreateBinary( );
	// 新建任务A
	
	// 新建任务B
	
	//启动调度器
}

2.3.2 互斥功能(二值信号)

例如:任务A和任务B均需要读写全局数据data[]。

// 同步信号
SemaphoreHandle_t semSyn;
// 数据
int data[100];
// 任务A对数据进行操作
void taskA()
{
	for(;;)
	{
		// 获取二值信号,获取不到阻塞200个时间片
	    if(xSemaphoreTake( semSyn ,200 )==pdPASS)
	    {
		    // 读写数据data[100]
		    .......
            xSemaphoreGive( semSyn )
	    }
	}
}
// 任务B对数据进行操作
void taskB()
{
	for(;;)
	{
		// 获取二值信号,获取不到阻塞100个时间片
	    if(xSemaphoreTake( semSyn ,100 )==pdPASS)
	    {
		    // 读写数据data[100]
		    .......
            xSemaphoreGive( semSyn )
	    }
	}

}
void main()
{
	// 新建二值信号,默认二值状态为1
	vSemaphoreCreateBinary(semSyn);
	// 新建任务A
	
	// 新建任务B
	
	//启动调度器
}

三、计数信号

3.1 结构体

        计数信号是一种没有队员的队列,即仅有队列控制块,控制块中的读写指针、存储指针均指向控制块本身;uxLength在初始化时可设,表示最大资源数;uxMessagesWaiting表示当前剩余的资源数,在信号获取和释放中会发生增减,该值小于等于uxLength。结构体具备信号释放和获取的阻塞任务链表,但在实际使用中,只有信号获取会被阻塞。

3.2 源码及功能分析

 信号创建:信号创建调用了队列创建函数xQueueCreateCountingSemaphore或xQueueCreateCountingSemaphoreStatic,创建出的队列没有队员,最大长度为可设(根据资源数量设置),类型为queueQUEUE_TYPE_COUNTING_SEMAPHORE计数信号。

#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) :uxMaxCount表示最大资源数,uxInitialCount表示当前剩余资源数。

关于函数xQueueGenericCreate的具体分析,参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。


// 创建计数信号(动态)
#define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
// 创建计数信号(静态)
#define xSemaphoreCreateCountingStatic( uxMaxCount, uxInitialCount, pxSemaphoreBuffer ) xQueueCreateCountingSemaphoreStatic( ( uxMaxCount ), ( uxInitialCount ), ( pxSemaphoreBuffer ) ) 

// 创建计数量信号函数
QueueHandle_t xQueueCreateCountingSemaphore( 
                        const UBaseType_t uxMaxCount,     // 最大资源数
                        const UBaseType_t uxInitialCount )// 剩余资源数
{
	QueueHandle_t xHandle;
    // 【1】断言
	configASSERT( uxMaxCount != 0 );
	configASSERT( uxInitialCount <= uxMaxCount );
    // 【2】新建队列,空队员,最大长度uxLength= uxMaxCount ;队列类型为queueQUEUE_TYPE_COUNTING_SEMAPHORE计数信号
	xHandle = xQueueGenericCreate( uxMaxCount, queueSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_COUNTING_SEMAPHORE );

    // 【3】赋值剩余资源uxMessagesWaiting= uxInitialCount 
	if( xHandle != NULL )
	{
		( ( Queue_t * ) xHandle )->uxMessagesWaiting = uxInitialCount;

		traceCREATE_COUNTING_SEMAPHORE();
	}
	else
	{
		traceCREATE_COUNTING_SEMAPHORE_FAILED();
	}

	return xHandle;
}

        信号释放信号获取【二、二值信号】章节代码一致,不过常用于多任务

3.3 应用实例

3.3.1 资源竞争 

例如:任务A、任务B、中断1均需要通过data()获取资源,但是data资源只有2组。

// 计数信号
SemaphoreHandle_t semSyn;
// 数据
extern int data();
// 任务A竞争数据
void taskA()
{
	for(;;)
	{
		// 占有资源,如果没有资源,阻塞100时间片
	    if(xSemaphoreTake( semSyn,100 )==pdPASS)
	    {
		    // 竞争数据data()
		    .......
            // 释放资源
            xSemaphoreGive( semSyn )

	    }
	}
}
// 任务B竞争数据
void taskB()
{
    for(;;)
	{
	    // 占有资源,如果没有资源,阻塞100时间片
	    if(xSemaphoreTake( semSyn,100 )==pdPASS)
	    {
		    // 竞争数据data()
		    .......
            // 释放资源
            xSemaphoreGive( semSyn )

	    }
    }

}
// 中断1竞争数据
interrupt int1()
{
    for(;;)
	{
	    // 占有资源
	    if(xSemaphoreTakeFromISR( semSyn,NULL)==pdPASS)
	    {
		    // 竞争数据data()
		    .......
            // 释放资源
            xSemaphoreGiveFromISR( semSyn ,NULL)

	    }
    }

}
void main()
{
	// 新建计数信号,默认资源数为2,当前剩余资源数为2
	semSyn=xSemaphoreCreateCounting(2,2);
	// 新建任务A
	
	// 新建任务B
	
	//启动调度器
}

3.3.2 多任务同步(或逻辑)

例1:任务A需要等待任务B、任务C中任意一个采集完数据data()后才能进行处理,任务A优先级高于任务B、任务C。

// 计数信号
SemaphoreHandle_t semSyn;
// 数据
extern int dataB();
extern int dataC();
// 任务A处理数据
void taskA()
{
	for(;;)
	{
		// 占有资源,如果没有资源,阻塞100时间片
	    if(xSemaphoreTake( semSyn,100 )==pdPASS)
	    {
		    // 处理数据dataB()、dataC()
		    .......
	    }
	}
}
// 任务B数据采集
void taskB()
{
    for(;;)
	{
		// 采集数据dataB()
		 .......
         // 释放资源
         xSemaphoreGive( semSyn )
    }

}
// 任务C数据采集
void taskA()
{
	for(;;)
	{
	     // 采集数据dataC()
	     .......
         // 释放资源
         xSemaphoreGive( semSyn )
	}
}
void main()
{
	// 新建计数信号,最大资源数为2,当前剩余资源数为0
	semSyn=xSemaphoreCreateCounting(2,0);
	// 新建任务A
	.....
	// 新建任务B
    .....
    // 新建任务C
    .....
	//启动调度器
    .....
}

3.3.2 多任务同步(与逻辑)

 例1:任务A需要等待任务B和任务C均采集完数据data()后才能进行处理,任务A优先级高于任务B、任务C。

// 计数信号
SemaphoreHandle_t semSyn;
// 数据
extern int dataB();
extern int dataC();
// 任务A处理数据
void taskA()
{
	for(;;)
	{
        // 判断资源数量
	    if(uxSemaphoreGetCount(semSyn)==2)
	    {
		    // 处理数据dataB()、dataC()
		    .......
            xSemaphoreTake(semSyn,0);
            xSemaphoreTake(semSyn,0);
	    }
        taskDelay();
	}
}
// 任务B数据采集
void taskB()
{
    for(;;)
	{
		// 采集数据dataB()
		 .......
         // 释放资源
         xSemaphoreGive( semSyn )
    }

}
// 任务C数据采集
void taskC()
{
	for(;;)
	{
         // 采集数据dataC()
	      .......
         // 释放资源
         xSemaphoreGive( semSyn );
	}
}
void main()
{
	// 新建计数信号,最大资源数为2,当前剩余资源数为0
	semSyn=xSemaphoreCreateCounting(2,0);
	// 新建任务A
	.....
	// 新建任务B
    .....
    // 新建任务C
    .....
	//启动调度器
    .....
}

四、互斥信号

4.1 结构体

        互斥信号是一种特殊二值信号,uxLength固定为1,uxMessagesWaiting表示当前二值状态,非0即1。比二值信号多pxMutexHolder(当前持有信号的任务)、uxRecursiveCallCount(递归次数)。并且控制块头地址设置为空,用于与其他信号区分。

4.2 源码及功能分析

 信号创建:信号创建调用了队列创建函数xQueueCreateMutex或xQueueCreateMutexStatic,创建出的队列没有队员,最大长度为1,类型为queueQUEUE_TYPE_MUTEX互斥信号。创建完成初始化互斥信号结构:

         互斥结构体头地址:pcHead=NULL,用于与其他队列和信号区分。

        互斥信号最大长度:uxLength=1;

        互斥信号持有任务:pxMutexHolder=NULL;

        互斥信号递归计数:uxRecursiveCallCount=0;

        互斥信号当前状态:uxMessagesWaiting=1;默认解锁。

关于函数xQueueGenericCreate和xQueueGenericSend的具体分析,参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。


// 创建互斥信号(动态)
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
// 创建互斥信号(静态)
#define xSemaphoreCreateMutexStatic( pxMutexBuffer ) xQueueCreateMutexStatic( queueQUEUE_TYPE_MUTEX, ( pxMutexBuffer ) ) 

// 创建互斥信号函数(动态)
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
	Queue_t *pxNewQueue;
	const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
    // 【1】创建队列,空队列,最大长度为1,队列类型为互斥信号
	pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
    // 【2】初始化互斥信号结构体
	prvInitialiseMutex( pxNewQueue );
	return pxNewQueue;
}

static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
	if( pxNewQueue != NULL )
	{
		pxNewQueue->pxMutexHolder = NULL;              // 初始化信号持有任务
		pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; // 即头部pcHead初始化为NULL
		pxNewQueue->u.uxRecursiveCallCount = 0;        // 递归计数初始化为0
		traceCREATE_MUTEX( pxNewQueue );
		// 信号状态uxMessagesWaiting初始化为1
		( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );/*从预期状态的信号量开始。*/
	}
	else
	{
		traceCREATE_MUTEX_FAILED();
	}
}

        信号释放信号获取2.2章节一致,但会涉及优先级继承逻辑

4.3 优先级继承

           假设任务H、任务M、任务L;优先级由高到低:H>M>L。       

4.3.1 优先级自动翻转

         任务H、任务L使用二值信号进行互斥时, 任务H因等待任务L释放信号而出现阻塞,但是任务L由于优先级低的原因,可能会被任务M打断,造成不能及时释放信号;从而导致任务H长时间阻塞。

        实质上是变相将任务H的优先级拉低到了与任务L一致,任务H优先级出现翻转,这样不符合实时系统的设计理念。 

4.3.2 优先级继承机制

         为了避免这种现象,互斥信号增加优先级继承机制,任务H因等待任务L释放信号而出现阻塞时,将任务L优先级强制为与任务H一致,这样可以和任务H轮询执行,不会被任务M打断;当任务L释放信号后,再将任务L恢复成原来的优先级。

4.3.3 优先级继承源码分析

        任务控制块(TCB)中与互斥信号相关和优先级继承相关的变量。

    UBaseType_t uxPriority;           /*< 任务当前的优先级。 0优先级最低*/
	// 互斥量功能
    #if ( configUSE_MUTEXES == 1 )
        UBaseType_t uxBasePriority;   /*< 任务初始化优先级,用于取消优先级继承,恢复原始优先级 */
        UBaseType_t uxMutexesHeld;    /*< 互斥信号持有个数*/
    #endif

        假设任务H、任务L;优先级:H>L。

(1)任务获取互斥信号成功后,会更新互斥信号持有者,更新信号持有任务为当前任务,同时任务累计任务控制块(TCB->uxMutexesHeld)持有信号计数。

比如:任务L成功获取互斥信号,任务L互斥信号持有数量uxMutexesHeld累加,互斥信号的持有者pxMutexHolder指向任务L句柄。 

void *pvTaskIncrementMutexHeldCount( void )
{
	// 如果在任何任务创建之前调用xSemaphoreCreateMutex()那么pxCurrentTCB将为NULL
	if( pxCurrentTCB != NULL )
	{
        // 累计任务持有信号计数器
		( pxCurrentTCB->uxMutexesHeld )++;
	}

	return pxCurrentTCB;
}

(2)高优先级任务获取互斥信号阻塞后,信号持有任务(低优先级)进行优先级继承处理。

比如:互斥信号被任务L持有,任务H获取互斥信号阻塞;由于任务L优先级小于任务H优先级,会进行优先级继承,将任务L优先级设置为与任务H一致。 

注意:如果多个互斥信号在任务L存在嵌套获取,且有多个高优先级任务因互斥被阻塞,则任务L互斥信号持有数量uxMutexesHeld与成功获取的信号个数一致;任务L继承的优先级取被阻塞任务最高的优先级。

// 优先级继承(当前任务被低优先级任务阻塞),pxMutexHolder互斥信号持有任务
void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
	if( pxMutexHolder != NULL )
	{
		// 【1】当前运行任务pxCurrentTCB优先级高与信号持有任务优先级
		if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
		{
			// 【2】信号持有任务事项条目未使用,更新信号持有任务事项条目优先级
			if(( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL ) //判断事件值设置位置
				{
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
				/*【3.1】如果信号持有任务优先级处于就绪状态,则需要将其移除、修改、移入。*/
				if( listIS_CONTAINED_WITHIN(&( pxReadyTasksLists[ pxTCB->uxPriority ] ),&( pxTCB->xStateListItem ) ) != pdFALSE )
				{
                    // 【3.1.1】从原始优先级就绪链表移除
					if( uxListRemove(&(pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						taskRESET_READY_PRIORITY( pxTCB->uxPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}
					// 【3.1.2】重置优先级
					pxTCB->uxPriority = pxCurrentTCB->uxPriority;
					// 【3.1.3】添加至高优先级就绪链表
					prvAddTaskToReadyList( pxTCB );
				}
				// 【3.2】非就绪状态只修改优先级
				else
				{
					pxTCB->uxPriority = pxCurrentTCB->uxPriority;
				}

				traceTASK_PRIORITY_INHERIT( pxTCB, pxCurrentTCB->uxPriority );
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}
	}

(3)信号持有任务释放互斥信号,进行优先级继承恢复处理。

比如:任务H获取互斥信号阻塞,并且信号持有任务L进行了优先级翻转;此时由于任务L完成了操作,进行互斥信号释放完成后;会执行优先级恢复操作,任务L恢复为它原来的优先级uxBasePriority。 

注意:如果由多个互斥信号在任务L存在嵌套问题,且有多个高优先级任务因互斥被阻塞,则任务L互斥信号持有数量uxMutexesHeld与成功获取的信号个数一致;且任务L翻转的优先级取被阻塞任务最高的优先级,只有在最后一个互斥信号释放后,任务L的优先级才会恢复,并强制上下文切换。

// 取消优先级继承,pxMutexHolder 互斥信号持有任务
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
	BaseType_t xReturn = pdFALSE;
	if( pxMutexHolder != NULL )
	{
			// 【1】断言,持有者为当前任务
			configASSERT( pxTCB == pxCurrentTCB );
			configASSERT( pxTCB->uxMutexesHeld );
			// 【2】当前任务持有数减少
			( pxTCB->uxMutexesHeld )--;
			
			/*【3】互斥对象的持有者是否继承了另一个任务的优先级?*/
			if( pxTCB->uxPriority != pxTCB->uxBasePriority )
			{
				/*【3.1】只有当没有其他互斥锁被持有时才取消继承 */
				if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
				{
					// 【3.2】移除状态条目(从就绪链表)
					if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{
						taskRESET_READY_PRIORITY( pxTCB->uxPriority );
					}
					else
					{
						mtCOVERAGE_TEST_MARKER();
					}

					/*【3.3】恢复优先级. */
					traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
					pxTCB->uxPriority = pxTCB->uxBasePriority;
					//【3.4】重置事件列表项值。如果这个任务正在运行,它不能用于任何其他目的,并且它必须正在运行才能返回互斥锁
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority );
                    //【3.5】恢复就绪
					prvAddTaskToReadyList( pxTCB );
					
					/*返回true,表示需要进行上下文切换。这实际上只需要在一个角落的情况下,
					即持有多个互斥锁,并且互斥锁以不同于获取它们的顺序返回。如果在返回第一个互斥锁时没有发生上下文切换,即使任务正在等待它,
					那么在返回最后一个互斥锁时应该发生上下文切换,无论任务是否正在等待它。*/
					xReturn = pdTRUE;
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		return xReturn;
	}

4.4 应用实例

       注意:互斥信号的获取与释放必然要成对出现,获取成功后,进行临界访问,访问完成一定要释放。

 例如:任务A和任务B均需要读写全局数据data[]。

// 互斥信号
SemaphoreHandle_t semSyn;
// 数据
int data[100];
// 任务A对数据进行采集
void taskA()
{
	for(;;)
	{
		// 获取互斥信号,获取不到阻塞200个时间片
	    if(xSemaphoreTake( semSyn ,200 )==pdPASS)
	    {
		    // 读写数据data[100]
		    .......
            xSemaphoreGive( semSyn )
	    }
	}
}
// 任务B对数据进行解析
void taskB()
{
	for(;;)
	{
		// 获取互斥信号,获取不到阻塞100个时间片
	    if(xSemaphoreTake( semSyn ,100 )==pdPASS)
	    {
		    // 读写数据data[100]
		    .......
            xSemaphoreGive( semSyn )
	    }
	}

}
void main()
{
	// 新建互斥信号,默认解锁状态
	semSyn=xSemaphoreCreateMutex();
	// 新建任务A
	
	// 新建任务B
	
	//启动调度器
}

五、递归互斥信号

        递归互斥信号与互斥信号唯一不一样的地方就是多了递归处理。

        互斥信号对同一个信号的获取和释放必须成对出现,没释放前,不能重复获取,否则会死锁。  

        递归互斥信号可以重复获取,通过参数uxRecursiveCallCount记录获取次数,获取次数与释放次数必须一致(即进行了多少次成功获取,就必须进行多少次释放)。

        互斥信号适用于比较简单明了的代码中;在结构复杂、模块化的代码中,递归互斥信号更加安全。

5.1 结构体

        与互斥信号一致,参考【四、互斥信号】章节。

5.2 源码及功能分析

信号创建:信号创建调用了队列创建函数xQueueCreateMutex或xQueueCreateMutexStatic,与互斥信号调用函数一致,创建出的队列没有队员,最大长度为1,类型为queueQUEUE_TYPE_RECURSIVE_MUTEX递归互斥信号。

// 创建递归信号(动态)
#define xSemaphoreCreateRecursiveMutex() xQueueCreateMutex( queueQUEUE_TYPE_RECURSIVE_MUTEX )
// 创建递归信号(静态)
#define xSemaphoreCreateRecursiveMutexStatic( pxStaticSemaphore ) xQueueCreateMutexStatic( queueQUEUE_TYPE_RECURSIVE_MUTEX, pxStaticSemaphore )

 信号获取:递归信号获取为独立接口xSemaphoreTakeRecursive( xMutex, xBlockTime ),任务第一次成功获取信号处理方式与互斥信号处理一样,只是多了递归计数(uxRecursiveCallCount)累加处理。当任务持有者重复获取信号时,递归计数(uxRecursiveCallCount)累加,并获取成功。其他非任务持有者获取信号仍然会被阻塞。

        关于函数xQueueGenericReceive的具体分析,参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。

// 获取递归信号
#define xSemaphoreTakeRecursive( xMutex, xBlockTime ) xQueueTakeMutexRecursive( ( xMutex ), ( xBlockTime ) )

// 获取递归互斥信号
BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
{
	BaseType_t xReturn;
	Queue_t * const pxMutex = ( Queue_t * ) xMutex;
	configASSERT( pxMutex );
	traceTAKE_MUTEX_RECURSIVE( pxMutex );
	// 【1】任务持有者重复调用。
	if( pxMutex->pxMutexHolder == ( void * ) xTaskGetCurrentTaskHandle() )
	{
		( pxMutex->u.uxRecursiveCallCount )++;
		xReturn = pdPASS;
	}
    // 【2】非任务持有者调用
	else
	{ 
		xReturn = xQueueGenericReceive( pxMutex, NULL, xTicksToWait, pdFALSE );
		if( xReturn != pdFAIL )
		{
			( pxMutex->u.uxRecursiveCallCount )++;
		}
		else
		{
			traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
		}
	}

	return xReturn;
}

信号释放:递归信号释放为独立接口xSemaphoreGiveRecursive( xMutex, xBlockTime ),当多次获取递归互斥信号后,任务持有者重复获取信号时,递归计数(uxRecursiveCallCount)递减,并释放成功。最后第一次释放信号处理方式与互斥信号释放处理一样,只是多了递归计数(uxRecursiveCallCount)递减。

        由此可见,递归信号的获取和释放次数必须一致。

        关于函数xQueueGenericSend的具体分析,参考 《基于STM32F103ZE平台分析FreeRtos(四)——消息队列》。

// 释放递归信号
#define xSemaphoreGiveRecursive( xMutex ) xQueueGiveMutexRecursive( ( xMutex ) )

// 释放互斥信号阻塞的接收任务	
BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
{
	BaseType_t xReturn;
	Queue_t * const pxMutex = ( Queue_t * ) xMutex;
	configASSERT( pxMutex );
	// 【1】信号持有任务释放,
	if( pxMutex->pxMutexHolder == ( void * )xTaskGetCurrentTaskHandle() )
	{
		traceGIVE_MUTEX_RECURSIVE( pxMutex );
        //【2】 递归计数自减
		( pxMutex->u.uxRecursiveCallCount )--;
		//【3】最后一次释放,才能真正释放信号
		if( pxMutex->u.uxRecursiveCallCount == ( UBaseType_t ) 0 )
		{
			/*返回互斥对象。这将自动解除可能等待访问互斥锁的任何其他任务的阻塞*/
			( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
		}
		else
		{
			mtCOVERAGE_TEST_MARKER();
		}

		xReturn = pdPASS;
	}
	else
	{
		xReturn = pdFAIL;
		traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
	}
	return xReturn;
}

5.3 优先级继承

        与互斥信号一致,参考【四、互斥信号】章节。

5.4 应用实例

 注意:递归互斥信号的获取与释放次数必须一致,即进行了多少次成功获取,就必须进行多少次释放。

 例如:任务A和任务B均通过接口读写全局数据data[],接口模块比较独立,自带互斥锁。

// 递归互斥信号
SemaphoreHandle_t semSyn;
// 数据
int data[100];


void setData()
{
	if(xSemaphoreTakeRecursive( semSyn ,200 )==pdPASS)
    {
		 // 读写数据data[100]
		    .......
         xSemaphoreGiveRecursive( semSyn )
	}
}

// 任务A对数据进行采集
void taskA()
{
	for(;;)
	{
		// 获取互斥信号,获取不到阻塞200个时间片
	    if(xSemaphoreTakeRecursive( semSyn ,200 )==pdPASS)
	    {
		    setData();
            xSemaphoreGiveRecursive( semSyn )
	    }
	}
}
// 任务B对数据进行解析
void taskB()
{
	for(;;)
	{
		// 获取互斥信号,获取不到阻塞100个时间片
	    if(xSemaphoreTakeRecursive( semSyn ,100 )==pdPASS)
	    {
		    
            setData();
            xSemaphoreGiveRecursive( semSyn )
	    }
	}

}
void main()
{
	// 新建递归互斥信号,默认解锁状态(uxMessagesWaiting=1)
	semSyn=xSemaphoreCreateRecursiveMutex();
	// 新建任务A
	
	// 新建任务B
	
	//启动调度器
}

六、信号量使用常见问题

6.1 死锁问题

        互斥信号强行递归:当一个任务已经获取互斥信号时,再次调用获取互斥信号接口,将因为互斥信号无法释放而永远获取失败,  这种情况应当使用递归互斥信号实现互斥功能。

// 互斥信号
SemaphoreHandle_t semSyn;
semSyn=xSemaphoreCreateMutex();
void taskA()
{
	for(;;)
	{
		// 获取互斥信号,获取不到阻塞200个时间片
	    if(xSemaphoreTake( semSyn ,200 )==pdPASS)
	    {
		    .......
            // 死锁
	        if(xSemaphoreTake( semSyn ,200 )==pdPASS)
	        {
		        .......
                xSemaphoreGive( semSyn )
	        }
            xSemaphoreGive( semSyn )
	    }
	}
}

任务相互同步死锁:

        任务A与任务B相互同步,如下代码,由于二值信号semSynA、semSynB均不能释放,任务A、B均会死锁。像这种情况,在初始化时先解锁一个信号(比如初始化调用xSemaphoreGive( semSynA)),便可以正常运行。

// 同步信号
SemaphoreHandle_t semSynA;
SemaphoreHandle_t semSynB;
semSynA=vSemaphoreCreateBinary( );
semSynB=vSemaphoreCreateBinary( );
// 任务A对数据进行采集
void taskA()
{
	for(;;)
	{
	    if(xSemaphoreTake( semSynB,100 )==pdPASS)
	    {
		   xSemaphoreGive( semSynA)
	    }
	}
}
// 任务B对数据进行解析
void taskB()
{
    for(;;)
	{
	    if(xSemaphoreTake( semSynA,100 )==pdPASS)
	    {
		   xSemaphoreGive( semSynB)
	    }
	}

}

6.2 饥饿问题

        抢占式调度中,高优先级任务阻塞时间设置不合理,或者直接无阻塞,会导致低优先级任务长期无法运行,高优先级任务也无法获取信号。如下所示,任务A优先级高于任务B。

// 同步信号
SemaphoreHandle_t semSyn;
semSyn=vSemaphoreCreateBinary( );
// 任务A对数据进行采集
void taskA()
{
	for(;;)
	{
	    if(xSemaphoreTake( semSyn,0 )==pdPASS)
	    {
		   xSemaphoreGive( semSyn)
	    }
	}
}
// 任务B对数据进行解析
void taskB()
{

	xSemaphoreGive( semSyn)

}

6.3 优先级翻转问题

        采用二值信号实现任务间互斥功能,如果涉及的任务优先级种类比较多。 高优先级任务因等待低优先级任务释放信号而出现阻塞,实质上是变相将高优先级的优先级拉低,优先级出现翻转。

        这种情况应当使用互斥信号实现互斥功能。

6.4 逻辑复杂问题

       当信号量使用较多时,互斥和同步逻辑将会变的十分复杂。这时候需要改变一下代码架构来避免逻辑问题。

        我们可以通过统一接口的方法减少信号接口调用:对临界区访问进行保护,通过统一接口访问临界区,在临界区接口中统一增加互斥锁。各个任务只需要调用接口即可。对资源申请,通过统一接口调配。  当程序模块比较多时,互斥锁尽量使用递归互斥信号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿来不是梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值