目录
前言
嵌入式工程师应该都知道没有初始值的全局变量或者静态局部变量存放在.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运行时变量的初始化。
注:本文章引用了一些第三方工具和文档,若有侵权,请联系作者删除!
正文
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地址,数据长度)。
有了以上的理论分析,如下的代码实现应该容易看懂了:
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);
}
}
}
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如下所示:
有了以上的理论分析,如下的代码实现应该容易看懂了:
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;
}
}
}
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如下所示:
有了以上的理论分析,如下的代码实现应该容易看懂了:
/*
* 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
}
}
}
Copy Table的起始地址_lc_ub_table = 0x 80003036, 结束地址_lc_ue_table = 0x80003E3C
TC387芯片中Copy Table的Memory Layout的解析如下所示:
Copy Table用来初始化C环境下全局变量或者静态局部变量,Copy Table中保存了全局变量或者静态局部变量的RAM地址或者初始值的ROM地址以及数据长度信息,开发者需要在Startup代码中使用Copy Table完成全局变量或者静态局部变量的初始值从ROM到RAM的搬运(或者清零.BSS段)。而这个过程,由于编译器特性不一样,需要开发者根据编译器特性来具体实现,具体参考正文内容。
值得注意的是,实际开发中如果我们使用编译器的启动代码,这个工作往往编译器自动帮忙我们实现了(很多初级的工程师可能都不知道这个copy table的存在)。但是,如果我们从芯片上电到Main函数之前的启动代码都是自己实现的(安全启动),Copy Table就必须要考虑了。
End
「汽车电子嵌入式在CSDN上同步推出AUTOSAR精进之路专栏,本专栏每个模块完全按实际项目中开发及维护过程来详细介绍。模块核心概念介绍、实际需求描述、实际工程配置、特殊需求介绍及背后原理、实际工程使用经验总结。目的是让读者看完每一个章节后能理解原理后根据需求完成一个模块的配置或者解决一个问题。」
点击文章最后左下角的阅读原文可以获取更多信息
或者复制如下链接到浏览器获取更多信息
https://blog.csdn.net/qq_36056498/article/details/132125693
文末福利
2.为便于技术交流,创建了汽车电子嵌入式技术交流群,可尽情探讨AP,CP,DDS,SOME/IP等前沿热点话题,后台回复“加群”即可加入;
注:本文引用了一些第三方工具和文档,若有侵权,请联系作者删除!
推荐阅读
汽车电子嵌入式精彩文章汇总第一期:20210530-20230703
汽车电子嵌入式精彩文章汇总第2期
汽车电子嵌入式精彩文章汇总第3期
【OS】AUTOSAR OS Event实现原理
【OS】AUTOSAR OS Spinlock实现原理(下篇)
【OS】AUTOSAR OS Spinlock实现原理(上篇)
CanNm处于PBS状态下接收到一帧诊断报文DCM会响应吗
TC3xx芯片CAN模块详解
AUTOSAR OS Alarm实现原理
AUTOSAR OsTask切换原理
TC3xx 芯片SPI模块详解
AUTSOAR ComStack如何实现PDU只收不发的
AUTOSAR OsStack监控原理
AUTOSAR架构下ICU唤醒详解
CanNm报文的触发发送详解
Can报文能发不能收问题分析
End
欢迎点赞,关注,转发,在看,您的每一次鼓励,都是我最大的动力!
汽车电子嵌入式
微信扫描二维码,关注我的公众号