堆内存管理
- 有五种内存分配方式
- 常用的为heap_4方式
任务管理
-  任务不能以任何方式实现函数返回,可以在任务的死循环外加上xTaskDelete( ) 
-  创建任务:xTaskCreate( ) - 任务堆栈的大小,空闲任务的最小是configMINIMAL_STACK_SIZE,其他任务不能比他小
- 任务优先级,0~configMAX_PRIORITIES-1 ,数字越小,优先级越低 。 - configMAX_PRIORITIES尽量保证必要最小值 ,越大消耗RAM越多
 
 
-  滴答中断:一般调度器都是基于时间片的抢占系统 - 滴答中断频率:configTICK_RATE_HZ,要与滴答定时器的频率匹配
- 典型值为100HZ,即10ms的时间片,但是要注意vTaskDelay函数的延时
- pdMS_TO_TICKS,使得100HZ的滴答只能延时10ms的整数倍时间,所以我感觉1000HZ也还可以
 
-  任务状态 - 阻塞状态: - 时间性事件:例如延时vTaskDelay
- 同步事件:进入阻塞等待数据到达,例如:队列、二进制和计数量、互斥量、事件组、任务通知等。如果设置10ms,10ms内数据到达,或者超过10ms数据没到达,都会离开阻塞状态
 
- 暂停状态:进入暂停唯一方法就是vTaskSuspend,退出方法就是vTaskResume
- 就绪状态:准备好运行,但是还没运行
 
- 阻塞状态: 
-  阻塞延时:vTaskDelay和vTaskDelayUntil 
-  空闲任务 - 调度器启动时会自动创建空闲任务,当其他任务都阻塞时也有空闲任务时刻运行
- 空闲任务钩子:vApplicationIdleHook - ***功能:***执行低优先级、后台或连续处理函数;测量空闲处理能力;将处理器置于低功耗模式
- **注意:**钩子函数绝对不能阻塞和暂停;如果使用了vTaskDelete,空闲任务负责清理被删除任务的资源,还要确保空闲任务不会被饿死
 
 
-  任务优先级:设置任务优先级:vTaskPrioritySet,获取任务优先级:vTaskPriorityGet 
-  **任务删除:**vTaskDelete 
-  **线程本地存储:**vTaskSetThreadLocalStoragePointer和pvTaskGetThreadLocalStoragePointer,根据任务数组的索引设置和读取数据 
-  调度算法*:*** - 时间片优先的抢占式调度:用的最多,但注意资源不能被多个任务同时访问,否则可能破坏资源 - 一个任务优先级高于运行状态任务会抢占运行状态任务
- 同等优先级任务使用时间片共享处理时间
- 自动会执行最高优先级就绪态的任务
 
- 不含时间片的抢占式调度 - 调度器转换新任务只有两种可能:高优先级任务就绪抢占,运行状态任务阻塞或者暂停
 
- 协同调度 - 只有运行态任务阻塞或者使用taskYIELD进行任务让步,才会发生切换
 
 
- 时间片优先的抢占式调度:用的最多,但注意资源不能被多个任务同时访问,否则可能破坏资源 
队列管理
-  FreeRTOS通过复制实现队列的方式,优点是可以直接发送栈变量到队列,不需要先分配缓冲区存放数据,发送和接收任务完全脱钩,复制实现队列不妨碍引用实现队列。 
-  **多任务访问:**队列本身就是对象,可以被任意个任务或者ISR访问 
-  队列读取、写入阻塞: - 当任务从队列读取或者写入数据时,可以选择性阻塞时间,当阻塞状态接收到数据或者阻塞时间超时,任务都会转移到就绪状态。
- 如果有多个任务等待队列数据,只会有一个任务会解除阻塞,最高优先级先解除,相同优先级则等待时间最长的先解除
 
-  **队列创建:**xQueueCreate 
-  **队尾插入:**xQueueSendToBack 
-  **队首插入:**xQueueSendToFront 
-  **队列接收:**xQueueReceive 
-  **数据量查询:**uxQueueMessagesWaiting 
-  从多个来源接收数据: - 简单:可以使用队列传输结构体,结构体包含来源和数据
 
-  处理大数据和可变大小数据:最好使用队列传输数据的指针 - 被指向的RAM的所有者必须明确定义
- 被指向的RAM保持有效
 
