实时操作系统-8-[FreeRTOS]信号量·互斥量与递归互斥量·优先级翻转与继承实验
基本概念
互斥量又称互斥信号量(本质是信号量),是一种特殊的二值信号量,它和信号量不同的是,它支持互斥量所有权、递归访问以及防止优先级翻转的特性,用于实现对临界资源的独占式处理。
任意时刻互斥量的状态只有两种,开锁或闭锁。当互斥量被任务持有时,该互斥量处于闭锁状态,这个任务获得互斥量的所有权。当该任务释放这个互斥量时,该互斥量处于开锁状态,任务失去该互斥量的所有权。
当一个任务持有互斥量时,其他任务将不能再对该互斥量进行开锁或持有。持有该互斥量的任务也能够再次获得这个锁而不被挂起,这就是递归访问,也就是递归互斥量的特性,这个特性与一般的信号量有很大的不同,在信号量中,由于已经不存在可用的信号量,任务递归获取信号量时会发生主动挂起任务最终形成死锁。
互斥量控制块
互斥量的 API 函数实际上都是宏,它使用现有的队列机制,这些宏定义在 semphr.h 文件中,如果使用互斥量,需要包含 semphr.h 头文件 。所以 FreeRTOS 的互斥量控制块结构体与消息队列结构体是一模一样的,只不过结构体中某些成员变量代表的含义不一样而已:
/*
* Definition of the queue used by the scheduler.
* Items are queued by copy, not reference. See the following link for the
* rationale: http://www.freertos.org/Embedded-RTOS-Queues.html
*/
typedef struct QueueDefinition
{
int8_t *pcHead; /*< Points to the beginning of the queue storage area. */
int8_t *pcTail; /*< Points to the byte at the end of the queue storage area. Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
int8_t *pcWriteTo; /*< Points to the free next place in the storage area. */
union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
{
int8_t *pcReadFrom; /*< Points to the last place that a queued item was read from when the structure is used as a queue. */
UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
} u;
List_t xTasksWaitingToSend; /*< List of tasks that are blocked waiting to post onto this queue. Stored in priority order. */
List_t xTasksWaitingToReceive; /*< List of tasks that are blocked waiting to read from this queue. Stored in priority order. */
volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */
UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */
volatile int8_t cRxLock; /*< Stores the number of items received from the queue (removed from the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
volatile int8_t cTxLock; /*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked. Set to queueUNLOCKED when the queue is not locked. */
#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
uint8_t ucStaticallyAllocated; /*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
#endif
#if ( configUSE_QUEUE_SETS == 1 )
struct QueueDefinition *pxQueueSetContainer;
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxQueueNumber;
uint8_t ucQueueType;
#endif
} xQUEUE;
/* The old xQUEUE name is maintained above then typedefed to the new Queue_t
name below to enable the use of older kernel aware debuggers. */
typedef xQUEUE Queue_t;
/*-----------------------------------------------------------*/
对于12~16行,通过使用联合体,确保 pcReadFrom 与 uxRecursiveCallCount 这对互斥变量不会同时出现。
- 当结构体用于队列时,pcReadFrom 指向出队消息空间的最后一个,就是读取消息时候是从 pcReadFrom 指向的空间读取消息内容。
- 当结构体用于互斥量时,uxRecursiveCallCount 用于计数,记录递归互斥量被“调用”的次数。
union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
{
int8_t *pcReadFrom; /*< Points to the last place that a queued item was read from when the structure is used as a queue. */
UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
} u;
对于21~23行的参数:
volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */
UBaseType_t uxLength; /*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
UBaseType_t uxItemSize; /*< The size of each items that the queue will hold. */
uxMessagesWaiting:
- 如果控制块结构体是用于消息队列:uxMessagesWaiting 用来记录当前消息队列的消息个数;
- 如果控制块结构体被用于互斥量的时候,这个值就表示有效互斥量个数,这个值是 1则表示互斥量有效,如果是 0 则表示互斥量无效。
uxLength:
- 如果控制块结构体是用于消息队列:uxLength 表示队列的长度,也就是能存放多少消息;
- 如果控制块结构体被用于互斥量的时候,uxLength 表示最大的信号量可用个数,uxLength 最大为 1,因为信号量要么是有效的,要么是无效的。
uxItemSize:
- 如果控制块结构体是用于消息队列:uxItemSize 表示单个消息的大小;
- 如果控制块结构体被用于互斥量的时候,则无需存储空间,为 0 即可。
互斥量函数接口
互斥量创建函数 xSemaphoreCreateMutex()
在使用该函数前需要注意几点:
- xSemaphoreCreateMutex()用于创建一个互斥量,并返回一个互斥量句柄。该句柄的原型是一个 void 型的指针,在使用之前必须先由用户定义一个互斥量句柄。
- 需在 FreeRTOSConfig.h 中把 configUSE_MUTEXES 宏定义打开,表示使用互斥量。
- 把宏 configSUPPORT_DYNAMIC_ALLOCATION 定义为 1,即开启动态内存分配。
函数原型:
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
#endif
从 xSemaphoreCreateMutex() 函数原型就可以看出,创建互斥量其实是调用xQueueCreateMutex 函数:
#if( ( configUSE_MUTEXES == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( pxNewQueue );
return pxNewQueue;
}
#endif /* configUSE_MUTEXES */
从中我们不难看出,互斥量的创建其实也是使用 xQueueGenericCreate() 函数进行创建。对于其中的三个参数:
uxMutexLength:为 1 表示创建的队列长度为 1,其实用作互斥量就表示互斥量的最大可用个数,前面我们也提到了,任意时刻互斥量的状态只有两种,开锁或闭锁。不正是非0即1的状态吗。
uxMutexSize:值为 0,表示创建的消息空间(队列项)大小为 0,因为这个所谓的“消息队列”其实并不是用于存储 消息的,而是被用作互斥量,因为我们根本无需关注消息内容是什么,只要知道互斥量是否有效即可。
ucQueueType:表示的是创建队列的类 型,在 queue.h 中有定义,现在创建的是互斥量,其类型就是 queueQUEUE_TYPE_MUTEX。
#define queueQUEUE_TYPE_BASE ((uint8_t)0U) /* 基础队列类型 */
#define queueQUEUE_TYPE_SET ((uint8_t)0U) /* 队列集合类型 */
#define queueQUEUE_TYPE_MUTEX ((uint8_t)1U) /* 互斥锁类型 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ((uint8_t)2U) /* 计数信号量类型 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ((uint8_t)3U) /* 二进制信号量类型 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ((uint8_t)4U) /* 递归互斥锁类型 */
创建完之后再调用 prvInitialiseMutex() 函数进行初始化互斥量。
prvInitialiseMutex()源码:
#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL
#if( configUSE_MUTEXES == 1 )
static void prvInitialiseMutex( Queue_t *pxNewQueue )
{
if( pxNewQueue != NULL )
{
/* The queue create function will set all the queue structure members
correctly for a generic queue, but this function is creating a
mutex. Overwrite those members that need to be set differently -
in particular the information required for priority inheritance. */
pxNewQueue->pxMutexHolder = NULL;
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
/* In case this is a recursive mutex. */
pxNewQueue->u.uxRecursiveCallCount = 0;
traceCREATE_MUTEX( pxNewQueue );
/* Start with the semaphore in the expected state. */
( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
}
else
{
traceCREATE_MUTEX_FAILED();
}
}
#endif /* configUSE_MUTEXES */

实例使用:
首先前面我们也说了互斥量的一些API函数都存放在 semphr.h 文件中,因此需要将它包含进来:
#include "semphr.h"
然后找到 FreeRTOSConfig.h 文件,将这两个宏置1:
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
//使用互斥信号量
#define configUSE_MUTEXES 1
然后创建一个互斥量的句柄:
SemaphoreHandle_t MuxSem_Handle =NULL;
对 AppTaskCreate() 文件进行修改,调用创建函数 xSemaphoreCreateMutex() :
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
main函数完整代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Usart.h"
#include "Key.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
SemaphoreHandle_t MuxSem_Handle =NULL;
/******************************* 宏定义 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些宏定义。
*/
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
All_Function_Init();//硬件初始化
while (1)
{
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
}
}
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
//初始化声明
static void All_Function_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
//按键初始化
Key_GPIO_Config();
}
运行结果:

