分享

浅谈嵌入式MCU软件开发之将应用程序代码重定向到系统栈(stack)上运行的实现原理和方法详解

 月牙博客 2021-07-05

内容提要

引言
1. 将应用程序代码重定向到stack运行的实现原理
1.1 C语言的堆栈(stack)工作原理和特性
1.2 C语言局部变量分配和初始化
1.3 基于C语言的结构体局部变量初始化特性实现重定向代码到stack运行的原理和方法
2. 将应用程序代码重定向到stack运行的实现实例和实现要点
2.1 基于GNU工具链(S32DS IDE)的ARM Cortex-M4F内核(S32K144)实现要点
2.2 基于CodeWarrior工具链(CodeWarrior 10.x/11.x IDE)的Power e200z内核(MPC5644A)实现要点
总结
引言

之前我写过一些文章介绍过在不同CPU架构的MCU在不同的软件开发工具链/IDE中实现代码重定向(relocate, 有时也称作remap,重映射)到RAM中运行,以避免代码单一分区(partition)Flash存储器上存储的Flash驱动程序或者片上总线矩阵(CrossBar)配置等关键代码执行时造成的Flash读写访问(RWW--Read While Write)冲突,感兴趣的读者可以点击下方文章标题,直接跳转阅读:

浅谈嵌入式MCU软件开发之S32K1xx系列MCU启动过程及重映射代码到RAM中运行方法详解》;
CodeWarrior IDE使用tips之prm链接文件详解(自定义存储器分区以及自定义RAM数据初始化与在RAM中运行函数)》;
《 CodeWarrior IDE使用Tips之如何通过prm文件指定汇编代码函数、全局变量和常量的储存地址》;
CodeWarrior IDE使用Tips之Qorivva MPC56xx新建应用工程选项、调试高级选项及下载过程控脚本详解》;

之前介绍的代码重定向实现原理和方法大致如下:

① 修改应用工程链接文件,预留特定地址区域的SRAM,并将用户自定义代码段放置到该预留SRAM地址区域,添加必要的链接信息生成符号(symbols);