-  **队列集:**能够从多个来源接收数据,但比结构体方法更繁琐效率更低 - 创建队列集
- 队列添加队列集
- 队列集读取数据
 
-  **队列集创建:**xQueueCreateSet 
-  **队列添加队列集:**xQueueAddToSet 
-  **队列集读取队列:**xQueueSelectFromSet 
-  **队列集移除队列:**xQueueRemoveFromSet 
-  ***队列创建邮箱:***邮箱用来指长度为1的队列 - 队列将数据从一个任务发送到另一个任务,发送者放置数据,接收者读取后移除数据
- 邮箱将数据从一个任务发送到另一个任务,发送者放置新数据并覆盖原数据,接收者读取数据并不移除
 
-  **发送并覆盖邮箱数据:**xQueueOverwrite 
-  **接收并不移除数据:**xQueuePeek 
软件定时器
- **软件定时器回调函数形式:**void ATimerCallback(TimerHandle_t xTimer) - 回调函数应该短小精悍,不能阻塞
- 可以多个定时器使用同一回调函数,但是需要在函数里判断定时器句柄
- 也可以每个定时器使用不同回调函数
 
- **定时器周期:**指从启动软件定时器到执行软件定时器回调函数之间的时间 - 一次性定时器:只执行一次回调函数
- 自动重载:到期后自动重启,回调函数周期执行
- 到期时间是从发送“启动定时器”命令到定时器命令队列的时间开始计算,而不是守护任务从命令队列收到“启动定时器”命令的时间开始计算的
 
- 定时器状态: - 休眠:休眠的定时器存在,可以通过句柄调用,回调函数不会执行 - xTimerCreate创建后就是休眠状态
- 运行转休眠,
 
- 运行:到规定的周期时间,自动执行回调函数 - 调用xTimerStart,xTimerReset,xTimerChangePeriod都可以转换为运行态
 
 
- 休眠:休眠的定时器存在,可以通过句柄调用,回调函数不会执行 
- RTOS守护任务 - 在调度器启动时自动创建,优先级和栈大小由常量设置
- 软件定时器API将命令从调用函数发送到守护任务,在守护任务的“定时器命令队列”上
- 守护任务的调度:只有当守护任务为能够运行的最高优先级时,才会处理命令和执行定时器回调函数
- 发送到定时器命令队列的命令包含时间戳,确保启动的定时器是从发送“启动定时器”命令到定时器命令队列的时间开始计算
 
- **创建软件定时器:**xTimerCreate
- 启动软件定时器:xTimerStart
- **停止定时器:**xTimerStop
- **删除定时器:**xTimerDelete
- **定时器ID:**是一个标签值,可以随意使用,创建软件定时器时,会给ID分配初始值 - vTimerSetTimerID,创建定时器时,会为软件定时器分配一个标识符 (ID), 此函数更改此标识符。
- pvTimerGetTimerID,返回分配给软件计时器的 ID
 
- **更改软件定时器周期:**xTimerChangePeriod
- **重置软件定时器:**xTimerReset
中断管理
-  任务是软件功能,和运行的硬件无关。中断是硬件功能,最低优先级的中断可以抢断最高优先级任务,反之则不可以。 
-  专门用于ISR的API函数,在名称后添加FromISR,不要在ISR中调用没有后缀的函数 
-  **使用单独中断安全函数的缺点:**有需要在ISR中调用第三方函数,但第三方函数用了正常的FreeRTOS API函数 - 将中断处理推迟给任务,就可以在任务中调用
- 如果有的话,将API改为FromISR结尾的函数
 
-  xHigherPriorityTaskWoken参数 -  中断结束时,会回到打断处继续执行,但如果中断期间,有了更高优先级任务就绪,就应该执行更高优先级任务,而不是返回原点继续执行。如果不管的话,更高优先级任务将保持就绪状态,直到不在中断时的下一次调度器运行。 - 切换更高优先级任务不会在中断内自动发生,设置了xHigherPriorityTaskWoken变量通知应该上下文切换
- FromISR结尾的函数有xHigherPriorityTaskWoken变量,用于此目的,taskYIELD函数是在任务中请求任务切换的函数。
- portYIELD_FROM_ISR是taskYIELD函数的中断版本
 