递归互斥量创建函数 xSemaphoreCreateRecursiveMutex()
xSemaphoreCreateRecursiveMutex()用于创建一个递归互斥量,不是递归的互斥量由函数 xSemaphoreCreateMutex() 或 xSemaphoreCreateMutexStatic()创建,且只能被同一个任务获取一次,如果同一个任务想再次获取则会失败。递归信号量则相反,它可以被同一个任务获取很多次,获取多少次就需要释放多少次。递归信号量与互斥量一样,都实现了优先级继承机制,可以降低优先级反转的危害。
xSemaphoreCreateRecursiveMutex()函数说明:

创建过程与上面也大同小异,首先添加头文件:
#include "semphr.h"
接着对宏定义进行修改:
//支持动态内存申请
#define configSUPPORT_DYNAMIC_ALLOCATION 1
//使用互斥信号量
#define configUSE_MUTEXES 1
//使用递归互斥信号量
#define configUSE_RECURSIVE_MUTEXES 1
定义一个递归互斥量句柄:
SemaphoreHandle_t xMutex =NULL;
对任务创建函数进行修改:
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
xMutex = xSemaphoreCreateRecursiveMutex();
if(NULL != xMutex)
printf("MuxSem_Handle互斥量创建成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
main完整代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Usart.h"
#include "Key.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
SemaphoreHandle_t xMutex =NULL;
/******************************* 宏定义 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些宏定义。
*/
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
All_Function_Init();//硬件初始化
while (1)
{
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
}
}
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
xMutex = xSemaphoreCreateRecursiveMutex();
if(NULL != xMutex)
printf("xMutex递归互斥量创建成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
//初始化声明
static void All_Function_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
//按键初始化
Key_GPIO_Config();
}
运行结果:

信号量删除函数 vSemaphoreDelete()
互斥量的本质是信号量,直接调用 vSemaphoreDelete()函数进行删除即可。
vSemaphoreDelete()用于删除一个信号量,包括二值信号量,计数信号量,互斥量和递归互斥量。
如果有任务阻塞在该信号量上,那么不要删除该信号量。意思就是如果有任务想要接收一个信号量的值,另一个任务你给删了,那么想要接收信号量的任务将接收不到信号量了,所以有任务想要接收信号量,不要去删除它。