② 在应用代码中,将需要重定向的应用程序代码/功能函数,通过软件开发工具链的特殊原语(#progma CODE_SEG <customized_func>)或者代码段属性指定(__attribute__((section('.code_ram'))) <customized_func> )等方法指定关键代码链接到用户自定义代码段;

③ 修改启动代码,利用链接生成的自定义代码段重定向信息(源和目的存储器起始地址和长度信息)将需要重定向的代码拷贝到SRAM中;

④ 调用原函数名和参数执行;

很明显,以上实现方法存在以下缺点:

① 要求工程师对目标MCU软件开发所使用的工具链/IDE的链接文件和启动代码以及存储器分布情况十分熟悉,并能够更加应用功能程序开发需求做必要的调整和修改;

② 需要占用应用程序编译结果存储Flash相同大小的SRAM地址空间,受限于目标MCU的SRAM大小。

若需要重定向的代码较多,对于SRAM size小的MCU来说,将很难实现,因为MCU的SRAM还需要分别用于存储应用程序C代码运行必须的全局变量(.data和.bss/.common段变量)和系统堆栈(heap和stack)。

那么有没有更好的办法实现应用代码重定向运行的问题呢?

答案是肯定的,那就是利用C语言局部变量分配和初始化的原理,将应用程序重定向到系统堆栈(stack)上执行。

下面我们就一起来看看具体的实现原理和方法。

1. 将应用程序代码重定向到stack运行的实现原理

1.1 C语言的堆栈(stack)工作原理和特性

C语言是目前嵌入式MCU应用软件程序开发最常用的编程语言,因为其不但功能强大,而且简单高效,易于学习和掌握。

C语言相对于汇编语言的一大特点就是,其运行时CPU内核能够更加工具链编译链接的结果自动分配和管理内核堆栈(stack),用于函数调用和内核异常/外设中断响应时的运行时上下文现场(context)保护,参数传递和临时变量分配。从而大大提高了代码的执行效率和SRAM的利用效率。

图片

C语言的堆栈(stack)具有如下特点:

1. CPU内核的堆栈指针寄存器(SP-Stack Pointer)始终指向栈顶(stack top),所有的进栈(pop)和出栈(push)由内核自动管理,用户只需要在启动代码中初始化堆栈(将栈顶地址赋值给CPU内核的堆栈指针寄存器);

2. 栈(stack)内的数据都是先进后出或者后进先出(LIFO--Last In First Out);

3. 栈(stack)的生长方向由CPU内核大小端模式决定:

  • 小端内核, 栈(stack)向下(低地址)生长,比如ARM Cotrex-M系列CPU内核;

  • 大端内核, 栈(stack)向上(高地址)生长,比如PowerPC e200z系列CPU内核;

如下为向上生长的大端内核的stack压栈(push)和出栈(pop)示意图:

压栈(push)操作:

图片

出栈(pop)操作:

图片

4. 栈(stack)必须指向一段可读可写(RW)属性的RAM存储器,可以是MCU的SRAM或者内核的TCM(紧耦合存储器),不能是Flash或者EEPROM;因此其访问速度/效率通常是MCU片内存储器中最高的,零等待的。

Tips: 通常讲的堆栈,其实包含堆(heap)和栈(stack)两个不同的内存管理概念/技术,本文所讲堆栈,若无特别说明,均代表栈(stack);

Tips: stack是CPU内核自行管理的私有存储空间,在多核架构中其属性为non-shareable,可以放心使能其cache功能,以提高内核性能,不必担心stack的数据一致性问题。

Tips:  关于C语言的堆栈使用和注意事项,请参考如下公众号文章(点击文章标题即可直接跳转阅读):

《 浅谈嵌入式 MCU 软件开发之应用工程的堆与栈》;

1.2 C语言局部变量分配和初始化

C语言函数的局部变量,也称作临时变量,将被自动分配到系统stack上(若局部变量比较小,且有可用CPU内核通用寄存器,按照相应的嵌入式应用程序二进制接口标准(EABI-Embedded Application Binary Interface),该局部变量也可能被直接分配到内核通用寄存器,而非stack),从而实现函数调用时实现临时分配和使用,退出函数调用即销毁的目的。

按照ANSI-C的规定:

  • 有初始化值局部变量的初始化由C语言调用内置库函数(编译和链接过程决定),比如memset()自动实现;

  • 无初始化值的局部变量,其初始值为stack原有的值,是随机的。

图片

1.3 基于C语言的结构体局部变量初始化特性实现重定向代码到stack运行的原理和方法

①. 定义需要重定向的目标函数相同原型(prototype)的函数指针类型;

②. 定义足够存储目标函数代码大小的RAM存储结构体类型;

③. 定义一个RAM存储结构体类型的局部变量,并将目标函数转换为RAM存储结构体类型并赋值初始化给新定义的RAM存储结构体类型局部变量,以实现目标代码的自动拷贝到stack;

④. 将新定义的RAM存储结构体类型局部变量,强制转换为目标函数原型(prototype)的函数指针类型,利用函数指针完成目标函数在stack上的调用和执行;

2. 将应用程序代码重定向到stack运行的实现实例和实现要点

下面以S32K144和Qorivva MPC5644A为例,介绍使用GNU工具链(S32DS IDE)和CodeWarrior IDE实现ARM Cortex-M4F内核和Power e200z4内核MCU的C语言应用程序代码重定向到stack上运行的实现要点,供大家参考学习。

2.1 基于GNU工具链(S32DS IDE)的ARM Cortex-M4F内核(S32K144)实现要点

以下代码为基于GNU工具链(S32DS IDE)的ARM Cortex-M4F内核的S32K144 MCU应用工程中,将应用功能函数Math_Func()重定向到栈上运行的具体实现代码:



















































/* define a macro to use attribute to achieve 8 byte alignment */#define ALIGN_8B __attribute__((aligned(8)))
typedef uint32_t (*ram_fuc)(uint32_t, uint32_t );
typedef struct{ uint8_t code[0x256]; /* Structure required to copy code to ram memory */ /* Size of this structure needs to be at least (but best) the size of the FnCmdInRam_ */} FnCmdInRamStruct;
uint32_t ALIGN_8B Math_Func(uint32_t a, uint32_t b){ uint32_t result = 0; result = (a *b 10) * (a b) *(a - b);
return result;}int main(void){ /* Write your code here */
uint32_t aa = 100; uint32_t bb = 36;
uint32_t cc = 0;
/* Create a copy of the function codes in RAM routine on stack */ FnCmdInRamStruct ALIGN_8B FnCmdInRam = *(FnCmdInRamStruct *)((uint32_t)Math_Func-1);
/* run the function via a function pointer */ cc = ((ram_fuc)((uint32_t)&FnCmdInRam 1))(aa,bb);
if(cc>100) { aa = Math_Func(bb,cc); } else { aa = Math_Func(cc,cc); }
for(;;) { if(exit_code != 0) { break; } } return exit_code;}
其实现要点如下:
① 通过GNU的__attribute__((aligned(8)))属性设置,保证重定向目标函数和结构体局部变量在栈上分配时,地址按照8字节对齐,以满足ARM Cortex-M内核对栈操作的地址对齐要求;

② 对结构体结构体赋值时,需要将目标函数地址减1,以保证目标函数代码能够被完整拷贝到stack上:


/* Create a copy of the function codes in RAM routine on stack */FnCmdInRamStruct ALIGN_8B FnCmdInRam = *(FnCmdInRamStruct *)((uint32_t)Math_Func-1);
③ 使用函数指针调用目标函数时,需要将结构体临时变量地址加1,以保证BLX指令调用目标函数时,其地址最低位(LSB)为1,从而保持内核Thumb状态:


/* run the function via a function pointer */cc = ((ram_fuc)((uint32_t)&FnCmdInRam 1))(aa,bb);

图片


2.2 基于CodeWarrior工具链(CodeWarrior 10.x/11.x IDE)的Power e200z内核(MPC5644A)实现要点

以下代码是Qorrivva MPC5644A的CodeWarrior 10.x/11.x IDE应用工程中,将Flash控制器的指令和数据预取功能(相当于MCU的二级缓存)关闭和恢复/使能配置API函数重定向到stack中运行的具体实现代码:






















































































































#include 'MPC5644A.h'
typedef unsigned char BOOL;
typedef signed char INT8;typedef unsigned char UINT8;typedef volatile signed char VINT8;typedef volatile unsigned char VUINT8;
typedef signed short INT16;typedef unsigned short UINT16;typedef volatile signed short VINT16;typedef volatile unsigned short VUINT16;
typedef signed long INT32;typedef unsigned long UINT32;typedef volatile signed long VINT32;typedef volatile unsigned long VUINT32;
#define FLASH_A_FMC 0xC3F88000#define FLASH_B_FMC 0xC3F8C000#define FLASH_PFB_CR 0x0000001CU /* PFBIU Configuration Register for port 0 */#define FLASH_PFB_CR_BFEN 0x00000001U /* PFBIU Line Read Buffers Enable */
/* Read and write macros */#define WRITE8(address, value) (*(VUINT8*)(address) = (value))#define READ8(address) ((UINT8)(*(VUINT8*)(address)))#define SET8(address, value) (*(VUINT8*)(address) |= (value))#define CLEAR8(address, value) (*(VUINT8*)(address) &= ~(value))
#define WRITE16(address, value) (*(VUINT16*)(address) = (value))#define READ16(address) ((UINT16)(*(VUINT16*)(address)))#define SET16(address, value) (*(VUINT16*)(address) |= (value))#define CLEAR16(address, value) (*(VUINT16*)(address) &= ~(value))
#define WRITE32(address, value) (*(VUINT32*)(address) = (value))#define READ32(address) ((UINT32)(*(VUINT32*)(address)))#define SET32(address, value) (*(VUINT32*)(address) |= (value))#define CLEAR32(address, value) (*(VUINT32*)(address) &= ~(value))/*************************************************************** Disable Flash Cache ****************************************************************/void DisableFlashControllerCache(UINT32 *origin_pflash_pfcr1, UINT32 *origin_pflash_pfcr2);
/****************************************************************** Restore configuration register of FCM *******************************************************************/void RestoreFlashControllerCache(UINT32 pflash_pfcr1, UINT32 pflash_pfcr2);/* function pointer definition of the relocated function prototype */typedef UINT32 (*ram_fuc_DisableFlashControllerCache)(UINT32 *, UINT32 * );typedef UINT32 (*ram_fuc_RestoreFlashControllerCache)(UINT32, UINT32 );
typedef struct{ UINT8 code[0x256]; /* Structure required to copy code to ram memory */ /* Size of this structure needs to be at least (but best) the size of the FnCmdInRam_ */} FnCmdInRamStruct;
/*************************************************************** Disable Flash Cache ****************************************************************/void DisableFlashControllerCache(UINT32 *origin_pflash_pfcr1, UINT32 *origin_pflash_pfcr2){ /* disable the CPU core global interrupt to avoid Flash access during the configuration */ asm('wrteei 0'); /* Read the values of PFB_CR*/ *origin_pflash_pfcr1 = READ32(FLASH_A_FMC FLASH_PFB_CR); *origin_pflash_pfcr2 = READ32(FLASH_B_FMC FLASH_PFB_CR); /* Disable Caches */ CLEAR32(FLASH_A_FMC FLASH_PFB_CR, FLASH_PFB_CR_BFEN); CLEAR32(FLASH_B_FMC FLASH_PFB_CR, FLASH_PFB_CR_BFEN); /* re-enable the CPU core global interrupt */ asm('wrteei 1'); }
/****************************************************************** Restore configuration register of FCM *******************************************************************/void RestoreFlashControllerCache(UINT32 pflash_pfcr1, UINT32 pflash_pfcr2){ /* disable the CPU core global interrupt to avoid Flash access during the configuration */ asm('wrteei 0'); WRITE32(FLASH_A_FMC FLASH_PFB_CR, pflash_pfcr1); WRITE32(FLASH_B_FMC FLASH_PFB_CR, pflash_pfcr2); /* re-enable the CPU core global interrupt */ asm('wrteei 1'); }/*****************************************************************Main function******************************************************************/void main(void){ UINT32 pflash_pfcr1, pflash_pfcr2; /* structure used for stack RAM code run */ FnCmdInRamStruct FnCmdInRam; /* Create a copy of the function codes in RAM routine on stack */ FnCmdInRam = *(FnCmdInRamStruct *)((UINT32)DisableFlashControllerCache); ((ram_fuc_DisableFlashControllerCache)(&FnCmdInRam))(&pflash_pfcr1,&pflash_pfcr2); /* Create a copy of the function codes in RAM routine on stack */ FnCmdInRam = *(FnCmdInRamStruct *)((UINT32)RestoreFlashControllerCache); ((ram_fuc_RestoreFlashControllerCache)(&FnCmdInRam))(pflash_pfcr1,pflash_pfcr2);
while(1) { ; }}
其实现要点如下:

① 不同的目标函数,可以使用同一个结构体局部变量以介绍系统stack,但是需要定义和使用对应的函数原型函数指针类型进行重定向调用;

② 使用的结构体临时变量要足够存储目标函数,且应用工程系统堆栈要设置足够大(通过应用工程的链接文件进行配置)

总结

本文详细介绍了如何利用C语言对结构体类型局部变量的初始化机制和函数指针,实现将存储在Flash上的应用程序代码重定向(自动拷贝)到系统栈(stack)运行具体方法和步骤。

该方法相较于传统的重定向实现方法有如下优势:

①能够更加高效的利用SRAM,大大节省了代码重定向到RAM中运行所需的SRAM存储器尺寸。从而,本方法可以适用于SRAM存储器尺寸较小的MCU平台;

② 不需要修改应用工程的链接文件和启动代码;

相应的缺点如下:

① 需要占用额外的系统栈,必须确保应用工程的stack设置足够大,否则容易造成堆栈溢出;

② 每次调用时,都需要执行拷贝过程,会消耗一定的CPU loading。
图片

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多