DSP

freeRTOS移植——初步分析

2019-07-13 15:52发布

基于之前的分析,freeRTOS的移植主要集中在以下部分,·       中断管理,包括ISR的配置、中断的关闭和开启等。·       TASK切换处理,包括触发TASK切换、TASK切换的出栈和压栈操作等。·       freeRTOS的基本配置和可选配置·       Heap管理,它们对应的源文件如下·       portable.h,申明了中断管理和TASK切换管理相关的函数申明,用户可以自行增减·       freeRTOS_Config.h,定义了freeRTOS的各类配置的宏,用于可以根据需要进行配置·       Heap_X.c,X=1~5,用户可以自行选择freeRTOS提供的heap模型,也可以自行开发 移植之前必须清楚待移植嵌入式系统的硬件架构,尤其是CPU和存储器的相关细节。后边将一一提到。实际上freeRTOS需要移植的必要代码都已经在portable.h中声明(不含freeRTOS_Config.h配置的可选功能),我们只需要按照该文件填写好对应的宏和函数即可。以下将以freeRTOS官网上给出的基于ZYNQ_ZC702的工程,对一些常见的需要移植的代码进行分析。

初始化Task Stack

对应函数声明强制如下,StackType_t*pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void*pvParameters )其在freeRTOS创建TASK时被调用,用于初始化TASK的STACK,细节参考API函数xTaskCreate()、xTaskCreateStatic()、xTaskCreateRestricted和xTaskCreateRestrictedStatic()。该函数的处理十分简单,实际就是压栈过程,但寄存器的取值完全用用户确定,但是需要注意以下几点·       寄存器压栈的顺序完全由用户确定,一般是建议按照寄存器的类型和编号来确定,以便于维护·       Stack Address Register对应的栈内位置存放TASK入口函数地址·       TASK入口函数的入参按照CPU要求存放在相应寄存器对应的栈内位置——一般而言,编译器会指定某些寄存器专门用于存放函数入参。·       TASK入口函数的返回值初始化为一特定值,按照CPU要求存放在相应今存其对应的栈内位置——一般而言,编译器会指定某一寄存器专门用于存放函数返回值。·       压栈之前先想栈内写入一些特殊值,可用于检测栈是否被异常改写。前两点需要用户查询CPU相关硬件手册,是必须做的。后两者并不强制要求,但是在实际移植中常常用到,可以用来检测OS崩溃。若业务代码没有频繁创建TASK,处于便于维护的考虑,建议用C语言实现。 以下为freeRTOS官网提供的部分代码,可见其认为栈是向下生长的,按照R15到R0的顺序进行入栈的,出栈时必须保证这样的顺序。

启动调度器

对应函数声明强制如下,BaseType_t xPortStartScheduler( void )其在freeRTOS提供的API函数vTaskStartScheduler()中调用,用于OS启动时设置ISR、初始系统硬件和运行firstTASK。其中,·       设置ISR是指为OS使用到的中断设定相应ISR的过程,不并强制在该函数中进行,只要在启动CPU之前进行即可,我个人更加趋向于放到main()函数中OS启动之前的位置,和设置业务代码所需中断一起,以方便管理和维护。·       初始化硬件,即初始化系统硬件模块,如定时器、UART、DMA等等。它并不强制在该函数中进行,根据需要在用户认为合适的地方执行即可。但个人建议最好在启动CPU之前进行,尤其是用于支持OS时间片机制的硬件定时器。·       运行first TASK, first TASK是指在启动CPU后,OS中处于Ready状态的优先级最高的TASK,它是OS首个运行的TASK,因此记为first TASK。该处理过程即first TASK的出栈过程,但在完成出栈后需要设置CPU使之正常运行(恢复到正常运行状态并能够接接收所有相关中断)。 freeRTOS官网上的代码实际上是使用函数vConfigureTickInterrupt()来设置中断和完成硬件初始化的,然后调用汇编语言实现的函数vPortRestoreTaskContext()运行first TASKvConfigureTickInterrupt()的基本流程如下图所示,其中使用了大量的ZYNQ 7000的提供的BSP,对于其他的硬件模块,也可以使用类似的流程完成相应中断设置和初始化
其中,l  对于GICl  寻找配置(LookupConfig),使用BSP提供的API函数XScuGic_LookupConfig()l  配置初始化(ConfigInitialization),只需要在首次配置中断时使用,使用BSP提供的API函数XScuGic_CfgInitialize()l  设置中断(SetInterrupt),即配置中断优先级,触发条件等,使用BSP提供的API函数XScuGic_SetPriorityTriggerType()l  链接(Connect),即配置中断服务程序(ISR),使用BSP提供的API函数XScuGic_Connect()l  开启中断(EnableInterrupt),即在GIC侧开启中断,使用BSP提供的API函数XScuGic_Enable()l  对于Timerl  寻找配置(LookupConfig),使用BSP提供的API函数XScuTimer_LookupConfig ()l  配置初始化(ConfigInitialization),使用BSP提供的API函数XScuTimer_CfgInitialize()l  设置Timer(Set Timer),即Timer的周期,运行模式等,使用BSP提供的API函数XScuTimer_EnableAutoReload()和XScuTimer_SetPrescaler()l  启动Timer(Start Timer),使用BSP提供的API函数XScuTimer_LoadTimer()和XScuTimer_Start()l  开启中断(EnableInterrupt),即在Timer 使用BSP提供的API函数XScuTimer_EnableInterrupt() 以下为相应代码,实际上所有外设中断都可以如是配置。