删除信号量过程其实就是删除消息队列过程,因为信号量其实就是消息队列,只不过是无法存储消息的队列而已。
我们来验证一下,这里以互斥量为例,我们先使用上面互斥量的代码,在其基础上进行更改,这里主要是对任务创建函数 AppTaskCreate() 的更改,我们调用删除函数 vSemaphoreDelete(),删除互斥量的句柄 MuxSem_Handle,同时将其置为NULL,进行判断:
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
vSemaphoreDelete(MuxSem_Handle);
MuxSem_Handle = NULL;
if(NULL == MuxSem_Handle)
printf("MuxSem_Handle互斥量删除成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
运行结果:

注意,这里手动置NULL的原因是 vSemaphoreDelete() 只负责释放信号量的内存,但不会修改传入的句柄变量。必须手动将 MuxSem_Handle= NULL,否则后续误用该句柄可能导致崩溃。
递归互斥量同理。
互斥量获取函数 xSemaphoreTake()
当互斥量处于开锁的状态,任务才能获取互斥量成功,当任务持有了某个互斥量的时候,其它任务就无法获取这个互斥量,需要等到持有互斥量的任务进行释放后,其他任务才能获取成功,任务通过互斥量获取函数来获取互斥量的所有权。
任务对互斥量的所有权是独占的,任意时刻互斥量只能被一个任务持有。
如果互斥量处于开锁状态,那么获取该互斥量的任务将成功获得该互斥量,并拥有互斥量的使用权;
如果互斥量处于闭锁状态,获取该互斥量的任务将无法获得互斥量,任务将被挂起,在任务被挂起之前,会进行优先级继承,如果当前任务优先级比持有互斥量的任务优先级高,那么将会临时提升持有互斥量任务的优先级。互斥量的获取函数是一个宏定义,实际调用的函数就是xQueueGenericReceive():
#define xSemaphoreTake( xSemaphore, xBlockTime )
xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ),
NULL,
( xBlockTime ),
pdFALSE )
xQueueGenericReceive()函数想必我们都不陌生,其实就是消息队列获取函数,只不过如果是使用了互斥量的时候,这个函数会稍微有点不一样,因为互斥量本身的优先级继承机制,所以,在这个函数里面会使用宏定义进行编译,如果获取的对象是互斥量,那么这个函数就拥有优先级继承算法,如果获取对象不是互斥量,就没有优先级继承机制,下面来看看 xQueueGenericReceive 源码:
BaseType_t xQueueGenericReceive(QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait,
const BaseType_t xJustPeeking)
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = (Queue_t *)xQueue;
/* 已删除一些断言 */
for (;;) {
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* 检查队列中是否有消息 */
if (uxMessagesWaiting > (UBaseType_t)0) {
/* 防止仅仅是读取消息,而不进行消息出队操作 */
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
/* 拷贝消息到用户指定存放区域 pvBuffer */
prvCopyDataFromQueue(pxQueue, pvBuffer);
if (xJustPeeking == pdFALSE) {
/* 读取消息并且消息出队 */
traceQUEUE_RECEIVE(pxQueue);
/* 获取了消息,当前消息队列的消息个数需要减一 */
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
/* 如果系统支持使用互斥量 */
#if (configUSE_MUTEXES == 1)
{
/* 如果队列类型是互斥量 */
if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) {
/* 获取当前任务控制块 */
pxQueue->pxMutexHolder =
(int8_t *)pvTaskIncrementMutexHeldCount();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif
/* 判断消息队列中是否有等待发送消息的任务 */
if (listLIST_IS_EMPTY(&(pxQueue->xTasksWaitingToSend)) == pdFALSE) {
/* 将任务从阻塞中恢复 */
if (xTaskRemoveFromEventList(&(pxQueue->xTasksWaitingToSend)) != pdFALSE) {
/* 如果被恢复的任务优先级比当前任务高,会进行一次任务切换 */
queueYIELD_IF_USING_PREEMPTION();
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
/* 消息队列中没有消息可读 */
else {
if (xTicksToWait == (TickType_t)0) {
/* 不等待,直接返回 */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED(pxQueue);
return errQUEUE_EMPTY;
} else if (xEntryTimeSet == pdFALSE) {
/* 初始化阻塞超时结构体变量 */
vTaskSetTimeOutState(&xTimeOut);
xEntryTimeSet = pdTRUE;
} else {
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
vTaskSuspendAll();
prvLockQueue(pxQueue);
/* 检查超时时间是否已经过去 */
if (xTaskCheckForTimeOut(&xTimeOut, &xTicksToWait) == pdFALSE) {
/* 如果队列还是空的 */
if (prvIsQueueEmpty(pxQueue) != pdFALSE) {
traceBLOCKING_ON_QUEUE_RECEIVE(pxQueue);
/* 如果系统支持使用互斥量 */
#if (configUSE_MUTEXES == 1)
{
/* 如果队列类型是互斥量 */
if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) {
taskENTER_CRITICAL();
{
/* 进行优先级继承 */
vTaskPriorityInherit((void *)pxQueue->pxMutexHolder);
}
taskEXIT_CRITICAL();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif
/* 将当前任务添加到队列的等待接收列表 */
vTaskPlaceOnEventList(&(pxQueue->xTasksWaitingToReceive), xTicksToWait);
prvUnlockQueue(pxQueue);
if (xTaskResumeAll() == pdFALSE) {
/* 如果有任务优先级比当前任务高,会进行一次任务切换 */
portYIELD_WITHIN_API();
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
/* 如果队列有消息了,就再试一次获取消息 */
prvUnlockQueue(pxQueue);
(void)xTaskResumeAll();
}
} else {
/* 超时时间已过,退出 */
prvUnlockQueue(pxQueue);
(void)xTaskResumeAll();
if (prvIsQueueEmpty(pxQueue) != pdFALSE) {
/* 如果队列还是空的,返回错误代码 errQUEUE_EMPTY */
traceQUEUE_RECEIVE_FAILED(pxQueue);
return errQUEUE_EMPTY;
} else {
mtCOVERAGE_TEST_MARKER();
}
}
}
}
如果互斥量有效,调用获取互斥量函数后结构体成员变量 uxMessageWaiting 会减 1,然后将队列结构体成员指针 pxMutexHolder 指向任务控制块,表示这个互斥量被哪个任务持有,只有这个任务才拥有互斥量的所有权 ,并且该任务的控制块结构体成员 uxMutexesHeld 会加 1,表示任务已经获取到互斥量。
如果此时互斥量是无效状态并且用户指定的阻塞时间为 0,则直接返回错误码(errQUEUE_EMPTY)。
而如果用户指定的阻塞超时时间不为 0,则当前任务会因为等待互斥量有效而进入阻塞状态,在将任务添加到延时列表之前,会判断当前任务和拥有互斥量的任务优先级哪个更高,如果当前任务优先级高,则拥有互斥量的任务继承当前任务优先级,也就是我们说的优先级继承机制。
如果互斥量是有效的,获取成功后结构体成员变量 pxMutexHolder指向当前任务控制块。pvTaskIncrementMutexHeldCount()函数做了两件事,把当前任务控制块的成员变量 uxMutexesHeld 加 1,表示当前任务持有的互斥量数量,然后返回指向当前任务控制块的指针 pxCurrentTCB。
/* 如果系统支持使用互斥量 */
#if (configUSE_MUTEXES == 1)
{
/* 如果队列类型是互斥量 */
if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) {
/* 获取当前任务控制块 */
pxQueue->pxMutexHolder =
(int8_t *)pvTaskIncrementMutexHeldCount();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif
如果互斥量是无效状态,当前任务是无法获取到互斥量的,并且用户指定了阻塞时间,那么在当前任务进入阻塞的时候,需要进行优先级继承。而 vTaskPriorityInherit()函数就是进行优先级继承操作。
/* 如果系统支持使用互斥量 */
#if (configUSE_MUTEXES == 1)
{
/* 如果队列类型是互斥量 */
if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) {
taskENTER_CRITICAL();
{
/* 进行优先级继承 */
vTaskPriorityInherit((void *)pxQueue->pxMutexHolder);
}
taskEXIT_CRITICAL();
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPriorityInherit()函数源码:
#if (configUSE_MUTEXES == 1)
void vTaskPriorityInherit(TaskHandle_t const pxMutexHolder)
{
TCB_t * const pxTCB = (TCB_t *)pxMutexHolder;//获取持互斥量的任务控制块。
if (pxMutexHolder != NULL) {
/* 判断当前任务与持有互斥量任务的优先级 */
if (pxTCB->uxPriority < pxCurrentTCB->uxPriority) { //判断当前任务与持有互斥量任务的优先级,如果当前任务比持有互斥量任务的优先级高,那么需要进行优先级继承。
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();
}
/* 如果被提升优先级的任务处于就绪列表中 */
if (listIS_CONTAINED_WITHIN(&(pxReadyTasksLists[pxTCB->uxPriority]),
&(pxTCB->xStateListItem)) != pdFALSE) { //如果被提升优先级的任务处于就绪列表中,就要麻烦一点,因为如果修改了任务的优先级,那么在就绪列表中的任务也要重新排序。
/* 先将任务从就绪列表中移除 */
if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0) {
taskRESET_READY_PRIORITY(pxTCB->uxPriority); //先将任务从就绪列表中移除,待优先级继承完毕就重新插入就绪列表中。
} else {
mtCOVERAGE_TEST_MARKER();
}
/* 暂时提升持有互斥量任务的优先级,提升到与当前任务优先级一致 */
pxTCB->uxPriority = pxCurrentTCB->uxPriority;//修改持有互斥量任务的优先级,暂时提升到与当前任务优先级一致。
/* 再插入就绪列表中 */
prvAddTaskToReadyList(pxTCB);//调用 prvAddTaskToReadyList()函数将已经修改的任务优先级重新插入就绪列表,插入就绪列表会重新按照优先级进行排序。
} else {
/* 如果任务不是在就绪列表中,就仅仅是提升任务优先级即可 */
pxTCB->uxPriority = pxCurrentTCB->uxPriority;//如果持有互斥量的任务不是在就绪列表中,就仅仅是提升任务优先级即可。
}
traceTASK_PRIORITY_INHERIT(pxTCB, pxCurrentTCB->uxPriority);
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
至此,获取互斥量的操作就完成了,如果任务获取互斥量成功,那么在使用完毕需要立即释放,否则很容易造成其他任务无法获取互斥量,因为互斥量的优先级继承机制是只能将优先级危害降低,而不能完全消除。同时还需注意的是,互斥量是不允许在中断 中操作的,因为优先级继承机制在中断是无意义的。
举个例子,这里在互斥量创建代码的基础上进行修改,创建一个任务句柄:
static void HighPriority_Task(void* pvParameters);/* MidPriority_Task任务实现 */
创建一个任务主体,时间是一直等待 portMAX_DELAY :
//HighPriority_Task 任务主体
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("HighPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Runing!\r\n");
Toggle_LED_R();
vTaskDelay(1000);
}
}
这里有个Toggle_LED_R(),实际上是一个电平翻转函数,为了方便观察实验现象的:
void Toggle_LED_R(void)
{
BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));
GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}
更改 AppTaskCreate() 任务,我们先用 xSemaphoreGive() 释放互斥量这个使用方法下面讲:
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
/* 创建HighPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task, /* 任务入口函数 */
(const char* )"HighPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&HighPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建HighPriority_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
main函数代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Usart.h"
#include "Key.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t HighPriority_Task_Handle = NULL;/* HighPriority_Task任务句柄 */
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
SemaphoreHandle_t MuxSem_Handle =NULL;
/******************************* 宏定义 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些宏定义。
*/
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void HighPriority_Task(void* pvParameters);/* MidPriority_Task任务实现 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
All_Function_Init();//硬件初始化
while (1)
{
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
}
}
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
/* 创建HighPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task, /* 任务入口函数 */
(const char* )"HighPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&HighPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建HighPriority_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
//HighPriority_Task 任务主体
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("HighPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Runing!\r\n");
Toggle_LED_R();
vTaskDelay(1000);
}
}
//初始化声明
static void All_Function_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
//按键初始化
Key_GPIO_Config();
}
运行结果:

可以看出由于我们在开始的时候事先释放了互斥量,被任务获取,因此第一次互斥量获取完后能够正常运行,但是任务获取完后,没有将其释放,导致下一次在运行任务时,因为一直等待的原因,不会进行接下来的函数。
递归互斥量的获取函数 xSemaphoreTakeRecursive()
xSemaphoreTakeRecursive()是一个用于获取递归互斥量的宏,与互斥量的获取函数一样,xSemaphoreTakeRecursive()也是一个宏定义,它最终使用现有的队列机制,实际执行的函数是 xQueueTakeMutexRecursive() 。

#if (configUSE_RECURSIVE_MUTEXES == 1)
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);
/* 如果持有互斥量的任务就是当前任务 */
/* 判断一下持有递归互斥量的任务是不是当前要获取的任务,如果是,则只需要将结构体中 u.uxRecursiveCallCount 成员变量自加,表示该任务调用了多少次递归互斥量获取即可,然后返回 pdPASS,这样子就无需理会用户指定的超时时间了,效率就会很高。 */
if (pxMutex->pxMutexHolder == (void *)xTaskGetCurrentTaskHandle()) {
/* u.uxRecursiveCallCount 自加,表示调用了多少次递归互斥量获取 */
(pxMutex->u.uxRecursiveCallCount)++;
xReturn = pdPASS;
} else {
/* 如果持有递归互斥量的任务不是当前任务,就只能等待递归互斥量被释放 */
xReturn = xQueueGenericReceive(pxMutex, NULL, xTicksToWait, pdFALSE);
if (xReturn != pdFAIL) {
/* 获取递归互斥量成功,记录递归互斥量的获取次数 */
(pxMutex->u.uxRecursiveCallCount)++;
} else {
traceTAKE_MUTEX_RECURSIVE_FAILED(pxMutex);
}
}
return xReturn;
}
#endif /* configUSE_RECURSIVE_MUTEXES */
举个例子,该工程是在递归互斥量的创建函数上进行修改,首先创建一个任务句柄:
static TaskHandle_t RecursiveTask_Handle = NULL;
递归互斥量任务主体:
// 递归任务示例
static void RecursiveTask(void* parameter)
{
BaseType_t xReturn;
while(1) {
printf("\r\n尝试获取递归互斥量... \r\n");
/* 第一次获取递归互斥量 */
xReturn = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
if(xReturn == pdPASS) {
printf("第一次获取成功! \r\n");
/* 第二次递归获取 */
xReturn = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
if(xReturn == pdPASS) {
printf("第二次获取成功! \r\n");
/* 临界区操作 */
Toggle_LED_R();
vTaskDelay(500);
/* 释放第一次获取 */
xSemaphoreGiveRecursive(xMutex);
printf("释放一次! \r\n");
}
/* 释放第二次获取 */
xSemaphoreGiveRecursive(xMutex);
printf("完全释放! \r\n");
}
vTaskDelay(1000); // 延时1秒
}
}
对 AppTaskCreate() 函数进行修改:
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
xMutex = xSemaphoreCreateRecursiveMutex();
if(NULL != xMutex)
printf("xMutex递归互斥量创建成功! \r\n");
/* 创建RecursiveTask任务 */
xReturn = xTaskCreate((TaskFunction_t )RecursiveTask, /* 任务入口函数 */
(const char* )"RecursiveTask",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&RecursiveTask_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建RecursiveTask任务成功! \r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
main函数完整代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Usart.h"
#include "Key.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t RecursiveTask_Handle = NULL;
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
SemaphoreHandle_t xMutex =NULL;
/******************************* 宏定义 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些宏定义。
*/
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void RecursiveTask(void* parameter);
static void All_Function_Init(void);/* 用于初始化板载相关资源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
All_Function_Init();//硬件初始化
while (1)
{
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
}
}
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
xMutex = xSemaphoreCreateRecursiveMutex();
if(NULL != xMutex)
printf("xMutex递归互斥量创建成功! \r\n");
/* 创建RecursiveTask任务 */
xReturn = xTaskCreate((TaskFunction_t )RecursiveTask, /* 任务入口函数 */
(const char* )"RecursiveTask",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&RecursiveTask_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建RecursiveTask任务成功! \r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
// 递归任务示例
static void RecursiveTask(void* parameter)
{
BaseType_t xReturn;
while(1) {
printf("\r\n尝试获取递归互斥量... \r\n");
/* 第一次获取递归互斥量 */
xReturn = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
if(xReturn == pdPASS) {
printf("第一次获取成功! \r\n");
/* 第二次递归获取 */
xReturn = xSemaphoreTakeRecursive(xMutex, portMAX_DELAY);
if(xReturn == pdPASS) {
printf("第二次获取成功! \r\n");
/* 临界区操作 */
Toggle_LED_R();
vTaskDelay(500);
/* 释放第一次获取 */
xSemaphoreGiveRecursive(xMutex);
printf("释放一次! \r\n");
}
/* 释放第二次获取 */
xSemaphoreGiveRecursive(xMutex);
printf("完全释放! \r\n");
}
vTaskDelay(1000); // 延时1秒
}
}
//初始化声明
static void All_Function_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
//按键初始化
Key_GPIO_Config();
}
运行结果:

互斥量释放函数 xSemaphoreGive()
任务想要访问某个资源的时候,需要先获取互斥量,然后进行资源访问,在任务使用完该资源的时候,必须要及时归还互斥量,这样别的任务才能对资源进行访问。FreeRTOS 给我们提供了互斥量释放函数 xSemaphoreGive(),任务可以调用 xSemaphoreGive()函数进行释放互斥量,表示我已经用完了,别人可以申请使用,互斥量的释放函数与信号量的释放函数一致,都是调用 xSemaphoreGive()函数,但是要注意的是,互斥量的释放只能在任务中,不允许在中断中释放互斥量。
使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,当任务调用 xSemaphoreGive()函数时会将互斥量变为开锁状态,等待获取该互斥量的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级。
#define xSemaphoreGive( xSemaphore )
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ),
NULL,
semGIVE_BLOCK_TIME,
queueSEND_TO_BACK )
我们知道互斥量、信号量的释放就是调用 xQueueGenericSend()函数,但是互斥量的处理还是有一些不一样的地方,因为它有优先级继承机制,在释放互斥量的时候我们需要恢复任务的初始优先级,所以,下面我们来看看具体在哪恢复任务的优先级,其实就是 prvCopyDataToQueue()这个函数,该函数在 xQueueGenericSend()中被调用:
#if (configUSE_MUTEXES == 1)
{
if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX)
{
/* 互斥量不再被持有 */
xReturn = xTaskPriorityDisinherit((void *)pxQueue->pxMutexHolder);
pxQueue->pxMutexHolder = NULL;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1;
真正恢复任务的优先级函数其实是调用 xTaskPriorityDisinherit(),而且系统会将结构体的 pxMutexHolder 成员变量指向 NULL,表示暂时没有任务持有改互斥量,对结构体成员 uxMessagesWaiting 加 1 操作就代表了释放互斥量,表示此时互斥量是有效的,其他任务可以来获取。下面来看看 xTaskPriorityDisinherit()函数的源码:
#if (configUSE_MUTEXES == 1)
BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder)
{
TCB_t * const pxTCB = (TCB_t *)pxMutexHolder;
BaseType_t xReturn = pdFALSE;
/* 只有当有任务持有互斥量的时候,才会进行释放互斥量的操作。而且必须是持有互斥量的任务才允许释放互斥量,其他任务都没有权利去操作被任务持有的互斥量 */
if (pxMutexHolder != NULL) {
configASSERT(pxTCB == pxCurrentTCB);
configASSERT(pxTCB->uxMutexesHeld);
(pxTCB->uxMutexesHeld)--;
/* 判断优先级是否被临时提升 */
if (pxTCB->uxPriority != pxTCB->uxBasePriority) {
/* 如果任务没有持有其他互斥量 */
if (pxTCB->uxMutexesHeld == (UBaseType_t)0) {
/* 将任务从状态列表中删除无论该任务处于什么状态,因为要恢复任务的初始优先级,就必须先从状态列表中移除,待恢复初后再添加到就绪列表中,按优先级进行排序 */
if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t)0) {
taskRESET_READY_PRIORITY(pxTCB->uxPriority);
} else {
mtCOVERAGE_TEST_MARKER();
}
traceTASK_PRIORITY_DISINHERIT(pxTCB, pxTCB->uxBasePriority);
/* 恢复任务的初始优先级 */
pxTCB->uxPriority = pxTCB->uxBasePriority;
/* 重置等待事件列表的优先级 */
listSET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem),
(TickType_t)configMAX_PRIORITIES - (TickType_t)pxTCB->uxPriority);
/* 将任务重新添加到就绪列表中 */
prvAddTaskToReadyList(pxTCB);
xReturn = pdTRUE;
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
} else {
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
#endif /* configUSE_MUTEXES */
举个例子
//HighPriority_Task 任务主体
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nHighPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Runing!\r\n");
Toggle_LED_R();
printf("HighPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
vTaskDelay(1000);
}
}
此时的运行结果:

