目录
前言
嵌入式工程师应该都知道没有初始值的全局变量或者静态局部变量存放在.BSS段,有初始值的全局变量或者静态局部变量存放在.DATA段,芯片上电后我们需要将.BSS段都初始化为0值,将ROM中保存的全局变量或者静态局部变量的初始值拷贝到RAM中也就是.DATA段中。
那么问题来了,在哪个地方进行.BSS段清零以及.DATA段中RAM初始值的拷贝了?
熟悉英飞凌TC3xx芯片的朋友应该知道英飞凌芯片的启动分为6个阶段(Phase1 - Phase6),在__StartUpSoftware_Phase6()中完成.BSS段和.DATA段的初始值拷贝。TC3xx芯片的启动流程参考以下的文章:
参考文章:https://zhuanlan.zhihu.com/p/644563274
如下图所示,最终由Ifx_Ssw_C_InitInline()函数完成初始化C运行时变量,也就说.BSS段的清零及.DATA段的初始化。
而Ifx_Ssw_C_InitInline()函数的具体实现根据编译的不同而不同,本文就来介绍3个常用编译器如何完成C运行时变量的初始化。
注:本文章引用了一些第三方工具和文档,若有侵权,请联系作者删除!
正文
1.GHS编译器
1.1. Clear and Copy Tables
Green Hills编译器会自动在.secinfo段(section)中定义三个表(Tables),Clear Table, Copy Table,以及compressed Copy table.
Table Type |
Start address |
End address |
Clear Table |
__ghsbinfo_clear |
__ghseinfo_clear |
Copy Table |
__ghsbinfo_copy |
__ghseinfo_copy |
compressed Copy table |
__ghsbinfo_comcopy |
__ghseinfo_comcopy |
注意:我们这里只套路Clear Table和Copy Table, compressed copy table还不知道咋用的。
Clear Table中存放的就是初始化.BSS段相关信息(未初始化的全局变量/静态局部变量的RAM地址、数据长度)。
Copy Table中中存放的就是初始化.DATA段相关信息(初始化的全局变量/静态局部变量的RAM地址、初始化的全局变量/静态局部变量的初始值所在的ROM地址,数据长度)。
1.2 Ifx_Ssw_C_InitInline实现
有了以上的理论分析,如下的代码实现应该容易看懂了:
typedef int ptrdiff_t;
typedef unsigned int syze_t;
typedef signed int signed_size_t;
extern void *memcpy(void *s1, const void *s2, syze_t n);
extern void *memset(void *s, int c, syze_t n);
/* rodata is absolute */
typedef const char rodata_ptr[];
IFX_SSW_INLINE void Ifx_Ssw_C_InitInline(void)
{
/*----------------------------------------------------------------------*/
/* */
/* Clear BSS */
/* */
/*----------------------------------------------------------------------*/
{
extern rodata_ptr __ghsbinfo_clear;
extern rodata_ptr __ghseinfo_clear;
extern rodata_ptr __ghsbinfo_aclear;
void **b = (void **)((char *)__ghsbinfo_clear);
void **e = (void **)((char *)__ghseinfo_clear);
void **a = __ghsbinfo_aclear != 0 ?
((void **)((char *)__ghsbinfo_aclear)) : e;
/* Warning: This code assumes
* __ghsbinfo_clear <= __ghsbinfo_aclear <= __ghseinfo_clear
* Which is currently enforced with elxr
* OR
* __ghsbinfo_aclear == 0 (i.e.: undefined)
*/
int OFFSET = PIDBASE;
while (b != e)
{
void *t; /* target pointer */
ptrdiff_t v; /* value to set */
size_t n; /* set n bytes */
while (b != a)
{
t = OFFSET + (char *)(*b++);
v = *((ptrdiff_t *)b); b++;
n = *((size_t *)b); b++;
(void)memset(t, v, n);
}
OFFSET = 0;
a = e;
}
}
/*----------------------------------------------------------------------*/
/* */
/* Copy from ROM to RAM */
/* */
/*----------------------------------------------------------------------*/
{
extern rodata_ptr __ghsbinfo_copy;
extern rodata_ptr __ghsbinfo_tcopy;
extern rodata_ptr __ghseinfo_copy;
void **b = (void **)((char *)__ghsbinfo_copy);
void **m = (void **)((char *)__ghsbinfo_tcopy);
void **e = (void **)((char *)__ghseinfo_copy);
while (b != e)
{
void *t; /* target pointer */
void *s; /* source pointer */
size_t n; /* copy n bytes */
t = ((b < m) ? PIDBASE : PICBASE) + (char *)(*b); b++;
s = PIRBASE + (char *)(*b++);
n = *((size_t *)b); b++;
(void)memcpy(t, s, n);
}
}
}
2.GNU编译器
2.1 Clear and Copy Tables
GNU的GCC编译器不会自动给我们创建Clear Tables及Copy Tables,需要我们在Link file(连接器脚本)中自定义Cleare Tables和Copy Table这些标识符(地址符号)。
参考文章:https://www.cnblogs.com/uestcliming666/p/11464709.html
__clear_table及__copy_table的memory layout如下所示:
2.2 Ifx_Ssw_C_InitInline实现
有了以上的理论分析,如下的代码实现应该容易看懂了:
extern unsigned int __clear_table[]; /**< clear table entry */
extern unsigned int __copy_table[]; /**< copy table entry */
IFX_SSW_INLINE void Ifx_Ssw_C_InitInline(void)
{
Ifx_Ssw_CTablePtr pBlockDest, pBlockSrc;
unsigned int uiLength, uiCnt;
unsigned int *pTable;
/* clear table */
pTable = (unsigned int *)&__clear_table;
while (pTable)
{
pBlockDest.uiPtr = (unsigned int *)*pTable++;
uiLength = *pTable++;
/* we are finished when length == -1 */
if (uiLength == 0xFFFFFFFF)
{
break;
}
uiCnt = uiLength / 8;
while (uiCnt--)
{
*pBlockDest.ullPtr++ = 0;
}
if (uiLength & 0x4)
{
*pBlockDest.uiPtr++ = 0;
}
if (uiLength & 0x2)
{
*pBlockDest.usPtr++ = 0;
}
if (uiLength & 0x1)
{
*pBlockDest.ucPtr = 0;
}
}
/* copy table */
pTable = (unsigned int *)&__copy_table;
while (pTable)
{
pBlockSrc.uiPtr = (unsigned int *)*pTable++;
pBlockDest.uiPtr = (unsigned int *)*pTable++;
uiLength = *pTable++;
/* we are finished when length == -1 */
if (uiLength == 0xFFFFFFFF)
{
break;
}
uiCnt = uiLength / 8;
while (uiCnt--)
{
*pBlockDest.ullPtr++ = *pBlockSrc.ullPtr++;
}
if (uiLength & 0x4)
{
*pBlockDest.uiPtr++ = *pBlockSrc.uiPtr++;
}
if (uiLength & 0x2)
{
*pBlockDest.usPtr++ = *pBlockSrc.usPtr++;
}
if (uiLength & 0x1)
{
*pBlockDest.ucPtr = *pBlockSrc.ucPtr;
}
}
}
3.Tasking编译器
3.1 Clear and Copy Tables
Tasking编译器不再区分Clear Table和Copy Tables,统一都用一个copy table来存放.bss和.data段的相关信息。_lc_ub_table标识符表示copy table的开始地址,_lc_ue_table标识符表示copy table的结束地址。
值得注意的是,如果我们想让Tasking编译器生成_lc_ub_table和_lc_ue_table标识符的地址信息,就必须不能勾选如下的编译选项。
如果勾选了如下选项,就意味着需要和GNU编译器一样在连接器脚本中自定义copy table相关的标识符。
Copy Table的memory layout如下所示:
3.2 Ifx_Ssw_C_InitInline实现
有了以上的理论分析,如下的代码实现应该容易看懂了:
/*
* Startup_CompilerTasking.c
*
* Created on: 2024年10月27日
* Author: Administrator
*/
#include "Std_Types.h"
#define LC_UB_TABLE_TYPE_BSS (0x2u)
#define LC_UB_TABLE_TYPE_DATA (0x1u)
extern uint32 _lc_ub_table[];
extern uint32 _lc_ue_table[];
uint32* g_lc_ub_table;
uint32* g_lc_ue_table;
void _c_init(void)
{
const uint32* copyTable;
const uint32* currentPtr;
const uint32* srcU32Ptr;
uint32* dstU32Ptr;
uint32 sectionType;
uint32 length;
uint32 value;
uint32 count;
uint16* srcU16Ptr;
uint16* dstU16Ptr2;
uint8* srcU8Ptr;
uint8* dstU8Ptr;
/*Just for debug*/
g_lc_ub_table = (uint32*)&_lc_ub_table;
g_lc_ue_table = (uint32*)&_lc_ue_table;
for(copyTable = (uint32*)&_lc_ub_table;
copyTable != (uint32*)&_lc_ue_table;
copyTable += 4)
{
currentPtr = copyTable;
sectionType = currentPtr[0];
dstU32Ptr = (uint32*)currentPtr[1];
length = currentPtr[3];
if(LC_UB_TABLE_TYPE_BSS == sectionType)
{
value = currentPtr[2];
for(count = length / 4; count > 0; count--)
{
*dstU32Ptr++ = value;
}
dstU16Ptr2 = (uint16*)dstU32Ptr;
if(length & 0x2)
{
*dstU16Ptr2++ = (uint16)value;
}
dstU8Ptr = (uint8*)dstU16Ptr2;
if(length & 0x1)
{
*dstU8Ptr = (uint8)value;
}
}
else if(LC_UB_TABLE_TYPE_DATA == sectionType)
{
srcU32Ptr = (uint32*)currentPtr[2];
for(count = length / 4; count > 0; count--)
{
*dstU32Ptr++ = *srcU32Ptr++;
}
dstU16Ptr2 = (uint16*)dstU32Ptr;
srcU16Ptr = (uint16*)srcU32Ptr;
if(length & 0x2)
{
*dstU16Ptr2++ = *srcU16Ptr++;
}
dstU8Ptr = (uint8*)dstU16Ptr2;
srcU8Ptr = (uint8*)srcU16Ptr;
if(length & 0x1)
{
*dstU8Ptr = *srcU8Ptr;
}
}
else
{
//Do nothing
}
}
}
3.3 调试分析
Copy Table的起始地址_lc_ub_table = 0x 80003036, 结束地址_lc_ue_table = 0x80003E3C
TC387芯片中Copy Table的Memory Layout的解析如下所示:
4.总结
Copy Table用来初始化C环境下全局变量或者静态局部变量,Copy Table中保存了全局变量或者静态局部变量的RAM地址或者初始值的ROM地址以及数据长度信息,开发者需要在Startup代码中使用Copy Table完成全局变量或者静态局部变量的初始值从ROM到RAM的搬运(或者清零.BSS段)。而这个过程,由于编译器特性不一样,需要开发者根据编译器特性来具体实现,具体参考正文内容。
值得注意的是,实际开发中如果我们使用编译器的启动代码,这个工作往往编译器自动帮忙我们实现了(很多初级的工程师可能都不知道这个copy table的存在)。但是,如果我们从芯片上电到Main函数之前的启动代码都是自己实现的(安全启动),Copy Table就必须要考虑了。
End
「
原文始发于微信公众号(汽车电子嵌入式):什么是Copy Table及如何使用Copy Table
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论