如何实现FreeRTOS应用到安全SAFERTOS的迁移
扫描二维码
随时随地手机看文章
转自 | 麦克泰技术
FreeRTOS是一个面向微控制器和小型微处理器的实时操作系统,基于MIT license许可分发,FreeRTOS的构建强调可靠性和易用性。
汽车、医疗和工业等市场的安全规范意味着设计者需要一个相关行业标准认证过的RTOS,但认证RTOS在长期的安全项目初期将是一笔昂贵的开支。
SAFERTOS是一个安全关键RTOS,通过了IEC61508和ISO26262预认证。SAFERTOS采用与FreeRTOS相同的功能模型,为安全而构建。安全产品中,项目原型可以使用FreeRTOS内核实现,在正式开发阶段再转为SAFERTOS。
本文通过一个简单的示例项目说明如何将一个FreeRTOS应用迁移到SAFERTOS。
硬件平台:NXP Freedom K64F开发板- FRDM K64F
开发环境:MCUXpresso IDE v11.1
示例项目可免费从
www.highintegritysystems.com/down-loads/下载。
项目包含3个工程:
1、FreeRTOS_Demo-基础的FreeRTOS工程
2、RTOS_Demo-特权模式的SAFERTOS工程
3、RTOS_UnprvDemo-具有非特权任务的SAFERTOS项目
FreeRTOS和SAFERTOS主要区别
与FreeRTOS相比,SAFERTOS:
· API函数更少
· 函数中执行了更多的错误检查
· 大多数API调用会返回状态码,其它函数通过引用返回数据
· 需要应用提供所有堆栈,任务控制块和队列缓冲区内存
· 使用静态分配机制,不提供heap函数
· 默认使用处理器的MPU单元
· 完全重新设计,满足安全关键软件需求
因此,当将FreeRTOS项目迁移到SAFERTOS时,需要做一些工作来完成内核启动和运行。
FreeRTOS内部隐藏了许多常规内存管理,在任务创建时动态分配堆栈,在内核启动时分配内核缓冲区等。在Free RTOS中也可以配置静态分配,由应用程序提供内存,但大多数人倾向于更简单的方法,让FreeRTOS实现。
FreeRTOS还提供了许多编译选项,并通过hook宏机制,允许应用程序设计者在内核中插入额外的功能代码,在任务切换时运行,例如,在任务删除或创建时,运行额外的hook函数。
API区别
RTOS定义的类型名称不同。使用FreeRTOS,应用程序文件需要包含(#include)API(任务,队列,信号量)相应的头文件;SAFERTOS中,应用程序文件只需要包含一个SafeRTOS_API.h头文件。
静态分配及MPU
SAFERTOS要求应用任务和内核对象所需的内存静态分配。安全严格系统推荐静态分配机制,容易证明运行时有足够的内存空间。
绝大多数SAFERTOS移植中假定使用了MPU。MPU的使用意味着应用程序设计人员需要监督内存结构地址的确切位置,包括内核任务和队列缓冲区。此外,MPU还需满足区域的对齐和大小限制,应用程序工程师需要仔细安排空间,以避免空间浪费。
因此,使用FreeRTOS时,在调用xTaskCreate之前,需确保heap中足够的空闲空间。使用SAFERTOS,需要预先分配并定位对齐的堆栈及任务TCB内存,然后将指向这些结构的指针传递到xTaskCreate的相应参数。
任务特权模式及内核函数封装层
每个SAFERTOS任务被分配一个操作权限,特权(Privileged)任务与内核代码具有相同的权限,许多CPU支持特权(privileged)和非特权(unprivileged)模式,限制非特权模式的指令访问,有限的软件trap、异常和中断等。
通常,应用程序以非特权模式运行,每个任务都提供了一组MPU参数,这些参数在任务切换时配置相应的MPU域。
SAFERTOS任务创建时,增加了一个MPU域,定义用户任务堆栈,确保任务只访问自己的堆栈。
内核API工作在特权模式,SAFERTOS的每个API有一个权限升级封装(privilege-escalating wrapper)层,通过触发异常(通常是系统调用)、同步中断或CPU的trap实现。API的封装层通过临时提升任务权限,允许非特权任务执行内核API,执行完成后降回任务原先的权限。因为实际的API函数与调用时的名称不同,调试不方便。
尽管FreeRTOS也通过权限升级封装类似的机制支持MPU功能,但仅提供了有限的MPU移植参考。在SAFERTOS中,我们假定应用任务运行非特权模式。在FreeRTOS MPU移植中,任务通常被假定为运行特权模式,但是任务可以选择创建为restricted,即非特权模式。
基于FreeRTOS的应用
示例项目中包含一个向导生成Amazon FreeRTOS项目:FreeRTOS_Demo。
自动生成的链接文件
工程构建后,将自动生成链接定位文件,由于SAFERTOS工程中,需修改链接文件,我们不希望自动生成的链接文件覆盖已修改内容,将生成的链接文件从Debug目录迁移到单独的目录,并在工程选项中关闭自动生成linker文件,并指向新的链接文件目录。
应用代码
应用包含3个LED任务和一个控制任务,控制任务更新每个LED的“目标亮度”值的全局数组,并使用互斥信号量监视全局数组的访问。全局数组不是任务之间通信的最佳方法,但我们的目标是提供简单的示例,说明从FreeRTOS如何转换为SAFERTOS。
从FreeRTOS迁移到SAFERTOS
一,将工程升级为SAFERTOS,所有代码运行在privileged模式
1、替换FreeRTOS内核代码为SAFERTOS
删除工程amazon-freertos目录中的代码,替换为SAFERTOS库及头文件,修改工程options中的include path-C/C++ Build/Settings->Tool Settings->MCU C compiler->includes,
向导自动生成的工程中,还需修改C/C++ General->Paths and Symbols>includes路径信息。
2、 编辑链接文件,导出SAFERTOS需要的符号
SAFERTOS需要使用链接文件中定义的段和变量符号,设置MPU区域保护内核代码和数据。需要的段和符号可查阅portmpu.h文件。
内核函数段名为kernel_func,内核数据段为kernel_data,GCC中,内核函数和数据通过段属性放到相应段中。
内核函数通常紧随向量表放置。内核数据被放置RAM中的某个位置,需符合MPU对齐需要。
链接文件还需导出段起始和结束符号,ROM及RAM的起始地址、结束地址及大小。
在portmpu.h中,需要下列符号
·lnkStartFlashAddress
· lnkEndFlashAddress
· lnkStartKernelFunc
· lnkEndKernelFunc
· lnkStartKernelData
· lnkEndKernelData
· lnkRAMEnd(RTOS_Demo_Debug_memory.ld)
· lnkRAMStart(RTOS_Demo_Debug_memory.ld)
3、安装SAFERTOS需要的中断和异常
K6xxF移植中需要SysTick,PendSV,SVC中断,这些函数使用CMSIS定义的实现处理,相应的处理入口位于默认的向量表位置,RTOS可以命名自己的异常处理函数。
在SAFERTOSConfig.h中,通过#defining 实现SAFERTOS异常处理替代CMSIS定义,但SAFERTOS库文件无法修改,可以重新定义startup文件向量表中的CMSIS名称。
工程代码中,向量表定义位于startup_mk64f12.c文件,在该文件中插入:
/* SAFERTOS system tick, SVC and PendSV handlers */
#define SysTick_Handler vTaskProcessSystemTickFromISR
#define SVC_Handler vSafeRTOSSVCHandler
#define PendSV_Handler vSafeRTOSPendSVHandler
与FreeRTOS不同,SAFERTOS在启动时检查向量表条目,如果它们不存在或不正确,则拒绝运行。
4、内核hook函数
SAFERTOS提供了有限的hook函数,其中最主要的是Error Hook函数。在系统检测到不可恢复错误时,进入安全的错误状态。例如检测到被破坏的TCB或堆栈溢出,将调用error hook。项目中,error hook是一个简单的无限循环(位于HookFunctions.c文件)。
5、内核任务、内核配置及启动
除hook函数的地址外,还需将堆栈及TCB的地址和大小传给空闲任务和timer任务(可选),内核任务还需MPU参数,timer 命令队列等信息。SAFERTOS通过一个专用结构将参数传给专用的API来配置内核。应用从FreeRTOS迁移到SAFERTOS最复杂的部分是配置并启动调度器。
内核配置(包含内核任务堆栈和TCB)信息,放在单独的SafeRTOSConfig.c文件中。
在内核启动的每个阶段,当配置结构被传递到内核配置函数后,内核启动,API将返回相应的错误代码。函数返回的错误代码,可以在内核include目录的projdefs.h文件中查找含义。该文件列出了所有错误代码信息。
对于示例应用,还需使用一个“备用”MPU区域,建立一个全局MPU区域,以访问板载LED的GPIO,这样我们就不必提升相应的任务权限以使用GPIO资源。在SafeRTOSConfig.c中,通过xMPUConfigureGlobal Region()调用设置了“全局”MPU区域,允许读写GPIO寄存器 (实际上,该区域包含所有的外设地址空间)。
6、应用任务TCB和堆栈
为每个应用任务设置堆栈和TCB,填充其它任务参数。
在FreeRTOS中,创建任务所需的6个参数直接传递给xTaskCreate()函数,函数将返回新创建任务的句柄或pdFALL错误信息。在SAFERTOS中,任务参数更多,通过一个指向移植特定的xTaskParameters结构参数,传递给xTaskCreate(),第二个参数接收新创建任务的句柄,函数将返回pdPASS或错误码。
在示例中可以看到,FreeRTOS与SAFERTOS的任务名称和参数各不相同。SAFERTOS任务参数中还有每个任务的特权级别和MPU区域设置。第一步我们使所有任务都运行在特权级别,所以目前不需要额外的MPU参数,这些参数设置为0/null。
7、更新调用的API
针对每个API调用,检查并修改SAFERTOS对应的函数名称及返回值。
8、类型更新
除API函数名称不同外,移植层定义的类型名也有区别。可以借助IDE的search&replace功能替换。
二、修改应用任务为非特权模式
我们已经通过一个“全局MPU域”允许所有任务访问使用的GPIO寄存器。任务被转换为非特权模式后,还需要有哪些访问权限?
将某个任务标记为unprivileged,然后加载并运行应用程序,如果应用最终进入MPU fault处理程序中。可以检查调试器的寄存器以识别故障地址,可以尝试使用调试器强制从处理程序返回,快速识别导致MPU故障的指令。
示例中,每个LED任务需要访问共享的“brightness request”数组,处理访问该数据的互斥量,互斥量buffer仅由内核代码访问,任务如何实现访问共享RAM region?
在链接文件中创建一个命名段,导出开始地址和大小符号,添加放置属性来指定函数或变量的定位,在GCC中如下:
__attribute__ ( ( section ( “section_name”)))
最后,使用链接文件导出的段符号来定义MPU域,添加到任务MPU参数中,以获得访问权限。注意,导出的linker符号为地址,在声明其为extern变量后,我们可以获取符号的地址,或者将其声明为数组类型,数组名将是其地址。
结论
本文通过一个简单的示例工程及迁移过程,探讨了FreeRTOS和SAFERTOS的差异。
通过示例项目,在FreeRTOS转换为SAFERTOS的每个阶段,分析预先配置的项目源代码,运行每个版本,可以分析原来的FreeRTOS平台上的应用程序与最终的非特权版本之间的差异。
显然,即使非常简单的代码,也有许多不同的SAFERTOS转换实现方式,早期的设计决策也会影响转换的简单性。建议尽量减少使用FreeRTOS特有的API,使用内核管理的任务间通信机制,使用FreeRTOS和SAFERTOS共享的类型名称,可以让事情变得更容易。
从应用一开始就考虑到任务为非特权执行模式,当升级到安全应用时,会更简单。
免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!