可以看出此时能够正常运行了。
递归互斥量释放函数 xSemaphoreGiveRecursive()
xSemaphoreGiveRecursive()是一个用于释放递归互斥量的宏。要想使用该函数必须在头文件 FreeRTOSConfig.h 把宏 configUSE_RECURSIVE_MUTEXES 定义为 1。
#if( configUSE_RECURSIVE_MUTEXES == 1 )
#define xSemaphoreGiveRecursive( xMutex )
xQueueGiveMutexRecursive( ( xMutex ) )
#endif
xSemaphoreGiveRecursive()函数用于释放一个递归互斥量。已经获取递归互斥量的任务可以重复获取该递归互斥量。使用 xSemaphoreTakeRecursive() 函数成功获取几次递归互斥量,就要使用 xSemaphoreGiveRecursive()函数返还几次,在此之前递归互斥量都处于无效状态,别的任务就无法获取该递归互斥量。使用该函数接口时,只有已持有互斥量所有权的任务才能释放它,每释放一次该递归互斥量,它的计数值就减 1。当该互斥量的计数值为 0 时(即持有任务已经释放所有的持有操作),互斥量则变为开锁状态,等待在该互斥量上的任务将被唤醒。如果任务的优先级被互斥量的优先级翻转机制临时提升,那么当互斥量被释放后,任务的优先级将恢复为原本设定的优先级:
#if (configUSE_RECURSIVE_MUTEXES == 1)
BaseType_t xQueueGiveMutexRecursive(QueueHandle_t xMutex)
{
BaseType_t xReturn;
Queue_t * const pxMutex = (Queue_t *)xMutex;
configASSERT(pxMutex);
/* 判断任务是否持有这个递归互斥量,判断任务是否持有这个递归互斥量,只有拥有这个递归互斥量所有权的任务才能对其进行释放操作。*/
if (pxMutex->pxMutexHolder == (void *)xTaskGetCurrentTaskHandle()) {
traceGIVE_MUTEX_RECURSIVE(pxMutex);
/* 调用次数的计数值减一 */
(pxMutex->u.uxRecursiveCallCount)--;
/* 如果计数值减到0 */
if (pxMutex->u.uxRecursiveCallCount == (UBaseType_t)0) {
/* 释放成功,需要调用一次通用入队函数 xQueueGenericSend()释放一个递归互斥量,注意了,这一步才是让递归互斥量从无效变成有效,同时系统还需要检查一下释放有任务想获取这个递归互斥量,如果有就将其恢复 */
(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;
}
#endif /* configUSE_RECURSIVE_MUTEXES */
互斥量和递归互斥量的最大区别在于一个递归互斥量可以被已经获取这个递归互斥量的任务重复获取, 而不会形成死锁。
这个递归调用功能是通过队列结构体成员u.uxRecursiveCallCount 实现的,这个变量用于存储递归调用的次数,每次获取递归互斥量后,这个变量加 1,在释放递归互斥量后,这个变量减 1。只有这个变量减到 0,即释放和获取的次数相等时,互斥量才能变成有效状态,然后才允许使用 xQueueGenericSend()函数释放一个递归互斥量。
实例演示
前面只是简单的API函数调用,下面我们结合实例来使用一下。
模拟优先级翻转实验
模拟优先级翻转实验是在 FreeRTOS 中创建了三个任务与一个二值信号量,任务分别是高优先级任务,中优先级任务,低优先级任务,用于模拟产生优先级翻转。低优先级任务在获取信号量的时候,被中优先级打断,中优先级的任务执行时间较长,因为低优先级还未释放信号量,那么高优先级任务就无法取得信号量继续运行,此时就发生了优先级翻转,任务在运行中,使用串口打印出相关信息:

① L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。
② 这个时刻 H 任务也要对该临界资源进行访问,但 L 任务还未释放资源, 由于保护机制,H 任务进入阻塞态,L任务得以继续运行,此时已经发生了优先级翻转现象。
③ 某个时刻 M 任务被唤醒,由于 M 任务的优先级高于 L 任务, M 任务抢占了 CPU 的使用权,M任务开始运行,此时 L 任务尚未执行完,临界资源还没被释放。
④ M 任务运行结束,归还 CPU 使用权,L 任务继续运行。
⑤ L 任务运行结束,释放临界资源,H 任务得以对资源进行访问,H 任务开始运行。
下面我们创建低L,中M,高H三个任务
首先,我们需要为三个任务创建任务句柄:
static void LowPriority_Task(void* pvParameters);/* LowPriority_Task任务实现 */
static void MidPriority_Task(void* pvParameters);/* MidPriority_Task任务实现 */
static void HighPriority_Task(void* pvParameters);/* MidPriority_Task任务实现 */
然后创建一个二值信号量的句柄:
SemaphoreHandle_t BinarySem_Handle =NULL;
然后创建低优先级的任务,主要完成的功能,获取信号量后执行长时间循环(模拟耗时操作),期间主动调用taskYIELD(),发起任务调度:
//LowPriority_Task任务主体
static void LowPriority_Task(void* parameter)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nLowPriority_Task 获取信号量!\r\n");
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if( xReturn == pdTRUE )
printf("LowPriority_Task Runing!\r\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用信号量
{
taskYIELD();//发起任务调度
}
printf("\r\nLowPriority_Task 释放信号量!\r\n");
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
// if( xReturn == pdTRUE )
// ; /* 什么都不做 */
Toggle_LED_R();
vTaskDelay(500);
}
}
然后创建中优先级任务,主要作用周期性输出,证明其能抢占低优先级任务:
//MidPriority_Task任务主体
static void MidPriority_Task(void* parameter)
{
while (1)
{
printf("MidPriority_Task Runing!\r\n");
vTaskDelay(500);
}
}
创建一个高优先级的任务:
//HighPriority_Task 任务主体
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nHighPriority_Task 获取信号量!\r\n");
//获取二值信号量 xSemaphore,没获取到则一直等待
xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Runing!\r\n");
Toggle_LED_R();
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
if( xReturn == pdTRUE )
printf("HighPriority_Task 释放信号量!\r\n");
vTaskDelay(500);
}
}
然后是对AppTaskCreate()函数的更改,创建一个二值信号量,分别对低中高三个任务进行配置,注意不要讲优先级给错了(FreeRTOS中数字越大优先级越高):
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建Test_Queue */
BinarySem_Handle = xSemaphoreCreateBinary();
if(NULL != BinarySem_Handle)
printf("BinarySem_Handle二值信号量创建成功!\r\n");
xReturn = xSemaphoreGive( BinarySem_Handle );//给出二值信号量
// if( xReturn == pdTRUE )
// printf("释放信号量!\r\n");
/* 创建LowPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LowPriority_Task, /* 任务入口函数 */
(const char* )"LowPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LowPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LowPriority_Task任务成功!\r\n");
/* 创建MidPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )MidPriority_Task, /* 任务入口函数 */
(const char* )"MidPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&MidPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建MidPriority_Task任务成功!\r\n");
/* 创建HighPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task, /* 任务入口函数 */
(const char* )"HighPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&HighPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建HighPriority_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
其中有个Toggle_LED_R()是LED灯电平翻转的函数,不想加删掉即可,没影响,主要是为了观察实验现象:
#define LED1_GPIO_PORT GPIOB /* GPIO端口 */
#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */
#define LED1_GPIO_PIN GPIO_Pin_5 /* 连接到SCL时钟线的GPIO */
#define LED1_OFF GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN)
void Toggle_LED_R(void)
{
BitAction LED_R = (BitAction)(1 - GPIO_ReadOutputDataBit(LED1_GPIO_PORT, LED1_GPIO_PIN));
GPIO_WriteBit(LED1_GPIO_PORT, LED1_GPIO_PIN, LED_R);
}
运行结果:

