FreeRTOS笔记篇:第三章 -- 任务管理(调度器)

    科技2024-06-24  91

    目录

    调度器分类总结总结(调度器工作原理)测试环境如下概述读者void ATaskFunction( void *pvParameters )创建任务任务创建案例一 任务一任务二各自创建任务创建案例二 任务一中创建任务二任务创建案例三 使用xTaskCreate第4个参数任务创建优先级设定(任务一般不超过32个)通常方法设定结构优化方法 时间管理和嘀嗒中断任务优先级案例一 修改数字同步事件可能会阻塞状态暂停状态(挂起状态)+ 就绪状态任务延迟 vTaskDelay()任务延迟 vTaskDelayUntil()空闲任务和空闲任务钩子任务优先级案例一 使用函数删除任务函数 vTaskDelete()调度算法--轮询调度算法抢占调度+时间切片模式抢占调度(无时间切片)协作式调度

    调度器分类总结

    FreeRTOS调度有3种, 第一种:抢占式+时间切片

    configUSE_PREEMPTION = 1configUSE_TIME_SLICING = 1 该状态为抢占式调度+时间切片

    时间切片又有宏configIDLE_SHOULD_YIELD定义 configIDLE_SHOULD_YIELD = 0 空闲任务和优先级相同的任务0 交替使用 占空比为(50%) configIDLE_SHOULD_YIELD = 1 空闲任务会让出时间,时间会给任务优先级相同的任务(90以上%)

    第二种:抢占式(无时间切片)

    configUSE_PREEMPTION = 1configUSE_TIME_SLICING = 0 抢占方式和上述方式相同 空闲任务和优先级相同的任务0 不会交替使用 (除非是更高级的任务打断,重新调度)

    第三种:合作式(无时间切片) (手动切换)方式

    configUSE_PREEMPTION = 0configUSE_TIME_SLICING = 任何值

    只有当运行状态任务进入阻塞状态时才会发生任务切换*,或者运行状态任务通过**手动请求重新调度taskYIELD().**任务永远不会被抢占,所以不能使用时间切片。

    总结(调度器工作原理)

    那么下面说人话: 村里有个姑娘叫小花(CPU),村里有很多小伙子都在追求她,小花她妈(任务调度器),每天都要去选小伙子(任务),选中的小伙子会被允许跟小花交往一天(任务占用CPU一个心跳周期),小花她妈选小伙子的标准是: 第一,小伙子要喜欢小花(任务处于就绪态)。 第二,小伙子要是所有小花追求者中条件最好的(就绪态任务中处于最高优先级)。 第三,如果有多个喜欢小花的小伙子条件一样好,就让他们轮流,一人跟小花交往一天(相同优先级的任务,任务调度器会让每个任务执行一个心跳周期,轮流执行)。 小伙子中有一个叫小绿的,特别喜欢小花,人又帅,条件又好,小花他妈一眼就看上了,小绿因为没有敌手,所以每天都能跟小花交往,同村里条件不好的小伙组,见小绿天天霸占着小花,殉情了(低优先级的任务被饿死),小绿见局势不妙,决定先去避一避,等过一段时间再回来(进入阻塞态),他对小花她妈谎称自己不喜欢小花了,由于小花他妈选择小伙子的标准的第一条就是小伙子要喜欢小花(任务必须处于就绪态)。所以小花妈就开始从别的追求小花的小伙子中选条件最好的(在就绪态的任务里选择最高优先级的任务)。过了一段时间,小绿见自己天天被绿,他决定复出(从阻塞态重返就绪态),这天绿小绿的小黄在跟小花逛街,被刚复出的小绿撞见,可小绿此时没办法,只能等小花他妈第二天来评评理(等待下一个心跳周期任务调度器仲裁),因为小花他妈一天只出现一次(每个心跳周期任务调度器运行一次),第二天,小花他妈见小绿回心转意,条件还那么好,就让他继续跟小花交往,这次小绿学聪明了,每隔一段时间就跟小花妈说自己不喜欢小花了,然后出去歇一阵子再回来(循环进入阻塞态),这样大家都还有机会可以跟小花交往,同村的小伙子们不会想殉情(高低优先级的任务都可以使用CPU资源,低优先级任务不会被饿死),直到有一天,头上已经是青青草原的小绿实在受不了了,他决定从此隐居山林(进入挂起态),除非有人去找他(调用resume函数唤醒),否则他再也不见小花了(挂起态的任务对任务调度器来说不可见)。同村的小伙子见小绿都这样了,也就收敛了很多,甚至有时候,小花他妈都找不到小伙子,但小花没有人陪着聊天咋办呢?那只找来门口的二傻(空闲任务)陪小花,二傻子从小就一直喜欢小花(空闲任务除了在运行态就在就绪态),二傻并不傻,他也会像其他小伙子一样做很多事情(空闲任务中也可以写一用户自己的函数(称为空闲任务钩子函数)),但他的条件太差了(优先级最低,为0),只要有小伙子比二傻条件好点,来追求小花,小花他妈就会让二傻走(只要该任务优先级高于0,空闲任务就会被抢占)。有一天又来了一个三傻,跟二傻条件差不多(用户自己的任务也可以跟空闲任务共享优先级:0),又没有其他小伙子来追求小花,小花他妈就让三傻跟跟二傻轮流陪小花聊天。 这里再补充一点,小花妈并不会凭主观判断哪个小伙子的条件好不好,因为村子里从古至今都流传着一个法则,每个小伙子都会有一个数字,这个数字越高,条件越好,这个数字就由掌管世界的神秘人来掌握,神秘人觉得谁条件好就把谁的数字调高,所以神秘人为了让小花和小伙子们相处和睦,必须谨慎分配这个数字,需要洞悉小花什么时候需要什么(根据任务的要紧程度,调整任务优先级),这是一件让人头疼的工作,毕竟洞悉女孩子的想法是很难的事。

    测试环境如下

    stm32F103C8T6 MDK keil5 stm32cube + FreeRTOS

    概述

    FreeRTOS 如何将处理时间分配给应用程序中的每个任务FreeRTOS 选择在任何给定的时间应该执行哪个任务。每个任务的相对优先级如何影响系统行为。任务可以存在的状态。

    读者

    如何落实任务如何创建任务的一个或多个实例。如何使用任务参数。如何更改已经创建的任务的优先级。如何删除任务如何使用任务实现周期性处理(软件计时器将在后面的章节中讨论)。空闲任务何时执行以及如何使用。

    void ATaskFunction( void *pvParameters )

    创建一个任务函数 下面展示一些 内联代码片。

    void ATaskFunction( void *pvParameters ) { int32_t lVariableExample = 0; /* A task will normally be implemented as an infinite loop. */ for( ;; ) { /* The code to implement the task functionality will go here. */ } vTaskDelete( NULL ); }

    任务的唯一运行

    创建任务

    任务是使用FreeRTOSx任务创建()API函数创建的。 这可能是所有API函数中最复杂的,因此不幸的是,它是第一次遇到的,但是任务必须首先掌握,因为它们是多任务系统中最基本的组件。 本书中的所有示例都使用了xTaskCreate()函数,因此有大量的示例可供参考。 代码: 下面展示一些 内联代码片。

    BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, const char * const pcName, const configSTACK_DEPTH_TYPE usStackDepth, void * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask )

    参数说明:

    pvTaskCode 任务只是永远不会退出的C函数,因此,作为一个无限循环来实现。 pv Task Code参数只是一个指向实现任务的函数的指针(实际上只是函数的名称)。pcName 任务的描述性名称。 这一点不被自由RTOS以任何方式使用。 它纯粹是作为调试辅助工具而包含的。 用人类可读的名称识别任务要比试图通过其句柄识别任务简单得多。usStackDepth 每个任务都有自己的唯一堆栈,在创建任务时由内核分配给任务。 堆栈深度值告诉内核使堆栈有多大。 空闲任务使用的堆栈的大小由应用程序定义的常量configMINIMAL_STACK_SIZE1定义。 在所使用的处理器体系结构的免费RTOS演示应用程序中分配给此常量的值是任何任务推荐的最小值。 如果您的任务使用了大量的堆栈空间,那么您必须分配更大的值。

    pvParameters 任务函数接受指向void(void*)的类型指针的参数*)。 分配给 pvParameters的值是传递给任务的值。 本书中的一些例子演示了如何使用参数。uxPriority 优先级 定义任务执行的优先级。 优先级可以从最低优先级的0分配到(configMAX_PRIORITIES-1),这是最高优先级。configMAX_PRIORITIES是用户定义的常量,如第3.5节所述。pxCreatedTask :pxCreatedTask创建任务可以用来传递正在创建的任务的句柄。 然后,这个句柄可以用于在API调用中引用任务,例如,更改任务优先级或删除任务。 如果您的应用程序没有使用任务句柄,那么PX创建的任务可以设置为NULL。返回值 1. pdPASS 或 2. pdFAIL ;类型为BaseType_t

    任务创建案例一 任务一任务二各自创建

    void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ const char *pcTaskName = "Task 1 is running\r\n"; /* Infinite loop */ for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osDelay(1000); } /* USER CODE END Func_usar1 */ } /* USER CODE BEGIN Header_StartTask04 */ /** * @brief Function implementing the myTask04 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask04 */ void StartTask04(void const * argument) { /* USER CODE BEGIN StartTask04 */ const char *pcTaskName = "Task 2 is running\r\n"; /* Infinite loop */ for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osDelay(1000); } /* USER CODE END StartTask04 */ }

    任务创建案例二 任务一中创建任务二

    void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ const char *pcTaskName = "Task 1 is running\r\n"; osThreadDef(myTask04, StartTask04, osPriorityLow, 0, 128); myTask04Handle = osThreadCreate(osThread(myTask04), NULL); /* Infinite loop */ for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osDelay(1000); } /* USER CODE END Func_usar1 */ }

    任务创建案例三 使用xTaskCreate第4个参数

    下面展示一些 内联代码片。

    static char *pcTask1Name = "Task 1 is running\r\n"; static char *pcTask2Name = "Task 2 is running\r\n"; osThreadDef(Task_LED1, Func_LED1, osPriorityNormal, 0, 128); Task_LED1Handle = osThreadCreate(osThread(Task_LED1), NULL); /* definition and creation of Task_LED2 */ osThreadDef(Task_LED2, Func_LED2, osPriorityIdle, 0, 128); Task_LED2Handle = osThreadCreate(osThread(Task_LED2), NULL); /* definition and creation of Task_Usar1 */ osThreadDef(Task_Usar1, Func_usar1, osPriorityIdle, 0, 128); Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name); /* definition and creation of myTask04 */ osThreadDef(myTask04, StartTask04, osPriorityLow, 0, 128); myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name); void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ char *pcTaskName; /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osDelay(1000); } /* USER CODE END Func_usar1 */ } /* USER CODE BEGIN Header_StartTask04 */ /** * @brief Function implementing the myTask04 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask04 */ void StartTask04(void const * argument) { /* USER CODE BEGIN StartTask04 */ char *pcTaskName; /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osDelay(1000); } /* USER CODE END StartTask04 */ }

    结果如下

    任务创建优先级设定(任务一般不超过32个)

    任务优先是由宏configMAX_PRIORITIES定义 FreeRTOSConfig.h. configMAX_PRIORITIES在最小的必要时,由于其值越高,RAM将被消耗越多 任务优先级范围的话是在0~(configMAX_PRIORITIES-1) 修改函数可以使用vTaskPrioritySet()

    通常方法设定

    宏定义必须要开启configUSE_PORT_OPTIMISED_TASK_SELECTION = 0

    结构优化方法

    configMAX_PRIORITIES的值不能超过32个 如果在Free RTOSConfig.h中configUSE_PORT_OPTIMISED_TASK_SELECTION设置为1,则将使用架构优化方法

    时间管理和嘀嗒中断

    time slicing 时间切片 tick interrupt 嘀嗒中断 是有宏configTICK_RATE_HZ设定 这边为1ms 如果configTICK_RATE_HZ设置为100(Hz),那么时间片将为10毫秒。 两个滴答中断之间的时间称为“滴答周期’。 一次切片等于一个滴答周期。

    configTICK_RATE_HZ的最佳值取决于正在开发的应用程序,尽管100的值是典型 的 任务切换图 pdMS_TO_TICKS()函数(限制使用当 configTICK_RATE_HZ大于1000) 宏将以毫秒为单位指定的时间转换为滴答中指定的时间。 举例:TickType_t xTimeInTicks = pdMS_TO_TICKS( 200 );

    任务优先级案例一 修改数字

    结果:

    结果: 为了使任务有用,必须重新编写它们,以实现事件驱动。 事件驱动的任务只有在触发事件发生后才有工作(处理)可执行,并且无法在事件发生之前进入运行状态。 调度程序总是选择能够运行的最高优先级任务。 无法运行的高优先级任务意味着调度程序无法选择它们,因此必须选择能够运行的低优先级任务。 因此,使用事件驱动的任务意味着任务可以在不同的优先级上创建,而不需要最高优先级的任务饥饿所有处理时间的低优先级任务。

    同步事件可能会阻塞状态

    任务进入阻塞状态

    时间(与时间相关的)事件-事件要么是延迟期到期,要么是绝对时间到达。 例如,任务可能会进入阻塞状态,等待10毫秒通过。同步事件-事件起源于另一个任务或中断。 例如,任务可能进入阻塞状态,等待数据到达队列。 同步事件涵盖广泛的事件类型。队列、二进制信号量、计数信号量、互斥量、递归互斥量、事件组

    暂停状态(挂起状态)+ 就绪状态

    vTaskSuspend()函数

    任务延迟 vTaskDelay()

    任何形式的轮询都有其他几个缺点,其中最重要的是效率低下。 在轮询期间,任务实际上没有任何工作要做,但它仍然使用最大的处理时间,因此浪费处理器周期 因此要使用下vTaskDelay() (宏INCLUDE_vTaskDelay = 1才能使用) 任务延迟()将调用任务放置到阻塞状态,以获得固定数量的滴答中断。 任务处于阻塞状态时不使用任何处理时间,因此任务只在实际有工作要做时使用处理时间。 参数xTicksToDelay

    在转换回就绪状态之前,调用任务将保持在阻塞状态的滴答数中断。例如,当滴答计数为10,000时,如果一个名为vTaskDelay(100)的任务,那么它将立即进入阻塞状态,并保持在阻塞状态,直到滴答计数达到10,100为止。任务延迟(pdMS_TO_TICKS(100)将导致调用任务保持在阻塞状态100毫秒。

    测试如下

    void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ char *pcTaskName; const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 ); /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); vTaskDelay( xDelay250ms ); // osDelay(1000); } } void StartTask04(void const * argument) { /* USER CODE BEGIN StartTask04 */ char *pcTaskName; const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 ); /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); vTaskDelay( xDelay250ms ); // osDelay(1000); } /* USER CODE END StartTask04 */ }

    加延迟后执行的过程

    任务延迟 vTaskDelayUntil()

    参数

    pxPreviousWakeTime 任务以固定的频率周期性地执行

    xTimeIncrement pdMS_TO_TICKS()可用于将以毫秒为单位指定的时间转换为以滴答为单位指定的时间 API 函数 vTaskDelayUntil()可以 用于实现一个固定执行周期的需求(当你需要让你的任务以固定频率周期性执行的时 候)。由于调用此函数的任务解除阻塞的时间是绝对时刻 vTaskDelayUntil()比调用 vTaskDelay()可以实现更精确的周期性 案例:

    osThreadDef(Task_Usar1, Func_usar1, 1, 0, 128); Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name); osThreadDef(Task_Usar2, Func_usar1, 1, 0, 128); Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask2Name); /* definition and creation of myTask04 */ osThreadDef(myTask04, StartTask04, 0, 0, 128); myTask04Handle = osThreadCreate(osThread(myTask04), NULL); void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ char *pcTaskName; /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osDelay(20); } /* USER CODE END Func_usar1 */ } /* USER CODE BEGIN Header_StartTask04 */ /** * @brief Function implementing the myTask04 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask04 */ void StartTask04(void const * argument) { /* USER CODE BEGIN StartTask04 */ static char *pcTask2Name = "Periodic task is running\r\n"; TickType_t xLastWakeTime; xLastWakeTime = xTaskGetTickCount(); /* Infinite loop */ for(;;) { // HAL_UART_Transmit(&huart1,(uint8_t *)xLastWakeTime,4,0xffff); HAL_UART_Transmit(&huart1,(uint8_t *)pcTask2Name,strlen(pcTask2Name),0xffff); vTaskDelayUntil( &xLastWakeTime, pdMS_TO_TICKS( 250 ) ); } /* USER CODE END StartTask04 */ }

    空闲任务和空闲任务钩子

    用 vTaskStartScheduler()时,调度器会自动创建一个空闲任务,**空闲任务拥有最低优先级(优先级 0)**以保证其不会妨碍具有更高优先级的应用任务进入运行态——当然,没有任何限制说是不能把应用任务创建在与空闲任务相同的优先级上;如果需要的话,你一样可以和空闲任务一起共享优先级

    任务优先级案例一 使用函数

    首先先介绍两个函数 void vTaskPrioritySet( TaskHandle_t pxTask, UBaseType_t uxNewPriority ); 宏定义开启INCLUDE_vTaskPrioritySet = 1才能使用 UBaseType_t uxTaskPriorityGet( TaskHandle_t pxTask ); 宏定义开启INCLUDE_uxTaskPriorityGet = 1才能使用 案例如下

    任务1(清单33)是以最高优先级创建的,因此保证首先运行。 任务1打印出几个字符串,然后将任务2的优先级(清单34)提高到高于自己的优先级任务2一旦具有最高相对优先级,就开始运行(进入运行状态。 只有一个任务可以在任何一次处于运行状态,因此当任务2处于运行状态时,任务1处于就绪状态任务2将其优先级设置为后退意味着任务1再次是最高优先级的任务,因此任务1重新进入运行状态,迫使任务2回到就绪状态。 osThreadDef(Task_Usar1, Func_usar1, osPriorityNormal, 0, 128); Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name); /* definition and creation of myTask04 */ osThreadDef(myTask04, StartTask04,osPriorityNormal , 0, 128); myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name); void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ char *pcTaskName; static char *string = "About to raise the Task 2 priority\r\n"; UBaseType_t uxPriority; uxPriority = uxTaskPriorityGet( NULL ); /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); HAL_UART_Transmit(&huart1,(uint8_t *)string,strlen(string),0xffff); vTaskPrioritySet( myTask04Handle, ( uxPriority + 1 ) ); // osDelay(20); } /* USER CODE END Func_usar1 */ } /* USER CODE BEGIN Header_StartTask04 */ /** * @brief Function implementing the myTask04 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask04 */ void StartTask04(void const * argument) { /* USER CODE BEGIN StartTask04 */ UBaseType_t uxPriority; static char *string = "About to lower the Task 2 priority\r\n"; char *pcTaskName; uxPriority = uxTaskPriorityGet( NULL ); pcTaskName = (char * )argument; /* Infinite loop */ for(;;) { // HAL_UART_Transmit(&huart1,(uint8_t *)xLastWakeTime,4,0xffff); HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); HAL_UART_Transmit(&huart1,(uint8_t *)string,strlen(string),0xffff); vTaskPrioritySet( NULL, ( uxPriority - 2 ) ); } /* USER CODE END StartTask04 */ }

    这边会在任务一和任务二之间相互切换 每个任务都可以通过简单地使用NULL来查询和设置自己的优先级 uxPriority = uxTaskPriorityGet( NULL );

    删除任务函数 vTaskDelete()

    宏定义INCLUDE_vTaskDelete = 1才能使用

    删除的任务不再存在,不能再次进入运行状态 空闲任务的责任是释放分配给此后被删除的任务的内存。

    参数说明

    pxTaskToDelete要删除的任务的句柄(主题任务)-请参阅x任务创建()API函数的PX创建任务参数,以获得任务句柄的信息任务可以通过传递NULL来代替有效的任务句柄来删除自己。

    案例说明:

    任务1由具有优先级1的主()创建。 当它运行时,它在优先级2下创建任务2。 任务2现在是最高优先级的任务,所以它立即开始执行。 主()的源如清单37所示,任务1的源如清单38所示。任务2除了删除自己之外什么都不做。 它可以通过将NULL传递给vTask Delete来删除自己()但为了演示的目的,它使用自己的任务句柄。 任务2的来源如清单39所示。当任务2被删除时,任务1再次是最高优先级的任务,因此继续执行-在这一点上,它调用vTaskDelay()来阻塞一时间。空闲任务在任务1处于阻塞状态时执行,并释放分配给现在删除的任务2的内存。当任务1离开阻塞状态时,它再次成为最高优先级的就绪状态任务,因此预注定空闲任务。 当它进入运行状态时,它再次创建任务2,因此它继续进行

    案例如下

    osThreadDef(Task_Usar1, Func_usar1, osPriorityNormal, 0, 128); Task_Usar1Handle = osThreadCreate(osThread(Task_Usar1), (void*)pcTask1Name); /* definition and creation of myTask04 */ // osThreadDef(myTask04, StartTask04,osPriorityNormal , 0, 128); // myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name); void Func_usar1(void const * argument) { /* USER CODE BEGIN Func_usar1 */ char *pcTaskName; const TickType_t xDelay100ms = pdMS_TO_TICKS( 100UL ); /* Infinite loop */ pcTaskName = (char * )argument; for(;;) { HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); osThreadDef(myTask04, StartTask04,osPriorityNormal , 0, 128); myTask04Handle = osThreadCreate(osThread(myTask04), (void*)pcTask2Name); vTaskDelay( xDelay100ms ); // osDelay(20); } /* USER CODE END Func_usar1 */ } /* USER CODE BEGIN Header_StartTask04 */ /** * @brief Function implementing the myTask04 thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartTask04 */ void StartTask04(void const * argument) { /* USER CODE BEGIN StartTask04 */ static char *string = "Task 2 is running and about to delete itself\r\n"; char *pcTaskName; pcTaskName = (char * )argument; /* Infinite loop */ for(;;) { // HAL_UART_Transmit(&huart1,(uint8_t *)pcTaskName,strlen(pcTaskName),0xffff); HAL_UART_Transmit(&huart1,(uint8_t *)string,strlen(string),0xffff); vTaskDelete( NULL ); } /* USER CODE END StartTask04 */ }

    调度算法–轮询调度算法

    调度器调度的是就绪状态而不是阻塞或者是挂起的任务,调度程序将始终选择优先级最高的就绪状态任务进入运行状态。

    调度算法修改可修改以下宏定义

    configUSE_PREEMPTIONconfigUSE_TIME_SLICINGconfigUSE_TICKLESS_IDLE:它的使用可能导致滴答中断被完全关闭长时间。 配置常数来更改算法。

    抢占调度+时间切片模式

    该设置将内核配置为使用具有时间分割的优先

    configUSE_PREEMPTION = 1configUSE_TIME_SLICING = 1 术语定义固定优先级被描述为“固定优先级”的调度算法不会改变分配给正在调度的任务的优先级,但也不会阻止任务本身改变自己的优先级或其他任务的优先级抢占式优先级高于运行状态任务的任务进入就绪状态,则抢占式调度算法将立即“抢占”运行状态任务时间切片时间切片用于在同等优先级的任务之间共享处理时间,即使任务没有显式屈服或进入阻塞状态。 使用“时间切片”描述的调度算法将选择一个新任务,在每个时间片的末尾进入运行状态,如果有其他准备状态任务具有与运行任务相同的优先级。 时间片等于两个RTOS滴答中断之间的时间。

    26和图27演示了在使用具有时间切片算法的固定优先级抢占式调度时如何调度任务。 图26显示了当应用程序中的所有任务具有唯一优先级时,选择任务进入运行状态的顺序。 图27显示了当应用程序中的两个任务共享优先级时,选择任务进入运行状态的顺序。

    Idle Task ( 空闲任务) : 空闲任务以最低优先级运行,的,因此,较高优先级的任务进入就绪状态时都会被预先加密-例如,在t3、t5和t9时。Task 3 :任务3是一个事件驱动的任务,执行优先级相对较低,但高于空闲优先级。 它的大部分时间都在阻塞状态中等待它感兴趣的事件,每次事件发生时从阻塞状态过渡到就绪状态。事件发生在t3和t5时,也发生在t9和t12之间。 在t3和t5时刻发生的事件被立即处理,因为在这些时刻,任务3是能够运行的最高优先级任务。 在t9和t12之间发生的事件直到t12才被处理,因为在此之前,较高优先级的任务Task1和Task2仍然在执行。 只有在t12时刻,任务1和任务2都处于阻塞状态,使任务3成为最高优先级的就绪状态任务。Task 2:任务2是一种周期性任务,它以高于任务3优先级的优先级执行,但低于任务1的优先级。 任务的周期间隔意味着任务2希望在T1、T6和T9时执行。在T6时,任务3处于运行状态,但任务2具有较高的相对优先级,因此预选任务3并立即开始执行。 任务2在t7时刻完成其处理并重新进入阻塞状态,此时Task3可以重新进入运行状态完成其处理。 任务3本身的块在时间T8Task 1: 任务1也是一个事件驱动的任务。 它以最高优先级执行,因此可以预先阻止系统中的任何其他任务。 显示的唯一Task1事件发生在时间t10,此时Task1预提示Task2。 任务2只有在任务1在时间t11重新进入阻塞状态后才能完成其处理。

    时间切片: configIDLE_SHOULD_YIELD

    如果configIDLE_SHOULD_YIELD设置为0,则空闲任务将保持其整个时间片的运行状态,除非它被更高优先级的任务抢占。

    空闲任务和任务2: 都是连续处理任务,两者的优先级都为0(尽可能低的优先级)。 调度程序只在没有能够运行的优先级更高的任务时,将处理时间分配给优先级0任务,并通过时间切片共享分配给优先级0任务的时间。 在每个滴答中断上启动一个新的时间片,图27中的时间是t1、t2、t3、t4、t5、t8、t9、t10和t11。 空闲任务和任务2依次进入运行状态,这可能导致两个任务在同一时间片的一部分处于运行状态,就像在时间T5和时间T8之间发生的那样。****!!!!

    任务1的优先级高于空闲优先级。 任务1是一个事件驱动的任务,它的大部分时间都在阻塞状态中等待它感兴趣的事件,每次事件发生时从阻塞状态过渡到就绪状态。 感兴趣的事件发生在时间t6,因此在t6,Task1成为能够运行的最高优先级任务,因此Task1通过时间片预先提示空闲任务部分。 事件的处理在时间t7完成,此时Task1重新进入阻塞状态

    configIDLE_SHOULD_YIELD 设置为1,然后空闲任务将在其循环的每次迭代中产生(自愿放弃其分配时间片的任何剩余),如果在就绪状态下有其他空闲优先级任务。这边任务2在就绪状态,所以运行

    在空闲任务之后选择进入运行状态的任务不执行整个时间片,而是执行空闲任务产生的时间片的任何剩余

    抢占调度(无时间切片)

    configUSE_PREEMPTION 1configUSE_TIME_SLICING 0

    没有时间切片的优先抢占调度保持与上一节中描述的相同的任务选择和抢占算法,但不使用时间切片在同等优先级的任务之间共享处理时间。 如果不使用时间切片,那么调度程序将只选择一个新任务进入运行状态,当两者之一:

    更高优先级的任务进入就绪状态运行状态下的任务进入阻塞或挂起状态 它假设configIDLE_SHOULD_YIELD设置为0 当不使用时间切片时,任务切换的时间比使用时间切片时要少,但是会导致相同优先级任务 因此,在没有时间切片的情况下运行调度程序被认为是一种先进的技术,只应由经验丰富的用户使用。滴答中断:滴答中断发生在T1、T2、T3、T4、T5、T8、T11、T12和T13。任务1:任务1是一个高优先级的事件驱动任务,它大部分时间都在阻塞状态中等待它感兴趣的事件。 任务1从阻塞状态转换为就绪状态(随后,由于它是最高优先级的就绪状态任务,每次事件发生时都转换为运行状态。 图29显示了任务1在t6和t7之间处理事件,然后在t9和t10之间处理事件空闲任务和任务2:1两者的优先级为0(空闲优先级)。 连续处理任务不进入阻塞状态2不使用时间切片,因此处于运行状态的空闲优先级任务将保持在运行状态,直到它被更高优先级的任务1抢占。3空闲任务在时间T1开始运行,并保持在运行状态,直到它在时间T6被Task1抢占任务2在时间t7开始运行,即任务1重新进入阻塞状态等待另一个事件。 任务2仍然处于运行状态,直到它也被任务1在时间t9抢占-在它进入运行状态后小于一个滴答周期。

    总结:抢占式不分时片方式,绝对的抢占式,相同等级任务就轮流执行。

    协作式调度

    本书的重点是抢占式调度为主,但是也可以使用协助式调度。

    configUSE_PREEMPTION = 0configUSE_TIME_SLICING = 任何值

    使用合作调度程序,只有当运行状态任务进入阻塞状态时才会发生任务切换,或者运行状态任务通过**手动请求重新调度taskYIELD().**任务永远不会被抢占,所以不能使用时间切片。

    图30中的水平虚线显示任务处于就绪状态时

    任务1的优先级最高。 它从阻塞状态开始,等待信号量。 在T3时,中断给出信号量,导致任务1离开阻塞状态并进入就绪状态,正在使用合作调度程序,任务1保持在就绪状态,直到时间T4-这是当运行状态任务调用任务YIELD()任任务2的优先级介于任务1和任务3之间。 它以阻塞状态开始,等待任务3在时间t2发送给它的消息。在时间T2任务2是最高优先级的就绪状态任务,如果使用了抢占式调度程序,任务2将成为运行状态任务。 然而,随着合作调度程序的使用,任务2保持在就绪状态,直到运行状态任务进入调用任务YIELD()的阻塞状态。运行状态任务在时间T4时调用任务YIELD(),但到那时,任务1是最高优先就绪状态任务,因此任务2在时间T5时重新进入阻塞状态之前实际上不会成为运行状态任务。当t6任务2重新进入阻塞状态以等待下一条消息时,此时Task3再次是最高优先级Ready状态任务。

    当使用合作(协助)调度器时,通常比使用先发制人调度器时更容易避免由同时访问引起的问题:

    例如:协助式确保任务1在将其整个字符串写入UART之前不会离开运行状态,并在这样做时,消除字符串被另一个任务的激活损坏的可能性。

    当使用抢占式调度程序时,**调度程序将立即开始运行任务,该任务将成为最高优先级就绪状态任务。 这在实时系统中往往是必不可少的,**这些系统必须在规定的时间内对高度优先事件作出反应。

    当使用合作调度程序时,将切换到已成为最高优先级的就绪状态任务,直到运行状态任务进入阻塞状态或调用任务YIELD()才会执行。

    Processed: 0.012, SQL: 8