【ACCESS精品源码栏目提醒】:网学会员,鉴于大家对ACCESS精品源码十分关注,论文会员在此为大家搜集整理了“freertos - 中考高考”一文,供大家参考学习!
从基本原理到实现 本文描述FreeRTOS飞拓是如何实现的。
如果你 1 希望修改FreeRTOS源代码 2 移植实时内核到另一个微控制器或者原型板prototyping board 3 第一次接触FreeRTOS希望得到关于它们在操作和实现上的更多信息 这些文档会有用。
本文档分为两个章节: 1. 基本原理和RTOS概念 包括多任务的背景信息和基本实时概念这是为初学者准备的is intended for beginners 2. 从底向上from the bottom up解释实时内核源代码 FreeRTOS实时内核已经移植到许多不同的微控制器架构下。
这份文档是以Atmel AVR为范例因为: 1. AVR架构简单 2. 有免费可用的开发工具 WinAVR GCC development tools. 3. 非常便宜的原型板STK500 prototyping board 在本文的最后还一步一步地详细描述了一个完整的上下文切换context switch。
RTOS基本原理 多任务 调度 上下文切换 实时应用 实时调度 这一节提供一个关于实时和多任务概念的简介。
读下一节之前必须理解这些概念。
多任务Multitasking 在一个操作系统内部内核kernel是最核心的部件。
像Linux那样的操作系统使用的内核从表面上看seemingly允许用户并发simultaneously访问 计算机。
多个用户似乎apparently可以并行concurrently执行多个程序。
在操作系统的控制下每个正在执行的程序就是一个任务task。
如果一个操作系统能够以这种方法执行多个任务这就叫做多任务multitasking. 多任务操作系统的使用可以简化应用程序的设计: 1 操作系统的多任务和任务间通信的机制允许复杂的应用程序被分成一系列更小的和更多的可以管理的任务。
2 程序的划分partitioning让软件测试更容易 团队工作分解work breakdown within teams也有利于代码复用。
3 复杂的定时和先后顺序的细节 可以从应用程序代码中 删除。
因为这成为操作系统的职责。
多任务Vs 并发 传统的conventional的处理器同时只能执行一个任务。
但通过快速的任务切换一个多任务操作系统可以使它看起来appear好像每个任务并行执行一样。
这可以下面的示意图来描述depicted。
它显示了有关with respect to时间的3个任务的执行模式。
任务名用颜色标注出来写在左手边。
时间从左到右增加相应的颜色的线条 显示该任务在某个特殊时间正在执行。
上面的图 演示的是用户所觉察到的并行执行模式下面的图是实际的多任务执行模式。
----所有可用的任务都好像在执行但实际上在任何一个时刻都只有一个任务在执行 调度 调度器scheduler是内核中负责 决定在某个特殊时间 哪个任务应该执行的部分。
内核可以在任务的生命期lifetime 挂起suspend / 恢复resume一个任务许多次。
调度策略scheduling policy是调度器用来决定哪个任务在哪个时间点执行的算法。
一个非实时多用户系统的策略很可能分配allow给每个任务一个quot公平quotfair的处理器时间片proportion of processor time。
用在实时系统/嵌入式系统的策略稍后再描述。
除了被RTOS内核无意的挂起外一个任务还可以自己挂起自己。
如果一个任务想延迟一段固定的时间也就是sleep或者等待也就是block某个资源可用比如一个串口或者等待一个事件出现比如一个键按下。
一个阻塞或者睡眠的任务是不能执行的不会为它分配任何处理时间。
上图中提到的编号: 1 Task1正在运行 2 内核挂起Task1 3 恢复任务Task2 4 Task2正在执行为独占访问exclusive
access它锁定一个处理器外设 5 内核 挂起Task2 6 恢复Task3 7 Task3试图访问同样的处理器外设发现它被锁定Task3不能继续所以自己挂起自己。
8 内核恢复Task1 …………. 9 接下来the next timeTask2在9处执行。
它完成了对处理器外设的访问所以解锁它 10 再下来Task3在10处执行。
它发现 现在可以访问处理器外设了于是开始执行直到被内核挂起。
上下文切换 跟任何其他程序一样一个任务执行时它使用 处理器/微控制器 的寄存器访问RAM ROM。
这些资源处理器的寄存器stack等一起组成任务的执行上下文the task execution context. 一个任务是一个连续有序的代码片断。
它并不知道它将何时被内核挂起或者恢复甚至不知道这些事情挂起或者恢复在什么时候已经发生了。
下面考查Consider的这个例子是用来求两个处理器中的寄存器值之和该任务在执行1条指令后就立即被挂起。
--gt任务将要执行ADD指令时被挂起 --gt先前的指令已经把数取到寄存器Reg1Reg2中了而这些寄存器Reg1Reg2将要被ADD指令用到。
当这个任务被恢复后ADD就是要执行的第1条指令。
这个任务不知道是否有另一个的任务会在中间时期 修改 Reg1或者Reg2 当这个任务挂起时其他任务继续执行可能会修改处理器寄存器的值。
在恢复之后这个任务也不知道处理器的寄存器被修改过altered.如果它使用这个修改过的值就会导致计算的和的结果不正确。
为了避免这类错误必须保证在恢复一个任务之后其上下文环境跟 即将挂起前是一样的。
操作系统内核有责任 通过在任务挂起前保存其上下文 来确保这种状况。
当任务恢复时保存的上下文 就被 操作系统内核恢复到先前的执行情况。
保存一个被挂起的任务的上下文 并在 任务恢复时 恢复其上下文的这个处理过程就叫做上下文切换context switching 实时应用 实时操作系统RTOSs通过同样的原理达到多任务的目的。
但他们的目标与那些非实时系统相比是很不一样的。
不同的目标影响到不同的调度策略。
实时/嵌入式系统设计成提供一个对 真实世界的事件的及时响应timely response。
出现在真实世界中的事件可能有一个时间限制deadline在此期限之前实时/嵌入式系统必须给出响应RTOS调度策略必须确保时间限制是恰当的met. 为了达到这个目的软件工程师必须首先为每个任务设置一个优先级priority。
RTOS的调度策略 只是简单地确认 能被执行的最高优先级别的任务 是分配了处理时间的任务the task given processing time。
这可能要求在相同优先级的 任务之间公平的共享 处理时间如果他们准备并发运行的话。
代码示例: 最基本的例子是 一个由键盘和LCD组成的实时系统。
用户必须 在合理的时段 为 每个键盘按下 取得视觉反馈visual feedback.。
如果用户不能 在这时段 看到 键盘按下 已经被接受软件产品将会很难使用be awkward to use。
如果最长的接受期是100ms那么在0到100ms的响应 可被接受。
这个功能可以用一个 下面这样的结构的独立autonomous任务 实现: void vKeyHandlerTask void pvParameters //键盘处理是一个连续的过程。
就像大多实时任务那样这个任务 //也是用一个无限循环实现的。
for Suspend waiting for a key press Process the key press 现在假设 实时系统也执行一个依赖数字滤波输入的控制功能。
这个输入必须被取样sampled滤波filtered并且 每2ms执行一次控制循环。
为了让滤波器正常操作取样的时间规律the temporal regularity必须精确到0.5ms。
这个功能可以 用下面这个结构的 独立任务 实现: void vControlTask void pvParameters for Suspend waiting for 2ms since the start of the previous cycle Sample the input Filter the sampled input Perform control algorithm Output result 软件工程师必须设置 控制任务为 最高的优先级因为 1 控制任务的时间限制deadline 比 键盘处理任务的 要严格 2 控制任务错过最后期限deadline的后果 比 键盘处理任务 要严重。
下面将演示 这些任务是如何被实时操作系统调度的. 实时调度 下面的图 演示 前面定义的那些任务是如何被时实操作系统调度的。
RTOS自己已经建立了一个任务----idle task---它只在没有其他任务执行的时候才被执行。
RTOS idle task 总是处于可以执行的状态注:也就是它不可能会因为等待什么外设资源而被阻塞而是处于一种随时待命的状态. 上图中: 1. 在最开始我们的两个任务都不能被执行-vControlTask等待合适correct的时间开始新的控制循环vKeyHandlerTask等待键盘按下。
处理器时间分配给 RTOS的idle task. 2. 在t1时刻一个键盘按下事件出现. VKeyHandlerTask任务现在可以执行它比RTOS的idle task有更高的优先级所以处理器时间给它。
3. 在t2时刻vKeyHandlerTask已经完成了对按键的处理并更新了LCD。
它不能继续直到另一个键被按下所以必须挂起它自己。
RTOS idle task又被恢复执行。
4. 在t3时刻一个定时器事件预示indicates可以执行下一个控制循环了。
VControlTask现在可以执行作为最高优先级的任务被立刻分配scheduled到处理器时间。
5. 在t3和t4之间当vControlTask任务还在执行的时候一个键按下。
VKeyHandlerTask不能被执行因为它没有vControlTask的优先级高。
不能分配scheduled到任何处理器时间。
6. 在t4时刻vControlTask完成了控制循环的处理不能够重新开始直到下一个时间事件出现所以它自己挂起自己。
而vKeyHandlerTask现在是最高优先级的任务可以运行了所以为了处理先前的键盘事件分配scheduled到了处理器时间. 7. 在t5时刻键盘已经被处理。
VkeyHandlerTask为了等待下一个键盘事件自己挂起自己。
现在我们的两个任务再度不能执行了。
RTOS idle task分配到处理器时间。
8. 在t5和t6之间一个定时器事件被处理但是没有更多的键盘事件出现。
9. 下一个键盘按下出现在t6时刻但在vKeyHandlerTask完成处理键之前一个定时器事件出现了。
现在两个任务都能被执行而vControlTask比vKeyHandlerTask 有更多的优先级所以vKeyHandlerTask在它完成处理键盘之前就被挂起了。
VControlTask分配到处理器时间。
10. 在t8时刻vControlTask完成处理控制循环挂起自己以等待下一个事件。
VKeyHandlerTask再次成为最高优先级的任务能够运行所以分配到处理器时间从而键盘按下事件 处理能够完成。
RTOS实现 模块Building Block 详细实例Detailed Example 这一节从底向上描述了RTOS上下文切换的源代码。
使用FreeRTOS Atmel AVR微控制器移植的代码作为例子。
本节的最后还一步一步地浏览step by step look了一个完整的上下文切换。
C开发工具 FreeRTOS的目标是简单且易于理解。
为了达到这个目标To this endRTOS的源代码的大部分都是用C写的而不是汇编。
这里演示的例子使用了 WinAVR development tools。
WinAVR是一个自由/免费的在windows下的AVR交叉编译器它是基于GCC的。
RTOS Tick 睡眠时一个任务将指定多长时间后它会醒来。
阻塞时一个任务将指定一个 希望最多等多久的时间。
FreeRTOS实时内核用tick count变量 来度量时间的。
定时器中断RTOS tick interrupt 用严格的时间精度temporal accuracy 来增加 tick count------- 允许实时内核 用一个指定的 定时器中断频率的精度resolution来测量 时间。
每次tick count增加后实时内核 必须检查看现在是否 解除阻塞 或者 唤醒 一个任务。
一个 比被中断的任务有更高的优先级的 任务 在 tick ISR期间 被 唤醒或者解除阻塞 是可能的。
如果是这种情况tick ISR应该返回到新的唤醒/解锁的任务---实际effectively中断一个任务却返回到另一个任务。
如下所述 上图提到的几个点 1 RTOS idle task正在运行 2 RTOS tick出现控制转移到tick ISR3 3 RTOS tick ISR使得vControlTask准备运行当vControlTask比RTOS idle task有更高的优先级切换上下文到 vControlTask.。
4 现在的执行上下文是vControlTask的。
从ISR4退出 返回到vControlTaskvControlTask从5开始执行。
以这种方式出现的上下文切换称为Preemptive。
因为被中断的任务 没有自愿voluntarily地挂起它自己就被抢占了preempted。
FreeRTOS的AVR移植版本 用一个在定时器1timer1的比较匹配compare match事件来产生RTOS tick. 后续将描述RTOS tick ISR是如何用WinAVR开发工具实现的。
GCC信号属性Signal Attribute GCC development tools允许用C来写中断程序。
一个在AVR 定时器1外设的比较匹配事件 可以用下面的 语法syntax实现: void SIG_OUTPUT_COMPARE1A void __attribute__ signal void SIG_OUTPUT_COMPARE1A void / ISR C code for RTOS tick. / vPortYieldFromTick 在函数原型前的 __attribute__ signal 指示符 告知 编译器这个函数是一个ISR会引起编译器输出的两个重要改变 1. signal属性保证每个在ISR 期间 被修改的 处理器的寄存器在从ISR中退出时恢复到它原来的值。
这就要求当中断将要执行时编译器不能做任何假定。
所以不能优化 哪个处理器寄存器要求保护或者不保护。
2signal也强制使用 一个 从中断返回return from interrupt指令RETI而不是 返回return指令RET. AVR微控制器 在进入ISR前禁止中断RETI指令要求在 退出时 重新打开中断。
下面是由编译器输出的代码 void SIG_OUTPUT_COMPARE1A void --------------------------------------- CODE GENERATED BY THE COMPILER TO SAVE THE REGISTERS THAT GET ALTERED BY THE APPLICATION CODE DURING THE ISR. PUSH R1 PUSH R0 IN R00x3F PUSH R0 CLR R1 PUSH R18 PUSH R19 PUSH R20 PUSH R21 PUSH R22 PUSH R23 PUSH R24 PUSH R25 PUSH R26 PUSH R27 PUSH R30 PUSH R31 --------------------------------------- CODE GENERATED BY THE COMPILER FROM THE APPLICATION C CODE. vTaskIncrementTick CALL 0x0000029B Call subroutine --------------------------------------- CODE GENERATED BY THE COMPILER TO RESTORE THE REGISTERS PREVIOUSLY SAVED. POP R31 POP R30 POP R27 POP R26 POP R25 POP R24 POP R23 POP R22 POP R21 POP R20 POP R19 POP R18 POP R0 OUT 0x3FR0 POP R0 POP R1 RETI --------------------------------------- GCC Naked 属性 前一节讲述了如何在C中用 signal属性来写一个ISR.以及它是如何使 执行上下文自动保存的只有那些被ISR修改过的处理器寄存器才会得到保存。
然而执行一个上下文切换需要保存完整的上下文。
应用程序代码能够 在进入ISR时明确explicitly地 保存所有寄存器但是这样会使 某些处理器寄存器 保存两次---一次是由编译器生成的代码另一次是由应用程序自己。
这不是我们所需要的可以在signal属性后 添加 naked属性来避免 void SIG_OUTPUT_COMPARE1A void __attribute__ signal naked void SIG_OUTPUT_COMPARE1A void / ISR C code for RTOS tick. / vPortYieldFromTick naked属性阻止编译器生成任何函数入口或退出代码。
现在变异这段代码会得到更少的编译器输出 void SIG_OUTPUT_COMPARE1A void --------------------------------------- NO COMPILER GENERATED CODE HERE TO SAVE THE REGISTERS THAT GET ALTERED BY THE ISR. --------------------------------------- CODE GENERATED BY THE COMPILER FROM THE APPLICATION C CODE. vTaskIncrementTick CALL 0x0000029B Call subroutine --------------------------------------- NO COMPILER GENERATED CODE HERE TO RESTORE THE REGISTERS OR RETURN FROM THE ISR. --------------------------------------- 看看入口 和 出口代码都没有了吧 使用 naked 属性编译器不会生成任何入口和出口代码所以必须明确explicitly加入。
portSAVE_CONTEXT和portRESTORE_CONTEXT这两个宏 是用来保存和恢复完整的 执行上下文的 void SIG_OUTPUT_COMPARE1A void __attribute__ signal naked void SIG_OUTPUT_COMPARE1A void / Macro that explicitly saves the execution context. / portSAVE_CONTEXT / ISR C code for RTOS tick. / vPortYieldFromTick / Macro that explicitly restores the execution context. / portRESTORE_CONTEXT / The return from interrupt call must also be explicitly added. / asm volatile quotretiquot naked属性给了应用程序完整的控制权在何时 怎么样保存AVR的上下文。
如果应用程序代码在进入ISR前保存了完整的上下文在执行上下文切换时不必再保存所以不会有处理器的寄存器被保存两次。
FreeRTOS Tick Code FreeRTOS的AVR移植版本的实际源代码 与 前一节的例子有些轻微的不同。
VPortYieldFromTick是作为一个naked函数的 它自己的实现上下文在vPortYieldFromTick.里被保存和恢复。
这样做的目的是为了实现一个non-preemptive的上下文切换这里一个任务自己阻塞自己.这里暂时不讲这种non-preemptive切换。
RTOS tick是这样在FreeRTOS中实现的看代码中注释片断获取更多细节 void SIG_OUTPUT_COMPARE1A void __attribute__ signal naked void vPortYieldFromTick void __attribute__ naked /--------------------------------------------------/ / RTOS tick中断服务程序. / void SIG_OUTPUT_COMPARE1A void /调用tick函数. / vPortYieldFromTick /从中断返回. 如果出现上下文切换将返回到一个不同的任务中 / asm volatile quotretiquot /--------------------------------------------------/ void vPortYieldFromTick void / 这是一个naked 函数所以需要保存上下文 / portSAVE_CONTEXT / 增加tick count检查新的tick count值是否引起一个延迟周期过期这个函数调用可导致一个任务变成准备运行. / vTaskIncrementTick /检查是否要求上限文切换。
如果 由vTaskIncrementTick准备好的任务比已经中断的任务有更高优先级就切换过去 / vTaskSwitchContext /恢复上下文.如果发生了上下文切换这将恢复要继续运行的任务的上下文 / portRESTORE_CONTEXT /从这naked 函数返回. / asm volatile quotretquot /--------------------------------------------------/ The AVR Context 上下文切换要求保存完整的上下文。
在AVR MCU中上下文包括 1 32位通用寄存器。
Gcc开发工具假定寄存器R1设定为0 2 状态寄存器。
状态寄存器的值影响指令的执行必须通过上下文切换保存preserved 3 程序计数器PC.恢复执行后一个任务必须从上次被挂起的地方继续执行。
a task must continue execution from the instruction that was about to be executed immediately prior to its suspension. 4 两个stack指针寄存器 Saving the Context 每个时实任务都有它自己的stack 内存区域所以上下文可 简单的通过将寄存器压入到任务栈 来保存上下.