我们在stm32f103c8t6单片机上验证RTOS互斥量有效解决优先级反转现象,利用stm32cube进行RTOS的配置。裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms 定时,用于任务调度),所以我们开启TIM2当做裸机的时钟源,为其他总线提供另外的时钟源。
验证的功能比较简单,选择V1 版本的内核完全够用。
1.验证思路
做两组实验进行对比,一组用二值型信号量,另一组用互斥型信号量,看一下有优先级反转和无优先级反转的情况区别
2.需用函数
二值型信号量
建立二值信号量SemaphoreHandle_t xSemaphoreCreateBinary( void )
返回值: 成功,返回对应二值信号量的句柄; 失败,返回 NULL
获取二值信号量BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
xSemaphore:要获取的信号量句柄
xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;
返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL 。
释放二值信号量BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )
xSemaphore:要释放的信号量句柄
返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL 。
互斥型信号量
建立互斥型信号量SemaphoreHandle_t xSemaphoreCreateMutex( void )
返回值: 成功,返回对应互斥量的句柄; 失败,返回 NULL
互斥型信号量信号的获取与释放所用的函数同二值型信号量
一样,只不过函数的参数不一样,即句柄不一样
3.互斥量的说明
互斥型信号量可以有效解决优先级反转现象。可发生优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任 务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务 会将低优先级任务的优先级提升到与自己相同的优先级。 优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。
听不懂没关系,我给你讲故事。
现在有High,Middle,Low三个人,我们分别用H,M,L来代替。其中H的优先级最高,M其次,L最低。H的工作就是在整洁的床上睡觉,M的工作就是闲的没事在H醒来之后打酱油,L的工作就是负责给H整理床铺。
下面我就用这个故事情节给大家说一下有无翻转优先级的区别
有翻转优先级的时候
按照优先级的排序,故事开始应该是这样发展的。H开始睡觉,睡了一会,H醒了走人。M过来打一会酱油,走了。接着L开始为H铺床,这个回合结束。按道理说H应该要睡觉了,但是L却没有把床铺好,因此H无法睡觉,只能等着不工作。这个时候L应该早点把床整理好,让H睡觉。但是M偏偏就是这么浪,嫌弃L整理的时间太长,自己的优先级又比L高,H又不工作,没人管它,M就欺负L,让L无法工作,自己又去打酱油。他打酱油了,L就无法工作了。那么L就只能等着M浪完在工作,别看此时H等级高,也只能等着M浪完,L把床叠好后在休息。
发现了吗?在使用二值信号量并没有使用互斥信号量的时候,本来应该正常工作的L却因M的优先级比自己高,而不能工作。在我们的执行流程中,就相当于低优先级的工作时间还没结束,便被翻转为中优先级,这就是典型的优先级翻转。
SYS
RCC
RTOS版本选择
在Times and Semapores里
选择Binary semaphores(二值信号),生成二值信号配置如下图所示
在Task and Queues里
配置三个任务
分别是TaskHigh,TaskMiddle,TaskLow,
对应的优先级分别是osPriorityAboveNormal, osPriorityNormal, osPriorityBelowNormal,
对应的入口函数分别是StartTaskHigh,StartTaskMiddle,StartTaskLow
usart.c
#include "stdio.h"
int fputc(int ch, FILE *f)
{
unsigned char temp[1]={ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
同时打开“魔术棒”,勾选Use MicroLIB,点击OK。这样就可以进行串口打印了。
freertos.c
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
/* USER CODE END Variables */
osThreadId TaskHighHandle;
osThreadId TaskMiddleHandle;
osThreadId TaskLowHandle;
osSemaphoreId myBinarySemHandle;
/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */
/* USER CODE END FunctionPrototypes */
void StartTaskHigh(void const * argument);
void StartTaskMiddle(void const * argument);
void StartTaskLow(void const * argument);
void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
/* GetIdleTaskMemory prototype (linked to static allocation support) */
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );
/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */
static StaticTask_t xIdleTaskTCBBuffer;
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )
{
*ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;
*ppxIdleTaskStackBuffer = &xIdleStack[0];
*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
/* place for user code */
}
/* USER CODE END GET_IDLE_TASK_MEMORY */
/**
* @brief FreeRTOS initialization
* @param None
* @retval None
*/
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* USER CODE BEGIN RTOS_MUTEX */
/* add mutexes, ... */
/* USER CODE END RTOS_MUTEX */
/* Create the semaphores(s) */
/* definition and creation of myBinarySem */
osSemaphoreDef(myBinarySem);
myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);
/* USER CODE BEGIN RTOS_SEMAPHORES */
/* add semaphores, ... */
/* USER CODE END RTOS_SEMAPHORES */
/* USER CODE BEGIN RTOS_TIMERS */
/* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* definition and creation of TaskHigh */
osThreadDef(TaskHigh, StartTaskHigh, osPriorityAboveNormal, 0, 128);
TaskHighHandle = osThreadCreate(osThread(TaskHigh), NULL);
/* definition and creation of TaskMiddle */
osThreadDef(TaskMiddle, StartTaskMiddle, osPriorityNormal, 0, 128);
TaskMiddleHandle = osThreadCreate(osThread(TaskMiddle), NULL);
/* definition and creation of TaskLow */
osThreadDef(TaskLow, StartTaskLow, osPriorityBelowNormal, 0, 128);
TaskLowHandle = osThreadCreate(osThread(TaskLow), NULL);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
}
/* USER CODE BEGIN Header_StartTaskHigh */
/**
* @brief Function implementing the TaskHigh thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskHigh */
void StartTaskHigh(void const * argument)
{
/* USER CODE BEGIN StartTaskHigh */
/* Infinite loop */
for(;;)
{
xSemaphoreTake(myBinarySemHandle, portMAX_DELAY);
printf("TaskH:我要睡觉了。\r\n");
HAL_Delay(1000);
//osDelay(1000);
printf("TaskH:我醒了,我走了。\r\n");
xSemaphoreGive(myBinarySemHandle);
osDelay(1000);
}
/* USER CODE END StartTaskHigh */
}
/* USER CODE BEGIN Header_StartTaskMiddle */
/**
* @brief Function implementing the TaskMiddle thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskMiddle */
void StartTaskMiddle(void const * argument)
{
/* USER CODE BEGIN StartTaskMiddle */
/* Infinite loop */
for(;;)
{
printf("TaskM:啦啦啦,我来打酱油了。\r\n");
osDelay(1000);
}
/* USER CODE END StartTaskMiddle */
}
/* USER CODE BEGIN Header_StartTaskLow */
/**
* @brief Function implementing the TaskLow thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_StartTaskLow */
void StartTaskLow(void const * argument)
{
/* USER CODE BEGIN StartTaskLow */
/* Infinite loop */
for(;;)
{
xSemaphoreTake(myBinarySemHandle, portMAX_DELAY);
printf("TaskL:我要整理床了。\r\n");
HAL_Delay(3000);
//osDelay(1000);
printf("TaskL:床铺整理好了。\r\n");
xSemaphoreGive(myBinarySemHandle);
osDelay(1000);
}
/* USER CODE END StartTaskLow */
}
运行的结果如下图所示,和我们的故事是吻合的
无翻转优先级的时候
按照优先级的排序,故事开始应该是这样发展的。H开始睡觉,睡了一会,H醒了走人。M过来打一会酱油,走了。接着L开始为H铺床,这个回合结束。按道理说H应该要睡觉了,但是L却没有把床铺好,因此H无法睡觉,只能等着不工作。这个时候L应该早点把床整理好,让H睡觉。但是M偏偏就是这么浪,嫌弃L整理的时间太长,自己的优先级又比L高,H又不工作,没人管它,M就想着欺负L,让L无法工作,但这一次,虽然H不工作却很严肃的对M说:“L现在在为我工作,看见L就相当看见我,它的优先级和我一样高”。这时候,就发生了优先级继承,M不敢惹L,L依旧可以工作,直到把床铺好,让H上床休息。L铺床和H上床之间是没有M横插一杠的。
发现了吗?在使用互斥信号量的时候,本来应该正常工作的L可以正常工作,不会因为M的优先级比自己高,而不能工作。在我们的执行流程中,就相当于低优先级的工作时间结束后才自动转到别的优先级,这就是没有发生优先级翻转。
四、无优先级防转时STM32cube的配置
首先把二值信号量删除,接着创建互斥量,其余的不变
五、无优先级防转时的代码
把xSemaphoreGive(myBinarySemHandle)和xSemaphoreTake(myBinarySemHandle)里面的参数换成myMutexHandle就可以了。
下图是无优先级防转时运行的情况,可以看出L在运行时并没有遭到M的打断,发生了优先级继承,和我们的故事情况一样。