---------------信号量---------------
信号量的定义:
操作系统中一种解决问题的机制,可以实现 “共享资源的访问”
- 信号:起通知作用
- 量:还可以用来表示资源的数量
- 当"量"没有限制时,它就是"计数型信号量"(Counting Semaphores)
- 当"量"只有0、1两个取值时,它就是"二进制信号量"(Binary Semaphores)
- 支持的动作:"give"给出资源,计数值加1;"take"获得资源,计数值减1
give的确切含义是,释放资源“当资源使用完毕后给出资源然后计数值 + 1 ,表示有资源被释放或者是该资源变为可用状态”。
task 确切含义是“占用资源”表示当前的资源处于被使用状态计数值 -1 ,表示该资源被使用,其余任务需要访问时无法获取到该资源,处于阻塞状态。
同步与互斥:
同步的概念:
举一个例子。在团队活动里,同事A先写完报表,经理B才能拿去向领导汇报。经理B必须等同事A完 成报表,AB之间有依赖,B必须放慢脚步,被称为同步。在团队活动中,同事A已经使用会议室了,经 理B也想使用,即使经理B是领导,他也得等着,这就叫互斥。经理B跟同事A说:你用完会议室就提醒 我。这就是使用"同步"来实现"互斥"。
互斥的概念:
同一时间只能有一个人使用的资源,被称为临界资源。比如任务A、B都要使用串口来打印,串口就是临 界资源。如果A、B同时使用串口,那么打印出来的信息就是A、B混杂,无法分辨。所以使用串口时, 应该是这样:A用完,B再用;B用完,A再用。
PV操作就是实现进程同步于互斥同步的有效方法,,P表示通过的意思,V表示释放的意思
任务之间信息传递(通信或同步)最重要,最基础的一种方法,就是信号量
信号量被设置为1的过程,被称为SemaphoreGive,等待信号量设置为1的过程被称为SemaphoreTake(等待资源)
二值信号量:
本次案例基于GD32使用HAL库实现LED灯每经过一秒钟翻转一次
步骤:
1: 创建二值信号量的句柄
2:创建开始任务
3:由开始任务创建二值信号量和另外两个任务并删除开始任务
创建二值信号量的API函数
使用信号量时,先创建、然后去添加资源、获得资源。使用句柄来表示一个信号量。
信号量的创建:
使用信号量之前,要先创建,得到一个句柄;使用信号量时,要使用句柄来表明使用哪个信号量。 对于二进制信号量、计数型信号量,它们的创建函数不一样:
创建二值信号量:
1:创建句柄:
// 创建二值信号量句柄使用API函数SemaphoreHandle_t 句柄名称(自定义)
SemaphoreHandle_t Binarysemhandle
2:创建开始任务:
// 开始任务的任务句柄为TaskHandle_t Start_handle// 创建一个任务用于创建另外的两个任务xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle);
3:由开始任务创建二值信号量和另外两个任务并删除开始任务
void Start_Task(void * p){// 进入临界区taskENTER_CRITICAL();// 创建二值信号量Binary = xSemaphoreCreateBinary();// 创建两个任务xTaskCreate(Give_binarySem,"Give_binarySem",128,(void *)0,10,&BinaryGive_Handle); xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); // 删除开始任务 vTaskDelete(Start_Handle);// 退出临界区taskEXIT_CRITICAL();}
具体完整的代码如下所示
// 创建二值信号量句柄
SemaphoreHandle_t Binary;
// 宏定义创建任务句柄
TaskHandle_t Start_Handle;
TaskHandle_t BinaryGive_Handle;
TaskHandle_t BinaryTask_Handle;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}// 获取二值信号量中的值
void Give_binarySem(void *p){while(1){// 获取二值信号量资源,也就是 +1 操作,参数是信号量的句柄xSemaphoreGive(Binary);// 然后每次间隔1秒钟释放一次vTaskDelay(1000); }}
void Task_binarySem(void *p){while(1){xSemaphoreTake(Binary,portMAX_DELAY);// 调用hal函数实现led每次间隔1秒钟翻转一次HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);vTaskDelay(500);}
}void Start_Task(void * p){// 进入临界区taskENTER_CRITICAL();// 创建二值信号量Binary = xSemaphoreCreateBinary();// 创建两个任务xTaskCreate(Give_binarySem,"Give_binarySem",128,(void *)0,10,&BinaryGive_Handle); xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); // 删除开始任务 vTaskDelete(Start_Handle);// 退出临界区taskEXIT_CRITICAL();}int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// 创建一个任务用于创建另外的两个任务xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); // 开启任务调度vTaskStartScheduler();while (1){}
}
FreeRTOS在使用信号量时可能出现的BUG:
解决方式可以是添加临界区防止程序在运行中被ISR打断,或者是等待资源时(也就是使用资源时先判断资源是否被释放,释放完毕之后再使用资源)。
生产消费问题:
二值信号量模拟生产快消费慢的情况
生产快也就是GIVE释放资源的速度比较快,Task获取资源的速度比较慢,在这样的一种情况下会出现次数丢失的问题,(也就是会损失精度),代码如下所示:释放资源的代码演示100毫秒,等待资源的代码延时500毫秒,初步的实验现象是丢失精度。
void Task_binarySem(void *p){while(1){xSemaphoreTake(Binary,portMAX_DELAY);// 调用hal函数实现led每次间隔1秒钟翻转一次HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);// 翻转之后延时一段时间,得到的是信号量生产快,然后消费得慢vTaskDelay(500);}
}// 创建一个任务函数可以比较快速的释放信号量:实现100毫秒释放一次
void Give_Fast_Free(void *p){// 这是一种c99语法uint8_t count = 0;while(1){// 一直释放信号量无法直接看出结果,所以使用for循环每次间隔1毫秒释放一次if(count++ < 30){xSemaphoreGive(Binary);}vTaskDelay(100);}
}
总结:
模拟生产快,翻转慢的情况,信号量释放快获取慢
def:如果信号量释放的速度很快的话,我们得到信号量之后LED的翻转速度比较慢,导致出现丢失次数的情况
【CAT1也是一种技术】
生产快,消费慢会产生的问题会导致数据漏发,最终可能导致数据的丢失率很高也就是数据原本计划是上传10次但是实际却只上传了5次的情况【在定时任务和采集通信之间没有缓存机制,不能很好的解决生产和消费能力不匹配的问题】
计数型信号量:
def:有一个较好的缓冲机制确保数据包不会丢失太多
创建计数型信号量的API是xSemaphoreCreateCounting(255,0);,第一个参数是计数的最大值,第二个参数是起始计数的位置。
// 创建计数型信号量,第一个参数是最大值,第二个参数是起始值CountSemaphore = xSemaphoreCreateCounting(255,0);
1:创建计数型信号量的任务句柄,任务句柄是第一个,后面三个是其余任务的任务句柄
// 创建计数信号量句柄
SemaphoreHandle_t CountSemaphore;
// 宏定义创建任务句柄
TaskHandle_t Start_Handle;
TaskHandle_t BinaryGive_Handle;
TaskHandle_t BinaryTask_Handle;
2:创建开始任务函数,开始任务函数的优先级要大于其余创建函数的优先级,不然后出现其余的任务抢占开始任务,导致任务无法创建成功的情况
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// 创建一个任务用于创建另外的两个任务xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); // 开启任务调度vTaskStartScheduler();while (1){}}
3:开始任务创建函数
void Start_Task(void * p){// 进入临界区taskENTER_CRITICAL();// 创建计数型信号量,第一个参数是最大值,第二个参数是起始值CountSemaphore = xSemaphoreCreateCounting(255,0);// 创建两个任务xTaskCreate(Give_Fast_Free,"Give_Fast_Free",128,(void *)0,10,&BinaryGive_Handle); xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); // 删除开始任务 vTaskDelete(Start_Handle);// 退出临界区taskEXIT_CRITICAL();}
完整的计数型信号量创建代码
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"UART_HandleTypeDef huart1;// 创建计数信号量句柄
SemaphoreHandle_t CountSemaphore;
// 宏定义创建任务句柄
TaskHandle_t Start_Handle;
TaskHandle_t BinaryGive_Handle;
TaskHandle_t BinaryTask_Handle;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}void Task_binarySem(void *p){while(1){xSemaphoreTake(CountSemaphore,portMAX_DELAY);// 调用hal函数实现led每次间隔1秒钟翻转一次HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);// 翻转之后延时一段时间,得到的是信号量生产快,然后消费得慢vTaskDelay(500);}
}// 创建一个任务函数可以比较快速的释放信号量:实现100毫秒释放一次
void Give_Fast_Free(void *p){// 这是一种c99语法uint8_t count = 0;while(1){// 一直释放信号量无法直接看出结果,所以使用for循环每次间隔1毫秒释放一次if(count++ < 30){xSemaphoreGive(CountSemaphore);}vTaskDelay(100);}
}void Start_Task(void * p){// 进入临界区taskENTER_CRITICAL();// 创建计数型信号量,第一个参数是最大值,第二个参数是起始值CountSemaphore = xSemaphoreCreateCounting(255,0);// 创建两个任务xTaskCreate(Give_Fast_Free,"Give_Fast_Free",128,(void *)0,10,&BinaryGive_Handle); xTaskCreate(Task_binarySem,"Task_binarySem",128,(void *)0,10,&BinaryTask_Handle); // 删除开始任务 vTaskDelete(Start_Handle);// 退出临界区taskEXIT_CRITICAL();}int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();// 创建一个任务用于创建另外的两个任务xTaskCreate(Start_Task,"Start_Task",128,(void *)0,12,&Start_Handle); // 开启任务调度vTaskStartScheduler();while (1){}}
通过计数型信号量解决二值信号量可能带来的数据包丢失的问题
信号量的删除:
对于动态创建的信号量,不再需要它们时,可以删除它们以回收内存。
vSemaphoreDelete可以用来删除二进制信号量、计数型信号量,函数原型如下:
-----------------互斥锁-------------
锁的概念:
例如:文件就是典型的临界资源,windows不可能因此暂停整个任务调度,windows操作系统的做法是在第一次打开文件的时候,给文件上一把锁,如果后面还有程序想要操作文件时,会因为锁的存在,提示打开失败。
锁的机制:
例如:资源我正在使用,并且已经上锁,什么时候解锁,你什么时候再使用,RTOS中互斥锁使用Mutex。
在RTOS中引入互斥锁的概念解决临界资源的争抢问题
注:
1:可以使用临界区,防止程序被打断的方式解决临界资源争抢的问题,但是临界区会拖慢整个操作系统的运行
2:建议使用互斥锁的方式解决临界资源争抢问题,可以替代临界区并且防止操作系统卡死
临界资源与临界区的概念
临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。
每个进程中访问临界资源的那段代码称为临界区(criticalsection),每次只允许一个进程进入临界区,进入后,不允许其他进程进入。不论是硬件临界资源还是软件临界资源,多个进程必须互斥的对它进行访问。多个进程涉及到同一个临界资源的的临界区称为相关临界区。使用临界区时,一般不允许其运行时间过长,只要运行在临界区的线程还没有离开,其他所有进入此临界区的线程都会被挂起而进入等待状态,并在一定程度上影响程序的运行性能。
创建互斥锁:
1:创建互斥锁句柄
// 定义互斥锁句柄
SemaphoreHandle_t MutexSemaphore;
2: 创建开始任务
具体函数代码如下所示:
#include "task_init.h"
#include "cmsis_os.h"
extern void print1(void *p);
extern void print2(void *p);extern SemaphoreHandle_t MutexSemaphore;/* 创建任务的函数 */
void task_init( void )
{BaseType_t xReturned;TaskHandle_t xHandle = NULL;// 创建互斥锁MutexSemaphore = xSemaphoreCreateMutex();/* 创建任务,存储句柄 */xReturned = xTaskCreate(print1, /* 执行任务的函数 */"print1Task", /* 任务名称 */STACK_SIZE, /* 堆栈大小,单位为字 */( void * ) 1, /* 传递给任务的参数 */tskIDLE_PRIORITY,/* 创建任务的优先级 */&xHandle ); /* 用于传递创建的任务句柄 */xReturned = xTaskCreate(print2, /* 执行任务的函数 */"print2Task", /* 任务名称 */STACK_SIZE, /* 堆栈大小,单位为字 */( void * ) 1, /* 传递给任务的参数 */tskIDLE_PRIORITY,/* 创建任务的优先级 */&xHandle ); /* 用于传递创建的任务句柄 */vTaskStartScheduler();
}
主函数代码如下所示:
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "task_init.h"UART_HandleTypeDef huart1;// 定义互斥锁句柄
SemaphoreHandle_t MutexSemaphore;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}void print1(void *p)
{while(1){// 等待互斥锁xSemaphoreTake(MutexSemaphore,portMAX_DELAY);// 通过加入临界区解决临界资源争抢printf("HELLO RTOS! HELLO PNXUETANG! HELLO ZNMCU!\r\n");// 释放互斥锁xSemaphoreGive(MutexSemaphore);}
}void print2(void *p)
{while(1){xSemaphoreTake(MutexSemaphore,portMAX_DELAY);printf("hello rtos! hello pnxuetang! hello znmcu!\r\n");xSemaphoreGive(MutexSemaphore);}
}int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();task_init();while (1){}}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK){Error_Handler();}
}/*** @brief USART1 Initialization Function* @param None* @retval None*/
static void MX_USART1_UART_Init(void)
{/* USER CODE BEGIN USART1_Init 0 *//* USER CODE END USART1_Init 0 *//* USER CODE BEGIN USART1_Init 1 *//* USER CODE END USART1_Init 1 */huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART1_Init 2 *//* USER CODE END USART1_Init 2 */}/*** @brief GPIO Initialization Function* @param None* @retval None*/
static void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOE_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOE, GPIO_PIN_6, GPIO_PIN_SET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);/*Configure GPIO pin : PE6 */GPIO_InitStruct.Pin = GPIO_PIN_6;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);/*Configure GPIO pin : PA8 */GPIO_InitStruct.Pin = GPIO_PIN_8;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//* USER CODE BEGIN Header_StartDefaultTask */
/*** @brief Function implementing the defaultTask thread.* @param argument: Not used* @retval None*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN 5 *//* Infinite loop */for(;;){osDelay(1);}/* USER CODE END 5 */
}/*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
死锁的概念:
“互斥锁使用不当造成的问题”
例子:
1:面试要求求职的学生有工作经验
2:学生没有工作经验表示如果不入职就没有工作经验
典型的死锁关系图如下所示:
以下有三个进程:进程1占用资源A,并且向资源B申请资源,进程3占用资源B的同时申请资源C,进程2占用资源C并且申请A资源,“问题:当进程2申请资源A的时候进程1正在使用还没有释放这个时候进程2是无法使用A资源的这个时候就出现了死锁,进程之间无法获得有效的资源”
互斥锁的递归和嵌套使用导致出现死锁问题
解决死锁问题:
此处使用递归互斥锁的方式来解决死锁问题
典型的死锁代码案例演示:
使用递归互斥量解决死锁
递归互斥量的API函数:
xSemaphoreCreateRecursiveMutex();
1:创建任务句柄
2:创建开始任务函数
3:创建开始任务
4:创建函数模拟资源的递归互斥访问“死锁问题的解决”
具体主函数的代码如下所示
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;SemaphoreHandle_t MutexSemaphore;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}void myfun(void)
{//........xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY); printf("data process complete!\r\n");xSemaphoreGiveRecursive(MutexSemaphore);//.......xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);printf("result is .....\r\n");xSemaphoreGiveRecursive(MutexSemaphore);
}void print1(void *p)
{while(1){xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);//printf("HELLO RTOS! HELLO PNXUETANG! HELLO ZNMCU!\r\n");myfun(); xSemaphoreGiveRecursive(MutexSemaphore);}
}void print2(void *p)
{while(1){xSemaphoreTakeRecursive(MutexSemaphore,portMAX_DELAY);//printf("hello rtos! hello pnxuetang! hello znmcu!\r\n");myfun();xSemaphoreGiveRecursive(MutexSemaphore);}
}void task_create_entry(void *p)
{ TaskHandle_t tmp_handle; TaskHandle_t xHandle;TaskHandle_t xHandle2;vTaskDelay(5000);//=====================================printf("start to create recursive_mutex\r\n"); MutexSemaphore = xSemaphoreCreateRecursiveMutex(); printf("start to create tasks\r\n"); printf("create 1st task\r\n");xTaskCreate(print1,"print1_task",128,(void *)0,10,&xHandle);printf("create 2nd task\r\n");xTaskCreate(print1,"print2_task",128,(void *)0,10,&xHandle2);//===================================== tmp_handle = xTaskGetHandle("task_create_task");vTaskDelete(tmp_handle);
}int main(void)
{TaskHandle_t xHandle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();shell_init();xTaskCreate(task_create_entry,"task_create_task",128,(void *)0,12,&xHandle);vTaskStartScheduler();while (1){}}
----------优先级翻转问题---------
注:
1:在FreeRTOS中使用中文打印串口数据可能会出现乱码问题,打印串口的时候尽量使用英文
优先级反转:
低优先级的任务干死高优先级的任务,在任务调度的过程中,高优先级的任务总是会优先得到调度
而优先级反转,导致高优先级处于阻塞状态没法得到运行,反而低优先级的任务先于高优先级的任务得到运行。
模拟优先级反转程序如下所示“这个时候高优先级的任务无法得到调度”,使用串口打印输出程序运行的结果。
优先级反转问题:
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);// 创建一个二进制信号量
SemaphoreHandle_t BinarySum;int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}// 创建一个最低优先级的任务
void Low_Task_Entry(void *p){// 低优先级的任务不断的释放信号量while(1){printf("give low task \r\n");xSemaphoreGive(BinarySum);}}
// 配置中等优先级的任务
void Middle_Task_Entry(void *p){// 中等优先级的任务独占CPU资源,CPU的资源没法得到释放,导致高优先级的任务没法运行while(1){printf("middle task is run\r\n");}
}// 创建一个最高优先级的任务
void High_Task_Entry(void *p){while(1){// 等待信号量xSemaphoreTake(BinarySum,portMAX_DELAY);printf("high task prior is run\r\n");}
}// 创建任务函数
void Task_Create_Init(void *p){TaskHandle_t tmp_handle;TaskHandle_t xHandle1;TaskHandle_t xHandle2;TaskHandle_t xHandle3;vTaskDelay(5000);// 创建二值信号量printf("Create xSemaphoreCreateBinary");BinarySum = xSemaphoreCreateBinary();printf("Create task1");xTaskCreate(Low_Task_Entry,"Low_Task_Entry",128,(void*)0,7,&xHandle1);printf("Create task2");xTaskCreate(Middle_Task_Entry,"Middle_Task_Entry",128,(void*)0,9,&xHandle2);printf("Create task3");xTaskCreate(High_Task_Entry,"High_Task_Entry",128,(void*)0,10,&xHandle3);// 获取开始任务的任务句柄tmp_handle = xTaskGetHandle("Task_Create_Init");vTaskDelete(tmp_handle);while(1){}}
int main(void)
{TaskHandle_t Handle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();shell_init();xTaskCreate(Task_Create_Init,"Task_Create_Init",128,(void*)0,12,&Handle); // 开启任务调度vTaskStartScheduler();while (1){}}
优先级反转问题解决:
使用FreeRTOS中的API函数vTaskPrioritySet();内部包含两个参数,第一个参数是任务句柄,第二个参数是需要提高或者降低的优先级等级
出现了一个情况,更改任务优先级之后出现仅仅只有低优先级任务在运行,初步判断是由于内存的空间不足导致的,在FreeRTOSCONFIG中提高任务栈的大小“注:分配的内存空间不足会影响任务的创建”。
串口打印输出演示结果
结果显示中等优先级的任务一直占用CPU的资源,导致低优先级和高优先级的任务一直无法得到运行
main函数代码展示
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);TaskHandle_t xHandle1;
TaskHandle_t xHandle2;
TaskHandle_t xHandle3;// 创建一个二进制信号量
SemaphoreHandle_t BinarySum;int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}// 创建一个最低优先级的任务
void Low_Task_Entry(void *p){// 低优先级的任务不断的释放信号量while(1){taskENTER_CRITICAL();printf("Low Task give\r\n");taskEXIT_CRITICAL();xSemaphoreGive(BinarySum);}}
// 配置中等优先级的任务
void Middle_Task_Entry(void *p){// 中等优先级的任务独占CPU资源,CPU的资源没法得到释放,导致高优先级的任务没法运行while(1){taskENTER_CRITICAL();printf("Mid task Create\r\n");taskEXIT_CRITICAL();}
}// 创建一个最高优先级的任务
void High_Task_Entry(void *p){while(1){// 打印输出任务被创建taskENTER_CRITICAL();printf("Task is Create\r\n");taskEXIT_CRITICAL();// 临时提高低优先级任务的优先级解决优先级反转问题vTaskPrioritySet(xHandle1,10);// 等待信号量xSemaphoreTake(BinarySum,portMAX_DELAY);taskENTER_CRITICAL();printf("High Task Create\r\n");taskEXIT_CRITICAL();// 把低优先级任务的优先级降低回原来的优先级vTaskPrioritySet(xHandle1,7);}
}// 创建任务函数
void Task_Create_Init(void *p){TaskHandle_t tmp_handle;vTaskDelay(5000);// 创建二值信号量printf("Create xSemaphoreCreateBinary");BinarySum = xSemaphoreCreateBinary();printf("Create task1\r\n");xTaskCreate(Low_Task_Entry,"Low_Task_Entry",128,(void*)0,7,&xHandle1);printf("Create task2\r\n");xTaskCreate(Middle_Task_Entry,"Middle_Task_Entry",128,(void*)0,9,&xHandle2);printf("Create task3\r\n");xTaskCreate(High_Task_Entry,"High_Task_Entry",128,(void*)0,10,&xHandle3);// 获取开始任务的任务句柄tmp_handle = xTaskGetHandle("Task_Create_Init");vTaskDelete(tmp_handle);while(1){}}
int main(void)
{TaskHandle_t Handle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();shell_init();xTaskCreate(Task_Create_Init,"Task_Create_Init",128,(void*)0,12,&Handle); // 开启任务调度vTaskStartScheduler();while (1){}}
上面解决优先级反转的问题比较老土,目前主流的解决优先级反转问题的方式是互斥锁解决优先级反转,互斥锁优先级继承解决反转问题。
互斥锁解决优先级反转问题:
xSemaphoreCreateMutex // 创建互斥锁的API函数
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);TaskHandle_t xHandle1;
TaskHandle_t xHandle2;
TaskHandle_t xHandle3;// 创建一个二进制信号量
SemaphoreHandle_t Mutex;int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}// 创建一个最低优先级的任务
void Low_Task_Entry(void *p){// 低优先级的任务不断的释放信号量while(1){taskENTER_CRITICAL();printf("Low Task give\r\n");taskEXIT_CRITICAL();xSemaphoreGive(Mutex);}}
// 配置中等优先级的任务
void Middle_Task_Entry(void *p){// 中等优先级的任务独占CPU资源,CPU的资源没法得到释放,导致高优先级的任务没法运行while(1){taskENTER_CRITICAL();printf("Mid task Create\r\n");taskEXIT_CRITICAL();}
}// 创建一个最高优先级的任务
void High_Task_Entry(void *p){while(1){// 等待信号量xSemaphoreTake(Mutex,portMAX_DELAY);taskENTER_CRITICAL();printf("High Task Create\r\n");taskEXIT_CRITICAL();}
}// 创建任务函数
void Task_Create_Init(void *p){TaskHandle_t tmp_handle;vTaskDelay(5000);// 创建二值信号量printf("Create xSemaphoreCreateBinary");// 修改为互斥锁Mutex = xSemaphoreCreateMutex();printf("Create task1\r\n");xTaskCreate(Low_Task_Entry,"Low_Task_Entry",128,(void*)0,7,&xHandle1);printf("Create task2\r\n");xTaskCreate(Middle_Task_Entry,"Middle_Task_Entry",128,(void*)0,9,&xHandle2);printf("Create task3\r\n");xTaskCreate(High_Task_Entry,"High_Task_Entry",128,(void*)0,10,&xHandle3);// 获取开始任务的任务句柄tmp_handle = xTaskGetHandle("Task_Create_Init");vTaskDelete(tmp_handle);while(1){}}
int main(void)
{TaskHandle_t Handle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();shell_init();xTaskCreate(Task_Create_Init,"Task_Create_Init",128,(void*)0,12,&Handle); // 开启任务调度vTaskStartScheduler();while (1){}}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;RCC_OscInitStruct.HSIState = RCC_HSI_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL2;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK){Error_Handler();}
}/*** @brief USART1 Initialization Function* @param None* @retval None*/
static void MX_USART1_UART_Init(void)
{/* USER CODE BEGIN USART1_Init 0 *//* USER CODE END USART1_Init 0 *//* USER CODE BEGIN USART1_Init 1 *//* USER CODE END USART1_Init 1 */huart1.Instance = USART1;huart1.Init.BaudRate = 115200;huart1.Init.WordLength = UART_WORDLENGTH_8B;huart1.Init.StopBits = UART_STOPBITS_1;huart1.Init.Parity = UART_PARITY_NONE;huart1.Init.Mode = UART_MODE_TX_RX;huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;huart1.Init.OverSampling = UART_OVERSAMPLING_16;if (HAL_UART_Init(&huart1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN USART1_Init 2 *//* USER CODE END USART1_Init 2 */}/*** @brief GPIO Initialization Function* @param None* @retval None*/
static void MX_GPIO_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOE_CLK_ENABLE();__HAL_RCC_GPIOA_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOE, GPIO_PIN_6, GPIO_PIN_SET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);/*Configure GPIO pin : PE6 */GPIO_InitStruct.Pin = GPIO_PIN_6;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);/*Configure GPIO pin : PA8 */GPIO_InitStruct.Pin = GPIO_PIN_8;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);}/* USER CODE BEGIN 4 *//* USER CODE END 4 *//* USER CODE BEGIN Header_StartDefaultTask */
/*** @brief Function implementing the defaultTask thread.* @param argument: Not used* @retval None*/
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void const * argument)
{/* USER CODE BEGIN 5 *//* Infinite loop */for(;;){osDelay(1);}/* USER CODE END 5 */
}/*** @brief This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}#ifdef USE_FULL_ASSERT
/*** @brief Reports the name of the source file and the source line number* where the assert_param error has occurred.* @param file: pointer to the source file name* @param line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
-------------任务主动切换---------
在RTOS中优先级相同的任务使用“时间片轮转”的方式进行任务调度,每个任务之间轮流的执行,不停的进行任务间上下文的切换。
上下文切换的概念:
本质上就是压栈和出栈,就是Push操作和Pop操作,如有两个任务,一个是任务A,一个是任务B
当任务A向任务B进行切换时,任务A的函数(操作)会被pop出栈,放进一个寄存器中进行存储
同时当前执行的位置会被记录下来,任务A此时处于阻塞状态,任务B在寄存器中的函数操作或者是地址会被PUSH进入栈中,任务B等到运行,时间片轮转,上下文的切换往复循环。
时间片轮状slice ---------> 一个切片就是一个Tick,Tick是系统提供的心跳节拍,来自于滴答定时器
注:频繁的切换上下文不利于操作系统的稳定运行
主动任务切换概念:
FreeRTOS中使用taskYeild()函数实现任务的主动切换,【taskYeild】主动任务切换,在多个任务优先级相同的情况下,为了更好的使用和调度CPU资源,通过主动任务切换的方式提高任务的运行效率,调用taskYeild这个API来实现主动任务切换。
例如:
系统中的空闲任务IDLE,当系统重有任务运行时会主动的让出CPU的资源
操作系统的实时性:一般情况下任务的切换是到最终切换完成时有一定的时间间隔的“相当于有了一个缓冲”
注:“两个任务优先级相同的情况下,相互之间不会去抢占做上下文的切换,任务的切换是在一个TICK中断中完成的”
主动山下文切换:
“
1: 在两个任务优先级有高有低的时候,B任务是8,A任务是7,在实时抢占式任务的内核下,仍然会为我们快速的切换上下文**(任务之间的切换是非常快的)**
2:在任务优先级相同的情况下,可以调用taskYIELD()主动上下文切换主动的放弃CPU的资源
”
主动上下文切换代码main.c
#include "main.h"
#include "cmsis_os.h"#include "stdio.h"
#include "math.h"
#include "shell.h"UART_HandleTypeDef huart1;void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);// 创建二进制信号量
SemaphoreHandle_t sem;int fputc(int ch,FILE *f)
{while((USART1->SR & 0X40) == 0);USART1->DR = (uint8_t)ch;return ch;
}void MyDelay(uint32_t num){while(num--){}
}
void taskSem_entry(void *p){while(1){// 释放信号量xSemaphoreTake(sem,portMAX_DELAY);// 翻转第一个LED灯HAL_GPIO_TogglePin(GPIOA,GPIO_PIN_8);}
}// 等待信号量的释放
void giveSem_entry(void *p){while(1){xSemaphoreGive(sem);HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_6);/*两个任务主动的切换上下文,可以调用taskYIELD();主动让出CPU资源完成任务的上下文切换*/ taskYIELD();MyDelay(1000000);}
}void task_create_entry(void *p){// 创建任务句柄TaskHandle_t tmp_handle;TaskHandle_t xHandle;TaskHandle_t xHandle2;vTaskDelay(5000);printf("start to create task\r\n");sem = xSemaphoreCreateBinary();printf("start to create task\r\n");printf("create 1st task\r\n");xTaskCreate(taskSem_entry,"taskSem_entry",128,(void *)0,10,&xHandle);// 创建两个任务优先级相同的情况下,调度器就不会主动的为我们切换上下文 xTaskCreate(giveSem_entry,"giveSem_entry",128,(void *)0,10,&xHandle2); // 通过中间变量获取开始任务的句柄tmp_handle = xTaskGetHandle("task_create_entry");vTaskDelete(tmp_handle);
}int main(void)
{TaskHandle_t Handle;HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();shell_init();xTaskCreate(task_create_entry,"task_create_entry",128,(void*)0,12,&Handle); // 开启任务调度vTaskStartScheduler();while (1){}}
.........
注:以上内容均基于本人对相关知识的理解进行撰写,如有错误欢迎评论区纠正