-  示例代码如下: void vTimerISR( void * pvParameters ) {BaseType_t xHigherPriorityTaskWoken = pdFALSE;xHigherPriorityTaskWoken = pdFALSE;xSemaphoreGiveFromISR( xSemaphore, &xHigherPriorityTaskWoken );/* Yield if xHigherPriorityTaskWoken is true. The actual macro used here is port specific. */portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); }
 
-  
-  推迟中断处理 - 中断所需的其他处理工作通常可以在任务中运行,所以可以将中断的工作推迟到任务
- 以下情况强烈建议推迟到任务 - 中断所需的处理并不简单
- 任务处理能方便执行ISR内部无法执行的操作
- 中断处理不确定,不知道处理工作需要多长时间
 
 
-  二进制信号量:能够有效使任务和中断同步,可以认为使长度为1的队列 -  设置推迟任务的优先级确保,该任务可以抢占系统其他任务 
-  在ISR实现中调用portYIELD_FROM_ISR,确保ISR返回到推迟中断处理任务 
-  创建二值信号量: xSemaphoreCreateBinary 
-  **释放信号量:**xSemaphoreGive 
-  **获取信号量:**xSemaphoreTake 
 
-  
-  **计数信号量:**可用于资源管理,计数事件 - **创建计数信号量:**xSemaphoreCreateCounting
- **释放信号量:**xSemaphoreGive
- **获取信号量:**xSemaphoreTake
 
-  **推迟工作到守护任务:**xTimerPendFunctionCallFromISR() 
-  中断程序使用队列: - 使用xQueueSendToFront和xQueueSendToBack的ISR版本
- 数据到达频率很高,队列效率不高
- 更高效且适合生产代码方法: - 直接内存访问DMA
- 接收到的字符复制到线程安全的RAM缓冲区
- 直接在ISR内处理接收到的字符,队列只发送处理结果
 
 
-  中断嵌套 - 数字优先级和逻辑优先级:数字是分配给中断优先级的数字,逻辑是描述该中断相较于其他中断的优先级
- configMAX_SYSCALL_INTERRUPT_PRIORITYD,低于此优先级中断可以被管理
- configKERNEL_INTERRUPT_PRIORITY,设置滴答中断的优先级,最低优先级
- 对时间精度要求非常严格的功能,可以考虑使用高于configMAX_SYSCALL_INTERRUPT_PRIORITYD的优先级
- 必须始终configKERNEL_INTERRUPT_PRIORITY设置为尽可能最低的中断优先级
- configMAX_SYSCALL_INTERRUPT_PRIORITYD的数值要注意,如cortex-m不允许设置为0
 
资源管理
-  资源访问导致数据损坏的例子:A任务打印hello,A打印到he,B任务抢占A,打印abort,最终结果就是heabortllo。这是不正确的。 
-  **函数重入:**函数可以安全的在多个任务调用,或既可以从中断也可以在任务使用,那么函数就是重入,也称作线程安全。每个任务都维护自己的栈和硬件寄存器值,除了访问栈上的数据或保存至寄存器的数据外,不访问其他数据,就是重入函数。 
-  **相互排斥:**任务之间共享的资源进行访问时,必须使用相互排斥进行管理。使资源不被共享,被单一程序访问。 
-  **临界区:**taskENTER_CRITICAL和taskEXIT_CRITICAL成对使用。FROM_ISR版本的taskENTER_CRITICAL会有返回值,返回值要传递给taskEXIT_CRITICAL。 - 工作原理是禁用中断,即禁用所有可以管理的中断
- 临界区必须短小,否则对中断响应产生不利响应
- 临界区的嵌套是安全的,Freertos中两个宏定义是改变中断使能状态唯一合法方式
 
-  **暂停调度器:**如果临界区代码过长,可以使用暂停调度器,但恢复调度器操作较慢 - vTaskSuspendAll,暂停调度器
- xTaskrResumeAll,恢复调度器
 
-  **互斥量:**特殊的二进制信号量,获取后必须归还,归还后别的任务才能获取,否则可能死锁。 - 创建互斥量:xSemaphoreCreateMutex
- 获取互斥量:xSemaphoreTake
- 释放互斥量:xSemaphoreGive
 
