北仑网站建设29gz,做电影网站用什么程序,wordpress伪静态htaccess,烟台网站主关键词在学习FreeRTOS过程中#xff0c;结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源#xff0c;整理了该篇文章。如有内容理解不正确之处#xff0c;欢迎大家指出#xff0c;共同进步。 参考#xff1a;https://rtos.100ask.net/zh/… 在学习FreeRTOS过程中结合韦东山-FreeRTOS手册和视频、野火-FreeRTOS内核实现与应用开发、及网上查找的其他资源整理了该篇文章。如有内容理解不正确之处欢迎大家指出共同进步。 参考https://rtos.100ask.net/zh/FreeRTOS/DShanMCU-F103/chapter9.html#_9-5-%E7%A4%BA%E4%BE%8B5-%E4%BB%BB%E5%8A%A1%E6%9A%82%E5%81%9C
https://freertos.blog.csdn.net/article/details/50312443
1. 任务
对于整个单片机程序我们称之为 application应用程序。
使用FreeRTOS时我们可以在application中创建多个任务(task)有些文档把任务也称为线程(thread)。
1.1 任务特性
使用RTOS的实时应用程序可认为是一系列独立任务的集合。每个任务在自己的环境中运行不依赖于系统中的其它任务或者RTOS调度器。在任何时刻只有一个任务得到运行RTOS调度器决定运行哪个任务。调度器会不断的启动、停止每一个任务宏观看上去就像整个应用程序都在执行。作为任务不需要对调度器的活动有所了解在任务切入切出时保存上下文环境寄存器值、堆栈内容是调度器主要的职责。为了实现这点每个任务都需要有自己的堆栈。当任务切出时它的执行环境会被保存在该任务的堆栈中这样当再次运行时就能从堆栈中正确的恢复上次的运行环境。
1.2 任务状态 运行如果一个任务正在执行那么说这个任务处于运行状态。此时它占用处理器。就绪就绪的任务已经具备执行的能力不同于阻塞和挂起但是因为有一个同优先级或者更高优先级的任务处于运行状态而还没有真正执行。阻塞如果任务当前正在等待某个时序或外部中断我们就说这个任务处于阻塞状态。比如一个任务调用vTaskDelay()后会阻塞到延时周期到为止。任务也可能阻塞在队列或信号量事件上。进入阻塞状态的任务通常有一个“超时”周期当事件超时后解除阻塞。挂起处于挂起状态的任务同样对调度器无效。仅当明确的分别调用vTaskSuspend() 和xTaskResume() API函数后任务才会进入或退出挂起状态。不可以指定超时周期事件不可以通过设定超时事件而退出挂起状态
阻塞状态
在阻塞状态的任务它可以等待两种类型的事件
时间相关的事件 可以等待一段时间我等2分钟也可以一直等待直到某个绝对时间我等到下午3点 同步事件这事件由别的任务或者是中断程序产生 例子1任务A等待任务B给它发送数据例子2任务A等待用户按下按键同步事件的来源有很多(这些概念在后面会细讲) 队列(queue)二进制信号量(binary semaphores)计数信号量(counting semaphores)互斥量(mutexes)递归互斥量、递归锁(recursive mutexes)事件组(event groups)任务通知(task notifications)
在等待一个同步事件时可以加上超时时间。比如等待队里数据超时时间设为10ms
- 10ms之内有数据到来成功返回
- 10ms到了还是没有数据超时返回挂起状态
在日常生活的例子中母亲正在电脑前跟同事沟通母亲可以暂停
好烦啊我暂停一会领导说你暂停一下
FreeRTOS中的任务也可以进入暂停状态唯一的方法是通过vTaskSuspend函数。函数原型如下
void vTaskSuspend( TaskHandle_t xTaskToSuspend );参数xTaskToSuspend表示要暂停的任务如果为NULL表示暂停自己。
要退出暂停状态只能由别人来操作
别的任务调用vTaskResume中断程序调用xTaskResumeFromISR
1.3 空闲任务及其钩子函数
1.3.1 空闲任务
空闲任务(Idle任务)的作用之一释放被删除的任务的内存。
除了上述目的之外为什么必须要有空闲任务一个良好的程序它的任务都是事件驱动的平时大部分时间处于阻塞状态。有可能我们自己创建的所有任务都无法执行但是调度器必须能找到一个可以运行的任务所以我们要提供空闲任务。在使用vTaskStartScheduler()函数来创建、启动调度器时这个函数内部会创建空闲任务。
#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )void vTaskStartScheduler( void )
{BaseType_t xReturn;/* The Idle task is being created using dynamically allocated RAM. */xReturn xTaskCreate( prvIdleTask,configIDLE_TASK_NAME,configMINIMAL_STACK_SIZE,( void * ) NULL,( tskIDLE_PRIORITY | portPRIVILEGE_BIT ),xIdleTaskHandle ); /*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */
}空闲任务优先级为0它不能阻碍用户任务运行空闲任务要么处于就绪态要么处于运行态永远不会阻塞空闲任务的优先级为0这意味着一旦某个用户的任务变为就绪态那么空闲任务马上被切换出去让这个用户任务运行。在这种情况下我们说用户任务抢占(pre-empt)了空闲任务这是由调度器实现的。要注意的是如果使用vTaskDelete()来删除任务那么你就要确保空闲任务有机会执行否则就无法释放被删除任务的内存。
我们可以添加一个空闲任务的钩子函数(Idle Task Hook Functions)空闲任务的循环每执行一次就会调用一次钩子函数。钩子函数的作用有这些
执行一些低优先级的、后台的、需要连续执行的函数测量系统的空闲时间空闲任务能被执行就意味着所有的高优先级任务都停止了所以测量空闲任务占据的时间就可以算出处理器占用率。
void vApplicationIdleHook( void )
{int i;// 用于获取系统运行时间和任务运行时间的统计信息。vTaskGetRunTimeStats(pcWriteBuffer);for (i 0; i 16; i)printf(-);printf(\n\r);printf(%s\n\r, pcWriteBuffer);
}让系统进入省电模式空闲任务能被执行就意味着没有重要的事情要做当然可以进入省电模式了。
空闲任务的钩子函数的限制
不能导致空闲任务进入阻塞状态、暂停状态如果你会使用vTaskDelete()来删除任务那么钩子函数要非常高效地执行。如果空闲任务执行卡在钩子函数里的话它就无法释放内存。
1.3.2 使用钩子函数的前提
在FreeRTOS\Source\tasks.c中可以看到如下代码所以前提就是
把这个宏定义为1configUSE_IDLE_HOOK实现vApplicationIdleHook函数
2. 支持多优先级
2.1 任务优先级
每个任务都要被指定一个优先级取值范围0~configMAX_PRIORITIES - 1configMAX_PRIORITIES定义在FreeRTOSConfig.h中。FreeRTOS的调度器可以使用2种方法来快速找出优先级最高的、可以运行的任务。使用不同的方法时configMAX_PRIORITIES 的取值有所不同。 通用方法 使用C函数实现对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小因为取值越大越浪费内存也浪费时间。configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时使用此方法。 架构相关的优化的方法 架构相关的汇编指令可以从一个32位的数里快速地找出为1的最高位。使用这些指令可以快速找出优先级最高的、可以运行的任务。使用这种方法时configMAX_PRIORITIES的取值不能超过32。configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时使用此方法。 低优先级数值代表低优先级。空闲任务idle task的优先级为0tskIDLE_PRIORITY。FreeRTOS调度器确保处于最高优先级的就绪或运行态任务获取处理器换句话说处于就绪状态的任务只有其中的最高优先级任务才会运行。任何数量的任务可以共享同一个优先级。如果宏configUSE_TIME_SLICING定义为1处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器。
2.2 查找最高优先级的就绪任务
就绪列表 pxReadyTasksLists[ configMAX_PRIORITIES ]是一个数组数组里面存的是就绪任务的 TCB准确来说是 TCB 里面的 xStateListItem 节点数组的下标对应任务的优先级优先级越低对应的数组下标越小。
空闲任务的优先级最低对应的是下标为 0 的链表。
任务在创建的时候会根据任务的优先级将任务插入到就绪列表不同的位置。相同优先级的任务插入到就绪列表里面的同一条链表中。
pxCurrentTCB 是一个全局的 TCB 指针用于指向优先级最高的就绪任务的 TCB即当前正在运行的 TCB 。那么我们要想让任务支持优先级即在任务切换taskYIELD的时候让pxCurrenTCB 指向**最高优先级的就绪任务的 TCB **就可以。
查找最高优先级的就绪任务有两种方法具体由configUSE_PORT_OPTIMISED_TASK_SELECTION/font 这个宏控制定义为 0 选择通用方法定义为 1 选择根据处理器优化的方法该宏默认在 portmacro.h 中定义为 1即使用优化过的方法。
2.3 通用方法
/* 空闲任务优先级宏定义在 task.h 中定义 */
#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )/* 定义 uxTopReadyPriority在 task.c 中定义 */
static volatile UBaseType_t uxTopReadyPriority tskIDLE_PRIORITY;2.3.1 taskRECORD_READY_PRIORITY()
taskRECORD_READY_PRIORITY()用于更新 uxTopReadyPriority 的值。
uxTopReadyPriority 用于表示创建的任务的最高优先级默认初始化为 0即空闲任务的优先级。
2.3.2 taskSELECT_HIGHEST_PRIORITY_TASK()
taskSELECT_HIGHEST_PRIORITY_TASK()用于寻找优先级最高的就绪任务实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值。
在创建时已通过taskRECORD_READY_PRIORITY记录当前就绪任务的最高优先级uxTopReadyPriority在这里再次更新uxTopReadyPriority是因为最高优先级的任务可能会被挂起或阻塞那就会从其就绪链表中移除若最高优先级的就绪链表中再没有其他就绪任务则该优先级的就绪链表就为空。所以需要从高优先级到低优先级遍历就绪链表找到第一个非空的就绪链表。
#if ( configUSE_PORT_OPTIMISED_TASK_SELECTION 0 )#define taskRECORD_READY_PRIORITY( uxPriority ) \{ \/* uxTopReadyPriority 存的是就绪任务的最高优先级 */\if( ( uxPriority ) uxTopReadyPriority )\{ \uxTopReadyPriority ( uxPriority );\} \} /* taskRECORD_READY_PRIORITY */#define taskSELECT_HIGHEST_PRIORITY_TASK() \{ \UBaseType_t uxTopPriority uxTopReadyPriority; \\/* 寻找包含就绪任务的最高优先级的队列 */\while( listLIST_IS_EMPTY( ( pxReadyTasksLists[ uxTopPriority ] ) ) )\{ \--uxTopPriority; \} \/* 获取优先级最高的就绪任务的 TCB然后更新到 pxCurrentTCB */ \listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, ( pxReadyTasksLists[ uxTopPriority ] ) );\/*更新 uxTopReadyPriority */\uxTopReadyPriority uxTopPriority; \} /* taskSELECT_HIGHEST_PRIORITY_TASK */2.4 优化方法
这得益于 Cortex-M 内核有一个计算前导零的指令CLZ所谓前导零就是计算一个变量Cortex-M 内核单片机的变量为 32位从高位开始第一次出现 1 的位的前面的零的个数。比如一个 32 位的变量uxTopReadyPriority其位 0、位 24 和 位 25 均 置 1 其 余 位 为 0 具 体 见 。 那 么 使 用 前 导 零 指 令 __CLZ (uxTopReadyPriority)可以很快的计算出 uxTopReadyPriority 的前导零的个数为 6。 如果 uxTopReadyPriority 的每个位号对应的是任务的优先级任务就绪时则将对应的位置 1反之则清零。那么图 10-2 就表示优先级 0、优先级 24 和优先级 25 这三个任务就绪其中优先级为 25的任务优先级最高。利用前导零计算指令可以很快计算出就绪任务中的最高优先级为
( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) ) ( 31UL - ( uint32_t ) 6 ) 25。
#define portGET_HIGHEST_PRIORITY( uxTopPriority, uxReadyPriorities ) \uxTopPriority ( 31UL - ( uint32_t ) __clz( ( uxReadyPriorities ) ) )
#define portRECORD_READY_PRIORITY( uxPriority, uxReadyPriorities ) \( uxReadyPriorities ) | ( 1UL ( uxPriority ) )#define portRESET_READY_PRIORITY( uxPriority, uxReadyPriorities ) \( uxReadyPriorities ) ~( 1UL ( uxPriority ) ) 2.4.1 taskRECORD_READY_PRIORITY()
根据传进来的形参通常形参就是任务的优先级将变量 uxTopReadyPriority 的某个位置 1。
uxTopReadyPriority它在优化方法中担任的是一个优先级位图表的角色即该变量的每个位对应任务的优先级如果任务就绪则将对应的位置 1反之清零。根据这个原理只需要计算出 uxTopReadyPriority 的前导零个数就算找到了就绪任务的最高优先级。与taskRECORD_READY_PRIORITY() 作 用 相 反 的 是 taskRESET_READY_PRIORITY() 。 #define taskRECORD_READY_PRIORITY( uxPriority )\ portRECORD_READY_PRIORITY( uxPriority, uxTopReadyPriority )2.4.2 taskRESET_READY_PRIORITY()
用于根据传进来的形参通常形参就是任务的优先级将变量 uxTopReadyPriority的某个位清零。
根据优先级调用 taskRESET_READY_PRIORITY()函数复位 uxTopReadyPriority 变量中对应的位时要先确保就绪列表中对应该优先级下的链表没有任务才行 如果 有则不清零。
假设当前实验中任务 1 会调用 vTaskDelay()会将自己挂起只能是将任务 1 从就绪列表删除不能将任务 1 在优先级位图表 uxTopReadyPriority 中对应的位清 0因为该优先级下还有任务 2否则任务 2 将得不到执行。
#define taskRESET_READY_PRIORITY( uxPriority )\{\if( listCURRENT_LIST_LENGTH( ( pxReadyTasksLists[ ( uxPriority ) ] ) ) ( UBaseType_t ) 0 )\{\portRESET_READY_PRIORITY( ( uxPriority ), ( uxTopReadyPriority ) ); \}\}2.4.3 taskSELECT_HIGHEST_PRIORITY_TASK()
用于寻找优先级最高的就绪任务实质就是更新 uxTopReadyPriority 和 pxCurrentTCB 的值。
根据 uxTopReadyPriority 的值找到最高优先级然后更新到uxTopPriority 这个局部变量中。
根据 uxTopPriority 的值从就绪列表中找到就绪的最高优先级的任务的 TCB然后将 TCB 更新到 pxCurrentTCB。
/* 查找最高优先级的就绪任务根据处理器架构优化后的方法 */
#define taskSELECT_HIGHEST_PRIORITY_TASK()\{\UBaseType_t uxTopPriority; \portGET_HIGHEST_PRIORITY( uxTopPriority, uxTopReadyPriority );\listGET_OWNER_OF_NEXT_ENTRY( pxCurrentTCB, ( pxReadyTasksLists[ uxTopPriority ] ) );\} 2.4.3.1 listGET_OWNER_OF_NEXT_ENTRY
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )\
{ \
List_t * const pxConstList ( pxList );\/* 节点索引指向链表第一个节点调整节点索引指针指向下一个节点 */\/* 如果当前链表有 N 个节点当第 N 次调用该函数时pxIndex 则指向第 N 个节点 */\( pxConstList )-pxIndex ( pxConstList )-pxIndex-pxNext;\/* 当遍历完链表后pxIndex 回指到根节点 */\if( ( void * ) ( pxConstList )-pxIndex ( void * ) ( ( pxConstList )-xListEnd ) )\{\( pxConstList )-pxIndex ( pxConstList )-pxIndex-pxNext;\}\/* 获取节点的 OWNER即 TCB */\( pxTCB ) ( pxConstList )-pxIndex-pvOwner;\
}用于获取下一个节点的 OWNER 。
假设当前链表有 N 个节点当第 N 次调用该函数时pxIndex 则指向第 N个节点即每调用一次节点遍历指针 pxIndex 则会向后移动一次用于指向下一个节点。 3. 创建任务
3.1 什么是任务 从这个角度想函数被暂停时我们怎么保存它、保存什么怎么恢复它、恢复什么 什么叫任务 运行中的函数、被暂停运行的函数 任务是一个函数 函数需要保存吗 函数保存在Flash上不会被破坏无需保存Flash上的函数无需再次保存所以任务不仅仅是函数 任务是变量吗 单纯通过变量无法做事所以任务不仅仅是变量 任务是一个运行中的函数 运行中可以曾经运行现在暂停了但是未退出怎么描述一个运行中的函数假设在某一个瞬间时间停止你怎么记录这个运行中的函数 函数执行到哪里运行的位置需要保存吗 它是一个CPU寄存器名为“PC”需要保存 函数用到了全局变量全局变量需要保存吗 全局变量在内存上不需要保存 函数里用到了局部变量局部变量需要保存吗 局部变量在栈里也是在内存里只要避免栈不被破坏即可局部变量无需保存 运算的中间值需要保存吗中间值保存在哪里 在CPU寄存器里另一个任务也要用到CPU寄存器所以CPU寄存器需要保存 汇总 CPU寄存器需要保存保存在任务的栈里 什么叫现场 暂且认为现场就是被打断瞬间CPU中的R0~R15这16个寄存器的值。 怎么保存 保存进内存里 保存到内存的哪里–栈里 把16个寄存器现场保存到栈里。
1提问LR被覆盖了怎么办LR用来保存返回地址
在C入口划分出自己的栈保存LR进栈里在内存中保存局部变量。
2提问局部变量是在栈里面分配的如何分配
局部变量有可能保存在寄存器中不一定保存在内存里或有 volatile局部变量保存在内存中或寄存器不够用时保存在内存中。
3提问为何每个RTOS任务都有自己的栈
每个任务都有自己的{ 调用关系局部变量现场 }所以需要自己的栈。栈从哪里分配大小怎样确定 栈的大小用估计的方法确定 局部变量的大小调用深度 栈从哪里分配 栈就是一块空闲的内存从巨大的数组里去划分出一块内存给某个任务用作栈 任务切换时怎样找到任务 -——通过链表 在freeRTOS中任务就是一个函数原型如下
void ATaskFunction( void *pvParameters );这个函数不能返回通常任务函数都是一个死循环。任务由xTaskCreate()函数创建由vTaskDelete()函数删除。同一个函数可以创建多个任务换句话说多个任务可以运行同一个函数函数内部尽量使用局部变量 每个任务都有自己的栈每个任务运行这个函数时 任务A的局部变量放在任务A的栈中任务B的局部变量放在任务B的栈中不同的局部变量有自己的副本 函数使用全局变量、静态变量的话 只有一个副本多个任务使用的是同一个副本要防止冲突
void ATaskFunction( void *pvParameters )
{/* 对于不同的任务局部变量放在任务的栈里有各自的副本 */int32_t lVariableExample 0;
/* 任务函数通常实现为一个无限循环 */for( ;; ){/* 任务的代码 */}/* 如果程序从循环中退出一定要使用vTaskDelete删除自己*//* NULL表示删除的是自己*/vTaskDelete( NULL );/* 程序不会执行到这里, 如果执行到这里就出错了 */3.2 创建任务视频补充
在嵌入式领域里面有很多RTOS
FreeRTOS操作系统 创建新任务是xTaskCreate RT-Thread操作系统 创建新任务是rt_thread_create
不同的操作系统创建任务的函数不一样若想统一起来则要在上面增加一个统一的接口层cmsis_os2.c该文件会抽象出一个统一的接口比如osThreadNew函数(348行)。我们写程序的人、写APP的人就可以直接调用该函数会根据不同的操作系统调用 不同的创建新任务的函数。 使用该原生代码font stylecolor:#DF2A3F;xTaskCreateStatic/font或font stylecolor:#DF2A3F;xTaskCreate/font来创建任务。
3.3 创建第1个多任务程序9.2.2创建任务
任务创建
动态创建 任务控制块和栈的内存是创建任务时动态分配的任务删除时内存可以释放。 静态创建 任务控制块和栈的内存需要事先定义好是静态的内存任务删除时内存不能释放。
创建任务时可以使用2个函数动态分配内存、静态分配内存。
动态分配内存
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数const char * const pcName, // 任务的名字const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节void * const pvParameters, // 调用任务函数时传入的参数UBaseType_t uxPriority, // 优先级TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务02-first-rtos-app程序 不同的任务pvParameters不一样
/* 使用同一个函数创建不同的任务 */xTaskCreate(LcdPrintTask, task1, 128, g_Task1Info, osPriorityNormal, NULL);xTaskCreate(LcdPrintTask, task2, 128, g_Task2Info, osPriorityNormal, NULL);xTaskCreate(LcdPrintTask, task3, 128, g_Task3Info, osPriorityNormal, NULL);3.4 创建任务流程
TCB
// 已删减typedef struct tskTaskControlBlock
{volatile StackType_t *pxTopOfStack; /* Points to the location of the last item placed on the tasks stack. THIS MUST BE THE FIRST MEMBER OF THE TCB STRUCT. */ListItem_t xStateListItem; /* (Ready, Blocked, Suspended ). */ListItem_t xEventListItem; /* Used to reference a task from an event list. */UBaseType_t uxPriority; /* The priority of the task. 0 is the lowest priority. */StackType_t *pxStack; /* Points to the start of the stack. */char pcTaskName[ configMAX_TASK_NAME_LEN ];/* Descriptive name given to the task when created. Facilitates debugging only. */ /*lint !e971 Unqualified char types are allowed for strings and single characters only. */#if ( ( portSTACK_GROWTH 0 ) || ( configRECORD_STACK_HIGH_ADDRESS 1 ) )StackType_t *pxEndOfStack; /* Points to the highest valid address for the stack. */#endif#if ( portCRITICAL_NESTING_IN_TCB 1 )UBaseType_t uxCriticalNesting; /* Holds the critical section nesting depth for ports that do not maintain their own count in the port layer. */#endif#if ( configUSE_TRACE_FACILITY 1 )UBaseType_t uxTCBNumber; /* Stores a number that increments each time a TCB is created. It allows debuggers to determine when a task has been deleted and then recreated. */UBaseType_t uxTaskNumber; /* Stores a number specifically for use by third party trace code. */#endif#if ( configUSE_MUTEXES 1 )UBaseType_t uxBasePriority; /* 创建任务时设置的优先级 */UBaseType_t uxMutexesHeld;#endif#if( configUSE_TASK_NOTIFICATIONS 1 )volatile uint32_t ulNotifiedValue;volatile uint8_t ucNotifyState;#endif} tskTCB;/* The old tskTCB name is maintained above then typedefed to the new TCB_t name
below to enable the use of older kernel aware debuggers. */
typedef tskTCB TCB_t;动态创建–函数调用关系如下 3.4.1 创建新任务prvInitialiseNewTask
// 3.1 获取栈顶地址并做向下做8字节对齐pxTopOfStack pxNewTCB-pxStack ( ulStackDepth - ( uint32_t ) 1 );pxTopOfStack ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) ( ~( ( uint32_t ) 0x0007 ) ) );
// 3.2 将任务的名字存在TCB中pxNewTCB-pcTaskName[ x ] pcName[ x ];
// 3.3 初始化优先级pxNewTCB-uxPriority uxPriority;
// 3.4 初始化链表vListInitialiseItem( ( pxNewTCB-xStateListItem ) );vListInitialiseItem( ( pxNewTCB-xEventListItem ) );
// 3.6 设置链表的拥有者listSET_LIST_ITEM_OWNER( ( pxNewTCB-xStateListItem ), pxNewTCB );listSET_LIST_ITEM_VALUE( ( pxNewTCB-xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );listSET_LIST_ITEM_OWNER( ( pxNewTCB-xEventListItem ), pxNewTCB );
// 3.5 初始化任务栈pxNewTCB-pxTopOfStack pxPortInitialiseStack(...)3.4.2 初始化任务栈pxPortInitialiseStack 3.4.3 放入就绪链表prvAddNewTaskToReadyList
prvAddNewTaskToReadyList(...)// 进入临界段taskENTER_CRITICAL();// 全局任务计时器加一操作uxCurrentNumberOfTasks;// 如果 pxCurrentTCB 为空则将 pxCurrentTCB 指向新创建的任务if( pxCurrentTCB NULL ){pxCurrentTCB pxNewTCB;// 如果是第一次创建任务则需要初始化任务相关的列表if( uxCurrentNumberOfTasks ( UBaseType_t ) 1 ){prvInitialiseTaskLists();}}else{ // 如果 pxCurrentTCB 不为空//则根据任务的优先级将 pxCurrentTCB 指向最高优先级任务的 TCBif( pxCurrentTCB-uxPriority pxNewTCB-uxPriority ){pxCurrentTCB pxNewTCB;}}
prvAddTaskToReadyList( pxNewTCB );
taskEXIT_CRITICAL();
3.4.3.1 就绪链表初始化prvInitialiseTaskLists
static void prvInitialiseTaskLists( void )
{UBaseType_t uxPriority;for( uxPriority ( UBaseType_t ) 0U; uxPriority ( UBaseType_t ) configMAX_PRIORITIES; uxPriority ){vListInitialise( ( pxReadyTasksLists[ uxPriority ] ) );}
}// 链表初始化函数vListInitialise
void vListInitialise( List_t * const pxList )
{pxList-pxIndex ( ListItem_t * ) ( pxList-xListEnd );pxList-xListEnd.xItemValue portMAX_DELAY;pxList-xListEnd.pxNext ( ListItem_t * ) ( pxList-xListEnd );pxList-xListEnd.pxPrevious ( ListItem_t * ) ( pxList-xListEnd );pxList-uxNumberOfItems ( UBaseType_t ) 0U;
} 3.4.3.2 任务放入对应的就绪链表prvAddTaskToReadyList
#define prvAddTaskToReadyList( pxTCB ) \traceMOVED_TASK_TO_READY_STATE( pxTCB );\/* 根据优先级将优先级位图表 uxTopReadyPriority 中对应的位置位*/ \taskRECORD_READY_PRIORITY( ( pxTCB )-uxPriority ); \/* 根据优先级将任务插入到就绪链表 pxReadyTasksLists[]*/ \vListInsertEnd( ( pxReadyTasksLists[ ( pxTCB )-uxPriority ] ), \( ( pxTCB )-xStateListItem ) ); \tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )任务控制块里面有一个 xStateListItem 成员数据类型为 ListItem_t我们将任务插入到就绪链表里面就是通过将任务控制块的 xStateListItem 这个链表项插入到就绪链表中来实现的。 4. 常用任务函数
4.1 任务挂起
4.1.1 vTaskSuspend()
仅仅是将任务进入挂起态其内部的资源都会保留下来同时也不会参与系统中任务的调度。当调用恢复函数的时候整个任务立即从挂起态进入就绪态并且参与任务的调度如果该任务的优先级是当前就绪态优先级最高的任务那么立即会按照挂起前的任务状态继续执行该任务。即挂起任务之前是什么状态都会被系统保留下来在恢复的瞬间继续执行。
void vTaskSuspend( TaskHandle_t xTaskToSuspend )执行流程
获取被挂起的任务的TCBpxTCB prvGetTCBFromHandle( xTaskToSuspend );从就绪/阻塞列表中删除任务并放入挂起列表中。
if( uxListRemove( ( pxTCB-xStateListItem ) ) ( UBaseType_t ) 0 )
{taskRESET_READY_PRIORITY( pxTCB-uxPriority );
}如果任务在等待事件也从等待事件列表中移除
if( listLIST_ITEM_CONTAINER( ( pxTCB-xEventListItem ) ) ! NULL )
{( void ) uxListRemove( ( pxTCB-xEventListItem ) );
}将任务添加到挂起列表中
vListInsertEnd( xSuspendedTaskList, ( pxTCB-xStateListItem ) );挂起的任务是 pxCurrentTCB 调度器在运行挂起的任务是 pxCurrentTCB 立即切换任务portYIELD()调度器没运行pxCurrentTCB指向的任务被挂起则必须并将其指向其他任务 切换任务空闲任务或其他就绪任务。vTaskSwitchContext();
4.1.2 vTaskSuspendAll()
将所有的任务都挂起挂起所有的任务就是挂起调度器。调度器被挂起后则不能进行上下文切换但中断还是使能的。当调度器被挂起的时候如果有任务需要进行上下文切换那么这个任务就会被挂起在调度器恢复之后才执行切换任务。
4.2 任务恢复函数
4.2.1 vTaskResume()
就是让挂起的任务重新进入就绪状态恢复的任务会保留挂起前的状态信息在恢复的时候根据挂起时的状态继续运行。如果被恢复任务在所有就绪态任务中处于最高优先级列表的第一位那么系统就会进行任务的上下文切换。
void vTaskResume( TaskHandle_t xTaskToResume ) 执行流程
进入临界区 taskENTER_CRITICAL(); 将要恢复的任务从挂起链表中删除并添加到就绪链表中去
( void ) uxListRemove( ( pxTCB-xStateListItem ) );
prvAddTaskToReadyList( pxTCB );如果刚刚恢复的任务的优先级pxTCB-uxPriority大于当前任务的优先级pxCurrentTCB-uxPriority则进行任务切换
if( pxTCB-uxPriority pxCurrentTCB-uxPriority )
{taskYIELD_IF_USING_PREEMPTION();
} #define taskYIELD_IF_USING_PREEMPTION() \portYIELD_WITHIN_API()#define portYIELD() \
{\/* Set a PendSV to request a context switch. */ \portNVIC_INT_CTRL_REG portNVIC_PENDSVSET_BIT; \__dsb( portSY_FULL_READ_WRITE );\__isb( portSY_FULL_READ_WRITE );\
}退出临界区 taskEXIT_CRITICAL();
4.2.2 xTaskResumeFromISR()
xTaskResumeFromISR()与 vTaskResume()一样都是用于恢复被挂起的任务不一样的
是 xTaskResumeFromISR() 专 门 用 在 中 断 服 务 程 序 中 。 无 论通过调用一次或多次vTaskSuspend()函数而被挂起的任务也只需调用一次 xTaskResumeFromISR()函数即可解挂。
BaseType_t xTaskResumeFromISR( TaskHandle_t xTaskToResume ) 关中断调度器是否被挂起 否 如果刚刚恢复的任务的优先级pxTCB-uxPriority大于当前任务的优先级pxCurrentTCB-uxPriority则进行任务切换在这里只是记录要进行切换
if( pxTCB-uxPriority pxCurrentTCB-uxPriority )
{xYieldRequired pdTRUE;
}2. 将要恢复的任务从挂起链表中删除并添加到就绪链表中去( void ) uxListRemove( ( pxTCB-xStateListItem ) );
prvAddTaskToReadyList( pxTCB );2. 是1. 任务被添加到待处理的就绪列表中直到调度器恢复再进行任务的处理。vListInsertEnd( ( xPendingReadyList ), ( pxTCB-xEventListItem ) );开中断返回 xYieldRequired 结果在外部选择是否进行任务切换。
使用 xTaskResumeFromISR()的时候有几个需要注意的地方
当函数的返回值为 pdTRUE 时恢复运行的任务的优先级等于或高于正在运行的任务 表明在中断服务函数退出 后 必须进行一次上下文切换 使用 portYIELD_FROM_ISR()进行上下文切换。当函数的返回值为 pdFALSE 时恢复 运行的任务的优先级低于当前正在运行的任务表明在中断服务函数退出后不需 要进行上下文切换。xTaskResumeFromISR() 通常被认为是一个危险的函数因为它的调用并非是固定 的中断可能随时来来临。所以xTaskResumeFromISR()不能用于任务和中断间 的同步如果中断恰巧在任务被挂起之前到达这就会导致一次中断丢失任务 还没有挂起调用 xTaskResumeFromISR()函数是没有意义的只能等下一次中 断。这种情况下可以使用信号量或者任务通知来同步就可以避免这种情况。
4.3 任务删除vTaskDelete
void vTaskDelete( TaskHandle_t xTaskToDelete );自杀vTaskDelete( NULL );B自杀空闲任务收尸
被杀别的任务执行vTaskDelete(pvTaskCode);pvTaskCode是自己的句柄。A杀BA给B收尸释放栈和TCB
注被删除的任务其在任务创建时由内核分配的存储空间会由空闲任务释放。如果有应用程序调用vTaskDelete()必须保证空闲任务获取一定的微控制器处理时间。任务代码自己分配的内存是不会自动释放的因此删除任务前应该将这些内存释放。
void vTaskDelete( TaskHandle_t xTaskToDelete ) 进入临界区 taskENTER_CRITICAL();获取任务控制块如果 xTaskToDelete 为 null 则删除任务自身
pxTCB prvGetTCBFromHandle( xTaskToDelete );将任务从就绪列表中移除 如果删除后就绪列表的长度为 0当前没有就绪的任务应该调用 taskRESET_READY_PRIORITY()函数清除任务的最高就绪优先级变量 uxTopReadyPriority 中的位。
if( uxListRemove( ( pxTCB-xStateListItem ) ) ( UBaseType_t ) 0 )
{taskRESET_READY_PRIORITY( pxTCB-uxPriority );
}如果当前任务在等待事件那么将任务从事件列表中移除
if( listLIST_ITEM_CONTAINER( ( pxTCB-xEventListItem ) ) ! NULL )
{( void ) uxListRemove( ( pxTCB-xEventListItem ) );
}删除的任务是任务自身的话这不能在任务本身内完成因为需要上下文切换到另一个任务。 将任务放在结束列表中 xTasksWaitingTermination空闲任务会检查结束列表并在空闲任务中释放删除任务的 控制块和已删除任务的堆栈内存。记录有多少个任务需要释放内存以便空闲任务知道有多少个已删除的任务需要进行内存释放
if( pxTCB pxCurrentTCB )
{vListInsertEnd( xTasksWaitingTermination, ( pxTCB-xStateListItem ) ); uxDeletedTasksWaitingCleanUp; 3. 需要发起一次任务切换if( xSchedulerRunning ! pdFALSE )
{if( pxTCB pxCurrentTCB ){configASSERT( uxSchedulerSuspended 0 );portYIELD_WITHIN_API();}
}删除的是其他任务块 当前任务数减一释放任务块与堆栈空间重置下一个任务的解除阻塞时间。重新计算一下还要多长时间执行下一个任务如果下个任务的解锁刚好是被删除的任务那么这就是不正确的因为 删除的任务对调度器而言是不可见的所以调度器是无法对删除的任务进行调度所以要 重新从延时列表中获取下一个要解除阻塞的任务。调用 prvResetNextTaskUnblockTime()函 数从延时列表的头部来获取下一个要解除任务的 TCB延时列表按延时时间排序。
--uxCurrentNumberOfTasks;
prvDeleteTCB( pxTCB );
prvResetNextTaskUnblockTime();退出临界区taskEXIT_CRITICAL();
4.3.1 prvCheckTasksWaitingTermination()
static List_t xTasksWaitingTermination {0};空闲任务会检查结束列表 xTasksWaitingTermination 并且释放对应删除任务的内存空间空闲 任 务 调 用 prvCheckTasksWaitingTermination() 函数进行这些相应操作该函数是FreeRTOS 内部调用的函数在 prvIdleTask 中调用
while( uxDeletedTasksWaitingCleanUp ( UBaseType_t ) 0U )
{taskENTER_CRITICAL();{/* 获取对应任务控制块 */pxTCB ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( xTasksWaitingTermination ) );/* 将任务从列表中删除 */( void ) uxListRemove( ( pxTCB-xStateListItem ) );/* 当前任务个数减一 */--uxCurrentNumberOfTasks;/* uxDeletedTasksWaitingCleanUp 的值减一直到为 0 退出循环 */--uxDeletedTasksWaitingCleanUp;}taskEXIT_CRITICAL();/* 删除任务控制块与堆栈 */prvDeleteTCB( pxTCB );
}4.3.2 prvResetNextTaskUnblockTime
static void prvResetNextTaskUnblockTime( void )
{
TCB_t *pxTCB;if( listLIST_IS_EMPTY( pxDelayedTaskList ) ! pdFALSE ){xNextTaskUnblockTime portMAX_DELAY;}else{( pxTCB ) ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( pxDelayedTaskList );xNextTaskUnblockTime listGET_LIST_ITEM_VALUE( ( ( pxTCB )-xStateListItem ) );}
}4.4 任务延时函数
4.4.1 vTaskDelay()和vTaskDelayUntil() 的区别
vTaskDelayUntil() 与 vTaskDelay () 一 样都是用来实现任务的延时。
vTaskDelay ()的延时是相对的是不确定的它的延时是等 vTaskDelay ()调用完毕后开始计算的。并且 vTaskDelay ()延时的时间到了之后如果有高优先级的任务或者中断正在执行被延时阻塞的任务并不会马上解除阻塞所有每次执行任务的周期并不完全确定。vTaskDelayUntil()延时是绝对的适用于周期性执行的任务。(*pxPreviousWakeTime xTimeIncrement)时间到达后vTaskDelayUntil()函数立刻返回如果任务是最高优先级的那么任务会立马解除阻塞所以说 vTaskDelayUntil()函数的延时是绝对性的。 4.4.2 vTaskDelay()
void vTaskDelay( const TickType_t xTicksToDelay )vTaskDelay()用于阻塞延时调用该函数后任务将进入阻塞状态进入阻塞态的任务将让出 CPU 资源。延时的时长由形参 xTicksToDelay 决定单位为系统节拍周期 比如系统的时钟节拍周期为 1ms那么调用vTaskDelay(1)的延时时间则为1ms。
vTaskDelay()延时是相对性的延时它指定的延时时间是从调用 vTaskDelay()结束后开始计算的经过指定的时间后延时结束。比如 vTaskDelay(100) 从调用 vTaskDelay()结束后任务进入阻塞状态经过 100 个系统时钟节拍周期后任务解除阻塞。因此vTaskDelay()并不适用与周期性执行任务的场合。此外其它任务和中断活动 也会影响到 vTaskDelay()的调用比如调用前高优先级任务抢占了当前任务进而影响到任务的下一次执行的时间。
#if ( INCLUDE_vTaskDelay 1 )void vTaskDelay( const TickType_t xTicksToDelay ){BaseType_t xAlreadyYielded pdFALSE;/* 延时时间要大于 0 个 tick否则会进行强制切换任务 */if( xTicksToDelay ( TickType_t ) 0U ){configASSERT( uxSchedulerSuspended 0 );vTaskSuspendAll();{traceTASK_DELAY();/* 将任务添加到延时列表中去 */prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );}xAlreadyYielded xTaskResumeAll();}/* 强制切换任务将 PendSV 的 bit28 置 1 */if( xAlreadyYielded pdFALSE ){portYIELD_WITHIN_API();}}4.4.2.1 prvAddCurrentTaskToDelayedList()
xTicksToWait 表示要延时多长时间单位为系统节拍周期。xCanBlockIndefinitely 表示是否可以永久阻塞如果 pdFALSE 表 示不允许永久阻塞也就是不允许挂起当然任务而如果是 pdTRUE则可以永久阻塞。xConstTickCount 获取当前调用延时函数的时间点
在将任务添加到延时列表之前从就绪列表中删除任务是否永久阻塞 是 直接将任务添加到挂起列表而不是延时列表 否 计算唤醒任务的时间
xTimeToWake xConstTickCount xTicksToWait; 2. 列表项按唤醒时间顺序插入listSET_LIST_ITEM_VALUE( ( pxCurrentTCB-xStateListItem ), xTimeToWake );3. 唤醒时间xTimeToWake是否溢出1. 溢出了 添加到延时溢出列表中 vListInsert( pxOverflowDelayedTaskList, ( pxCurrentTCB-xStateListItem ) );2. 没有溢出1. 添加到延时列表中 vListInsert( pxDelayedTaskList, ( pxCurrentTCB-xStateListItem ) );2. 如果进入阻塞状态的任务被放置在被阻塞任务列表的头部也就是下一个要唤醒的任务就是当前任务那么就需要更新 xNextTaskUnblockTime 的值if( xTimeToWake xNextTaskUnblockTime )
{xNextTaskUnblockTime xTimeToWake;
}static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{TickType_t xTimeToWake;const TickType_t xConstTickCount xTickCount;/* 在将任务添加到阻止列表之前从就绪列表中删除任务因为两个列表都使用相同的列表项。 */if( uxListRemove( ( pxCurrentTCB-xStateListItem ) ) ( UBaseType_t ) 0 ){portRESET_READY_PRIORITY( pxCurrentTCB-uxPriority, uxTopReadyPriority );}#if ( INCLUDE_vTaskSuspend 1 ){if( ( xTicksToWait portMAX_DELAY ) ( xCanBlockIndefinitely ! pdFALSE ) ){/* 支持挂起则将当前任务挂起直接将任务添加到挂起列表而不是延时列表*/vListInsertEnd( xSuspendedTaskList, ( pxCurrentTCB-xStateListItem ) );}else{/* 计算唤醒任务的时间 */xTimeToWake xConstTickCount xTicksToWait;/* 列表项将按唤醒时间顺序插入 */listSET_LIST_ITEM_VALUE( ( pxCurrentTCB-xStateListItem ), xTimeToWake );if( xTimeToWake xConstTickCount ){/* 唤醒时间如果溢出了则会添加到延时溢出列表中 */vListInsert( pxOverflowDelayedTaskList, ( pxCurrentTCB-xStateListItem ) );}else{/* 没有溢出添加到延时列表中 */vListInsert( pxDelayedTaskList, ( pxCurrentTCB-xStateListItem ) );/* 如果进入阻塞状态的任务被放置在被阻塞任务列表的头部也就是下一个要唤醒的任务就是当前任务那么就需要更新xNextTaskUnblockTime 的值 */if( xTimeToWake xNextTaskUnblockTime ){xNextTaskUnblockTime xTimeToWake;}}}}
}4.4.3 vTaskDelayUntil()
vTaskDelayUntil()绝对延时函数 这个绝对延时常用于较精确的周期运行任务比如我有一个任务希望它以固定频率定期执行而不受外部的影响任务从上一次运行开始到下一次运行开始的时间间隔是绝对的而不是相对的。
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{TickType_t xTimeToWake;BaseType_t xAlreadyYielded, xShouldDelay pdFALSE;configASSERT( pxPreviousWakeTime );configASSERT( ( xTimeIncrement 0U ) );configASSERT( uxSchedulerSuspended 0 );vTaskSuspendAll();{/* 获取开始进行延时的时间点 */const TickType_t xConstTickCount xTickCount;/* 计算延时到达的时间也就是唤醒任务的时间 */xTimeToWake *pxPreviousWakeTime xTimeIncrement;/* pxPreviousWakeTime 中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了*/if( xConstTickCount *pxPreviousWakeTime ){/* 如果唤醒的时间小于上次唤醒时间并且唤醒时间大于开始计时的时间这样子就是相当于没有溢出也就是保了证周期性延时时间大于任务主体代码的执行时间*/if( ( xTimeToWake *pxPreviousWakeTime ) ( xTimeToWake xConstTickCount ) ){xShouldDelay pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}else{/* 只是唤醒时间溢出的情况,或者都没溢出保证了延时时间大于任务主体代码的执行时间*/if( ( xTimeToWake *pxPreviousWakeTime ) || ( xTimeToWake xConstTickCount ) ){xShouldDelay pdTRUE;}else{mtCOVERAGE_TEST_MARKER();}}/* 更新上一次的唤醒时间 */*pxPreviousWakeTime xTimeToWake;if( xShouldDelay ! pdFALSE ){traceTASK_DELAY_UNTIL( xTimeToWake );/* prvAddCurrentTaskToDelayedList()函数需要的是阻塞时间, 而不是唤醒时间因此减去当前的滴答计数。 */prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );}else{mtCOVERAGE_TEST_MARKER();}}xAlreadyYielded xTaskResumeAll();/* 强制执行一次上下文切换 */if( xAlreadyYielded pdFALSE ){portYIELD_WITHIN_API();}else{mtCOVERAGE_TEST_MARKER();}
}#endif /* INCLUDE_vTaskDelayUntil */xTimeIncrement任务周期时间。
pxPreviousWakeTime上一次唤醒任务的时间点。
xTimeToWake本次要唤醒任务的时间点。
xConstTickCount进入延时的时间点。
溢出情况
如果唤醒的时间小于上次唤醒时间并且唤醒时间大于开始计时的时间这样子就是相当于没有溢出也就是保了证周期性延时时间大于任务主体代码的执行时间
if( ( xTimeToWake *pxPreviousWakeTime ) ( xTimeToWake xConstTickCount ) )
{xShouldDelay pdTRUE;
}只是唤醒时间溢出的情况,或者都没溢出保证了延时时间大于任务主体代码的执行时间
if( ( xTimeToWake *pxPreviousWakeTime ) ||( xTimeToWake xConstTickCount ) )
{xShouldDelay pdTRUE;
}从图 16-2、图 16-3 与图 16-4 可以看出无论是溢出还是没有溢出都要求在下次唤醒任务之前当前任务主体代码必须被执行完。也就是说任务执行的时间必须小于任务周期时间 xTimeIncrement总不能存在任务周期为 10ms 的任务其主体代码执行时间为 20ms这样子根本执行不完任务主体代码。计算的唤醒时间合法后就将当前任务加入延时列表同样延时列表也有两个。每次产生系统节拍中断都会检查这两个延时列表查看延时的任务是否到期如果时间到则将任务从延时列表中删除重新加入就绪列表任务从阻塞态变成就绪态如果此时的任务优先级是最高的则会触发一次上下文切换。
4.4.3.1 绝对延时函数 vTaskDelayUntil()实例
void vTaskA( void * pvParameters )
{/* 用于保存上次时间。调用后系统自动更新 */ static portTickType PreviousWakeTime; /* 设置延时时间将时间转为节拍数 */ const portTickType TimeIncrement pdMS_TO_TICKS(1000); /* 获取当前系统时间 */ PreviousWakeTime xTaskGetTickCount(); while (1){/* 调用绝对延时函数,任务时间间隔为 1000 个 tick */ vTaskDelayUntil( PreviousWakeTimeTimeIncrement ); }// ....// 这里为任务主体代码// ....
}注意在使用的时候要将延时时间转化为系统节拍在任务主体之前要调用延时函数。
任务会先调用 vTaskDelayUntil()使任务进入阻塞态等到时间到了就从阻塞中解除然后执行主体代码任务主体代码执行完毕。会继续调用 vTaskDelayUntil()使任务进入阻塞态然后就是循环这样子执行。即使任务在执行过程中发生中断那么也不会影响这个任务的运行周期仅仅是缩短了阻塞的时间而已到了要唤醒的时间依旧会将任务唤醒。
5. 任务的设计要点
任务的优先级信息任务与中断的处理任务的运行时间、逻辑、状态等都要知道才能设计出好的系统 所以在设计的时候需要根据需求制定框架。在设计之初就应该考虑下面几点因素任务运行的上下文环境、任务的执行时间合理设计。
FreeRTOS 中程序运行的上下文包括
中断服务函数。普通任务。空闲任务。
中断服务函数
中断服务函数是一种需要特别注意的上下文环境它运行在非任务的执行环境下一 般为芯片的一种特殊运行模式也被称作特权模式在这个上下文环境中不能使用挂起当前任务的操作不允许调用任何会阻塞运行的 API 函数接口。
另外需要注意的是中 断服务程序最好保持精简短小快进快出一般在中断服务函数中只做标记事件的发生 然后通知任务让对应任务去执行相关处理因为中断服务函数的优先级高于任何优先级 的任务如果中断处理时间过长将会导致整个系统的任务无法正常运行。所以在设计的 时候必须考虑中断的频率、中断的处理时间等重要因素以便配合对应中断处理任务的工 作。
任务
任务看似没有什么限制程序执行的因素似乎所有的操作都可以执行。
但是做为一个优先级明确的实时系统如果一个任务中的程序出现了死循环操作此处的死循环是指没有阻塞机制的任务循环体那么比这个任务优先级低的任务都将无法执行当然也包括了空闲任务因为死循环的时候任务不会主动让出 CPU低优先级的任务是不可能得到 CPU 的使用权的而高优先级的任务就可以抢占 CPU。
这个情况在实时操作系统中是必须 注意的一点所以在任务中不允许出现死循环。如果一个任务只有就绪态而无阻塞态势 必会影响到其他低优先级任务的执行所以在进行任务设计时就应该保证任务在不活跃 的时候任务可以进入阻塞态以交出 CPU 使用权这就需要我们自己明确知道什么情况下 让任务进入阻塞态保证低优先级任务可以正常运行。在实际设计中一般会将紧急的处理事件的任务优先级设置得高一些。
空闲任务
空闲任务idle 任务是 FreeRTOS 系统中没有其他工作进行时自动进入的系统任务。
因为处理器总是需要代码来执行——所以至少要有一个任务处于运行态。FreeRTOS 为了保证这一点当调用 vTaskStartScheduler()时调度器会自动创建一个空闲任务空闲任务是一个非常短小的循环。
用户可以通过空闲任务钩子方式在空闲任务上钩入自己的功能函数。通常这个空闲任务钩子能够完成一些额外的特殊功能例如系统运行状态的指示系统省电模式等。
除了空闲任务钩子FreeRTOS 系统还把空闲任务用于一些其他的功能比如当系统删除一个任务或一个动态任务运行结束时在执行删除任务的时候并不会释放任务的内存空间只会将任务添加到结束列表中真正的系统资源回收工作在空闲任务完成空闲任务是唯一一个不允许出现阻塞情况的任务因为 FreeRTOS 需要保证系统永远都有一个可运行的任务。
对于空闲任务钩子上挂接的空闲钩子函数它应该满足以下的条件
永远不会挂起空闲任务不应该陷入死循环需要留出部分时间用于系统处理系统资源回收。
任务的执行时间
任务的执行时间一般是指两个方面一是任务从开始到结束的时间二是任务的周期。
在系统设计的时候这两个时间候我们都需要考虑例如对于事件 A 对应的服务任务Ta系统要求的实时响应指标是 10ms而 Ta 的最大运行时间是 1ms那么 10ms 就是任务 Ta 的周期了1ms 则是任务的运行时间简单来说任务 Ta 在 10ms 内完成对事件 A 的响应即可。此时系统中还存在着以 50ms 为周期的另一任务 Tb它每次运行的最大时间长度是 100us。在这种情况下即使把任务 Tb 的优先级抬到比 Ta 更高的位置对系统的实时性指标也没什么影响因为即使在 Ta 的运行过程中Tb 抢占了 Ta 的资源等到 Tb 执行完毕消耗的时间也只不过是 100us还是在事件 A 规定的响应时间内(10ms)Ta 能够安全完成对事件 A 的响应。但是假如系统中还存在任务 Tc其运行时间为 20ms假如将 Tc的优先级设置比 Ta 更高那么在 Ta 运行的时候突然间被 Tc 打断等到 Tc 执行完毕那 Ta 已经错过对事件 A10ms的响应了这是不允许的。所以在我们设计的时候必须考虑任务的时间一般来说处理时间更短的任务优先级应设置更高一些。