结合这张图进行看:

注意下面的①②③......是对应的串口调试助手里的:
①:任务的一些创建;
②:高优先级任务获取信号量,运行释放信号量;
③:高优先级运行完毕,中优先级任务开始运行;
④:中优先级任务运行完毕,低优先级任务开始运行,不过从这里我们需要开始注意了,此时的低优先级任务虽然获取了信号量,不过其内的任务由于运行时间较长造成了阻塞,导致信号量迟迟无法释放(这里我们通过for循环模拟的任务运行时间过长);
⑤:高优先级此时想要运行,但是由于二值信号量未被释放,导致高优先级进入阻塞,等待低优先级任务释放信号量,但是中优先级不需要信号量,中优先级的优先级又比低优先级的优先级高,因此中优先级开始运行。
⑥:低优先级释放信号量,高优先级马上抢占资源开始运行,然后释放信号量,在运行中优先级任务。
⑦:又开始②~⑥的内容。
互斥量优先级继承机制
互斥量实验是基于优先级翻转实验进行修改的,目的是为了测试互斥量的优先级继承机制是否有效。

① L 任务正在使用某临界资源, H 任务被唤醒,执行 H 任务。但 L 任务并未执行完毕,此时临界资源还未释放。
② 这个时刻 H 任务也要对该临界资源进行访问,但 L 任务还未释放资源, 由于保护机制,H 任务进入阻塞态,此时由于优先级继承机制,将 L 任务的优先级拉倒和 H 任务的优先级一样。
③ 某个时刻 M 任务被唤醒,虽然 M 优先级比最初的 L 任务的优先级高,但是,此时 L 的优先级被拉到和 H 任务的优先级一样,所以 M 处于就绪态。
④ L 任务运行结束,释放临界资源,L 任务的优先级回到最初的状态。
⑤ 此时任务 H 和 M均处于就绪态,但是 H 的优先级比 M 的高,且 L 任务已经运行结束,释放临界资源,因此 H 任务得以对资源进行访问,H 任务开始运行。
⑥ M 任务比 L 任务的优先级高,M 任务执行。
我们来测试一下,这里主要是将上面的工程二值信号量的函数换成互斥量的函数,找个空白工程,当然也能在上面工程进行修改,首先别忘了去FreeRTOSConfig.h中将有关互斥量的宏定义置为1:
//使用互斥信号量
#define configUSE_MUTEXES 1
三个任务句柄:
static TaskHandle_t LowPriority_Task_Handle = NULL;/* LowPriority_Task任务句柄 */
static TaskHandle_t MidPriority_Task_Handle = NULL;/* MidPriority_Task任务句柄 */
static TaskHandle_t HighPriority_Task_Handle = NULL;/* HighPriority_Task任务句柄 */
互斥量句柄:
SemaphoreHandle_t MuxSem_Handle =NULL;
低优先级任务,主要是更改了获取和释放函数:
//LowPriority_Task任务主体
static void LowPriority_Task(void* parameter)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nLowPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("LowPriority_Task Runing!\r\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
Toggle_LED_R();
vTaskDelay(500);
}
}
中优先级不需要就行修改:
//MidPriority_Task任务主体
static void MidPriority_Task(void* parameter)
{
printf("\r\n");
while (1)
{
printf("MidPriority_Task Runing!\r\n");
vTaskDelay(500);
}
}
高优先级也是主要更改获取和释放函数:
//HighPriority_Task 任务主体
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nHighPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Runing!\r\n");
Toggle_LED_R();
printf("HighPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
vTaskDelay(500);
}
}
任务创建:
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
// if( xReturn == pdTRUE )
// printf("释放信号量!\r\n");
/* 创建LowPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LowPriority_Task, /* 任务入口函数 */
(const char* )"LowPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LowPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LowPriority_Task任务成功!\r\n");
/* 创建MidPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )MidPriority_Task, /* 任务入口函数 */
(const char* )"MidPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&MidPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建MidPriority_Task任务成功!\r\n");
/* 创建HighPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task, /* 任务入口函数 */
(const char* )"HighPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&HighPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建HighPriority_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
完整main函数代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.h"
#include "Usart.h"
#include "Key.h"
/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/*
* 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
* 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
* 这个句柄可以为NULL。
*/
static TaskHandle_t AppTaskCreate_Handle = NULL; /* 创建任务句柄 */
static TaskHandle_t LowPriority_Task_Handle = NULL;/* LowPriority_Task任务句柄 */
static TaskHandle_t MidPriority_Task_Handle = NULL;/* MidPriority_Task任务句柄 */
static TaskHandle_t HighPriority_Task_Handle = NULL;/* HighPriority_Task任务句柄 */
/********************************** 内核对象句柄 *********************************/
/*
* 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
* 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
* 们就可以通过这个句柄操作这些内核对象。
*
* 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
* 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
* 来完成的
*
*/
SemaphoreHandle_t MuxSem_Handle =NULL;
/******************************* 宏定义 ************************************/
/*
* 当我们在写应用程序的时候,可能需要用到一些宏定义。
*/
//一些函数声明
static void AppTaskCreate(void);/* 用于创建任务 */
static void LowPriority_Task(void* pvParameters);/* LowPriority_Task任务实现 */
static void MidPriority_Task(void* pvParameters);/* MidPriority_Task任务实现 */
static void HighPriority_Task(void* pvParameters);/* MidPriority_Task任务实现 */
static void All_Function_Init(void);/* 用于初始化板载相关资源 */
int main(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
All_Function_Init();//硬件初始化
while (1)
{
/* 创建AppTaskCreate任务 */
xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任务入口函数 */
(const char* )"AppTaskCreate",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )1, /* 任务的优先级 */
(TaskHandle_t* )&AppTaskCreate_Handle);/* 任务控制块指针 */
/* 启动任务调度 */
if(pdPASS == xReturn)
vTaskStartScheduler(); /* 启动任务,开启调度 */
else
return -1;
}
}
//任务创建函数
static void AppTaskCreate(void)
{
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
taskENTER_CRITICAL(); //进入临界区
/* 创建MuxSem */
MuxSem_Handle = xSemaphoreCreateMutex();
if(NULL != MuxSem_Handle)
printf("MuxSem_Handle互斥量创建成功!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
// if( xReturn == pdTRUE )
// printf("释放信号量!\r\n");
/* 创建LowPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )LowPriority_Task, /* 任务入口函数 */
(const char* )"LowPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&LowPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建LowPriority_Task任务成功!\r\n");
/* 创建MidPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )MidPriority_Task, /* 任务入口函数 */
(const char* )"MidPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )3, /* 任务的优先级 */
(TaskHandle_t* )&MidPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建MidPriority_Task任务成功!\r\n");
/* 创建HighPriority_Task任务 */
xReturn = xTaskCreate((TaskFunction_t )HighPriority_Task, /* 任务入口函数 */
(const char* )"HighPriority_Task",/* 任务名字 */
(uint16_t )512, /* 任务栈大小 */
(void* )NULL,/* 任务入口函数参数 */
(UBaseType_t )4, /* 任务的优先级 */
(TaskHandle_t* )&HighPriority_Task_Handle);/* 任务控制块指针 */
if(pdPASS == xReturn)
printf("创建HighPriority_Task任务成功!\r\n");
vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
taskEXIT_CRITICAL(); //退出临界区
}
//LowPriority_Task任务主体
static void LowPriority_Task(void* parameter)
{
static uint32_t i;
BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nLowPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("LowPriority_Task Runing!\r\n");
for(i=0;i<2000000;i++)//模拟低优先级任务占用互斥量
{
taskYIELD();//发起任务调度
}
printf("LowPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
Toggle_LED_R();
vTaskDelay(500);
}
}
//MidPriority_Task任务主体
static void MidPriority_Task(void* parameter)
{
printf("\r\n");
while (1)
{
printf("MidPriority_Task Runing!\r\n");
vTaskDelay(500);
}
}
//HighPriority_Task 任务主体
static void HighPriority_Task(void* parameter)
{
BaseType_t xReturn = pdTRUE;/* 定义一个创建信息返回值,默认为pdPASS */
while (1)
{
printf("\r\nHighPriority_Task 获取互斥量!\r\n");
//获取互斥量 MuxSem,没获取到则一直等待
xReturn = xSemaphoreTake(MuxSem_Handle,/* 互斥量句柄 */
portMAX_DELAY); /* 等待时间 */
if(pdTRUE == xReturn)
printf("HighPriority_Task Runing!\r\n");
Toggle_LED_R();
printf("HighPriority_Task 释放互斥量!\r\n");
xReturn = xSemaphoreGive( MuxSem_Handle );//给出互斥量
vTaskDelay(500);
}
}
//初始化声明
static void All_Function_Init(void)
{
/*
* STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
* 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
* 都统一用这个优先级分组,千万不要再分组,切忌。
*/
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
/* LED 初始化 */
LED_GPIO_Config();
/* 串口初始化 */
USART_Config();
//按键初始化
Key_GPIO_Config();
}
运行结果:

还是和这张图结合着看比较好理解:

注意下面的①②③......是对应的串口调试助手里的:
①:任务的一些创建;
②:高优先级任务获取信号量,运行释放信号量;
③:高优先级运行完毕,中优先级任务开始运行;
④:中优先级运行完毕,低优先级任务开始运行,此时低优先级获取完互斥量,并未进行释放;
⑤:高优先级开始运行,获取互斥量,但是由于低优先级没有释放,因此此时高优先级将优先级继承给低优先级,此时低优先级的优先级会暂时比中优先级高,因此在等待互斥量释放期间不会再运行中优先级,而是等待释放,释放完后运行高优先级,然后运行中有优先级;
⑥:继续②~⑤的操作。

浙公网安备 33010602011771号