-  互斥量和优先级密切相关的概念 - **优先级反转:**优先级高的任务等待优先级低的任务释放互斥量,仔细思考资源访问尽量避免。
- **优先级继承:**为了解决互斥量产生的优先级反转,rtos会将持有者的优先级提高至与等待互斥量的任务优先级相同,即继承优先级,归还后优先级恢复原状。不能在中断中使用互斥量。
- **死锁:**两个任务都在等待对方持有的资源而无法继续,充分考虑系统,识别并消除。
- **递归互斥量:**当任务获取了互斥量,但执行过程中有函数继续获取互斥量,导致自己锁死自己。 - 使用递归互斥量来避免,可以被同一任务多次获取,接着执行调用归还后,才能归还。类似成对使用。
- 创建递归互斥量:xSemaphoreCreateRecursiveMutex
- 获取递归互斥量:xSemaphoreTakeRecursive
- 释放递归互斥量:xSemaphoreGiveRecursive
 
 
-  互斥量和任务调度 -  不同优先级任务获取同一互斥量,优先级高的任务先进入运行状态。 
-  如果A,B任务优先级相同,A先获取了互斥量,等到时间片结束切换B,接着回A,释放互斥量,再等到时间片结束,B才能运行 -  推荐的更平等处理时间的例子 
-  // 记录时间 Xtime = xTaskGetTickCount(); // 释放互斥量 xSemaphoreGive // 滴答计数变化时,调用切换 if(xTaskGetTickCount() != Xtime) {taskYIELD(); }
 
-  
 
-  
-  守门人任务:指对任务拥有唯一所有权的任务,其他需要访问资源的任务只能通过守门人任务。 - 方法简单,基本没有反转和死锁风险
 
事件组
-  事件组的特性: - 允许任务阻塞状态下等待一个或多个事件组合发生
- 事件组会解除所有在等待同一个事件或事件组合的任务的阻塞
- 通常可以用一个事件组代替多个二进制信号量
 
-  事件组标志是一个布尔值,每一位都代表一个事件是否发生 - configUSE_16_BIT_TICKS为1,则事件组为16位,但其中的高八位保留,即包含8个可用事件位
- configUSE_16_BIT_TICKS为0,则事件组为32位,但其中的高八位保留,即包含24个可用事件位
 
-  **事件组创建:**xEventGroupCreate 
-  **事件组位设置:**xEventGroupSetBits 
-  **读取事件组位:**xEventGroupWaitBits 
-  **多任务事件组相互同步:**xEventGroupSync 
任务通知
-  任务通知允许任务之间交互,不要单独的通信对象。通过任务通知,任务或ISR可以直接向接收任务发送事件。 
-  通常用来替代二值信号量 
-  优势与劣势: - 任务通知比队列、信号量等操作快得多
- 所需RAM也要小的多
- 无法向ISR发送事件和数据
- 无法启用多个接收任务
- 无法缓冲多个数据项
- 无法向多个任务广播
- 无法在阻塞状态等待发送完成
 
-  **发送任务通知:**xTaskNotify 
-  **发送任务通知的简单版:**xTaskNotifyGive 
-  **接收任务通知:**xTaskNotifyWait 
-  **接收任务通知的简单版:**ulTaskNotifyTake 
低功耗支持
- **与节能有关的宏:**portSUPPRESS_TICKS_AND_SLEEP
开发者支持
- **断言:**cofigASSERT
- **任务状态信息快照:**uxTaskGetSystemState,比较重要的信息主要有 - 到目前为止分配给任务的总运行时间
- 任务剩余的最小堆栈空间量
 
- **提供可读的任务信息ASCII表格:**vTaskList - 很耗费CPU,仅用于调试阶段
 
- **将运行时统计信息格式化为可读表格:**vTaskGetRunTimeStats - 很耗费CPU,仅用于调试阶段
- 还需要实现一个计数器
 
故障排除
- 演示工程添加任务导致演示工程崩溃 - 延时工程的堆空间很精确,没有足够的堆空间
 
- 中断使用API导致应用崩溃 - 使用FROM_ISR结尾的API函数
 
- 有时应用程序在中断服务程序中崩溃 - 中断是否导致了栈溢出
 
- 调度器启动第一个任务时崩溃 - 有些处理器启动调度器哦i之前必须处于特权模式
- 确保移植没问题,中断处理程序没问题
 
- 应用程序在调度器启动前崩溃 - 启动前不允许上下文切换
 
- 调度器暂停或者临界区调用API函数,导致应用程序崩溃 - 调度器暂停不能调用API函数
- 临界区内不能调用API函数