vPortRestoreTaskContext()实际上就是出栈的处理过程,也用于任务切换的处理过程,在后边会提到。 

ISR

一般而言,ISR处理过程如下图所示,包含压栈、用户自定义处理和出栈过程,其中,·       用户自定义处理过程基于函数指针数组做成API提供给用户,以方便用户自行定义ISR·       压栈和出栈即之前提到的CPU处理中断过程中的出栈和压栈,与之前说的一样,多以汇编语言实现·       若支持中断嵌套,则在压栈时将中断嵌套信息保存到interrupt stack中后更新,出栈时则从interrupt stack恢复中断嵌套该信息。·       出栈时若需要触发TASK切换,则需要在中断处理完成(包含中断的出栈)后,直接进行TASK切换或触发相应的任务切换中断。该部分的移植需要特别考虑如何提高出栈和压栈代码的效率,以及提供给用户更加方便的用户自定义处理过程的API接口。除此之外,还需要考虑如何将ISR注册到CPU认可的中断向量表中去。FreeRTOS使用了中断向量表替换的方式来完成注册,如下图所示,其中的函数vPortInstallFreeRTOSVectorTable()需要在操作系统启动前调用
对应的中断处理向量表freertos_vector_table如下,PS:freertos_vector_table需要在链接器文件中注册,其中尤其要注意把vector_table的KEEP修饰关键字去掉

freeRTOS提供了名为FreeRTOS_IRQ_Handler的函数来实现ISR,细节如下。压栈过程代码如下,其中的176行即对当前TASK进行压栈,在此之前,保存了了R14和SPSR,然后切入到SVC模式。
用户自定义处理过程相关代码如下,其中,·       用户自定义处理过程在201行执行,其最终会调用到用户定义的中断服务程序表(XScuGic_ConfigTable),用户可以通过配置该程序表来完成对该过程的自定义。·       用户自定义处理过程前后,再次使用了压栈和出栈机制(200行和203行),暂存了中断嵌套信息()实际为临界区嵌套层数,变量ulPortInterruptNestingConst)·       181行和182行将当前临界区嵌套信息(实际为临界区嵌套层数)保存到了寄存器r1,并将其进行了更新(自加1)·       216行恢复了更新前的临界区嵌套信息出栈过程相关代码如下,其中·       225行判断了在ISR处理完成后是否进行TASK切换,变量ulPortYieldRequired提供给OS,输出是否在ISR接收后进行TASK切换的信息,·       若需要TASK切换,则执行switch_beore_exit,其在248行完成了出栈,并且需要注意OS提供了API函数vTaskSwitchContextConst用于支持TASK切换,使用portSAVE_CONTEXT和portRESTORE_CONTEXT完成了TASK切换时的压栈和出栈,相关细节将在后边提到·       若不需要TASK切换,则执行exit_without_switch,其在233行进行了出栈

TASK切换

如之前所述,TASK切换主要是压栈和出栈操作。freeRTOS还提供了了以API函数用于选取当前处于ready状态的优先级最高的TASK,即vTaskSwitchContextConst(),并且freeRTOS还提供了名为pxCurrentTCB的变量用于指示当前TASK的栈顶地址。  freeRTOS提供函数FreeRTOS_SWI_Handler用于TASK切换,
其中portSAVE_CONTEXT为实现压栈的宏,其中首先切换到了SYS模式,然后将必要的寄存器,中断嵌套层数(ulCriticalNestingConst)等全部入栈保存。
其中portRESTORE_CONTEXT为实现压栈的宏,其中过程与portSTORE_CONTEXT()刚好相反,但是由于OS提供的vTaskSwitchContextConst()中已经改变了当前OS执行TASK信息(pxCurrentTCBConst),因此出栈后切换到到了另外一TASK。

中断控制

如之前所属,OS基于中断实现了临界区和主动触发TASK切换的功能,它们以宏的方式定义,名称如下,l  portYIELD(),用于触发TASK切换,一般是用触发指定软中断的方式实现l  portYIELD_FROM_ISR(x),当x不为0时在ISR中触发TASK切换l  portENTER_CRITICAL(),进入临界区,l  portSET_INTERRUPT_MASK_FROM_ISR(),在ISR中关闭OS使用的中断,可用于ISR中进入临界区l  portEXIT_CRITICAL(),退出临界区l  portCLEAR_INTERRUPT_MASK_FROM_ISR( x ),在ISR中开启OS使用的中断,可用于ISR中进入临界区l  portDISABLE_INTERRUPTS(),关闭中断l  portENABLE_INTERRUPTS(),开启中断 freeRTOS官网上提供了相关代码,非常简单易懂,这里就不在累述。