C语言引用连接脚本lds中的符号——清除bss段,c实现方式
之前我们的启动文件清除bss和拷贝都是通过汇编的方式的实现,但是,我们能够使用C语言,就不使用汇编:
先看连接脚本:
SECTIONS { . = 0x30000000; __code_start = .; . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : { *(.rodata) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } _end = .; }
现在我们编写C语言的copy和clean函数,但是在我们的c程序中,需要访问连接脚本中的符号。
先看代码,稍后解释:
void copy2sdram(void) { /* 要从lds文件中获得 __code_start, __bss_start * 然后从0地址把数据复制到__code_start */ extern int __code_start, __bss_start; volatile unsigned int *dest = (volatile unsigned int *)&__code_start; volatile unsigned int *end = (volatile unsigned int *)&__bss_start; volatile unsigned int *src = (volatile unsigned int *)0; while (dest < end) { *dest++ = *src++; } } void clean_bss(void) { /* 要从lds文件中获得 __bss_start, _end */ extern int _end, __bss_start; volatile unsigned int *start = (volatile unsigned int *)&__bss_start; volatile unsigned int *end = (volatile unsigned int *)&_end; while (start <= end) { *start++ = 0; } }
start.S部分展示:
先不管连接脚本的引用,我们现在讨论一个问题,我们这里使用bl,是相对跳转,我们必须认识到一个问题,此时我们的sdram虽然初始化好了,但是此刻我们sdram里面是没有程序的,需要先进行重定位,把nor flash的内容全部copy到sdram,所以此时(还没有拷贝的时候) copy2sdram是在nor flash里面执行的,那么使用bl也就可以理解了。但是clean_bss这个函数,到底应不应该使用bl呢?似乎好像我们完成了重定位,让text段,rodata段,data段都重定位到了sdram,可是,sdram这上面几个段之后才开始存放bss段,而bss段的变量是为0值得,不会放在bin文件中,如果这里不使用bl clean_bss,而使用 ldr pc,=clean_bss绝对跳转,程序没有输出,因为此时没有保存lr寄存器的值。
但是,如果非要使用ldr绝对跳转呢?可以这样,增加一个标号:
ldr pc,=m m: bl clean_bss bl main /* 绝对跳转, 跳到SDRAM */
增加标号m,绝对跳转到m处执行,这个时候程序运行于sdram了,后面使用bl指令即可。
为什么想要这样呢?
严格来说,重定位完毕,我们的程序就可以在sdram中运行了,而我们之前的程序采用
bl clean_bss
ldr pc,=main
这样会导致清除bss段的代码还是运行于nor flash上,而并非出于sdram上,所以我们采用绝对跳转的方式。清除bss段。
回到C语言引用连接脚本的方法:
在编译阶段,会有一个叫做符号表的东西,
c程序中不保存lds中的变量,但是万一c程序需要使用lds的文件,就可以通过编译程序时的符号表来引用。
上图已经显示了,C语言是变量名,然后变量地址,而连接脚本lds中,是符号名,然后它的值。
现在,我们这样对比,我们在C语言中要取一个变量的地址,是使用 &符号,就得到符号表中对应的地址了,
同样的道理,在c中引用连接脚本的符号,我们需要的是lds符号的值,也还是通过 &符号,只不过,此时取地址符 & +lds的符号,得到的是符号的值,而不是符号本身的地址了,而这符号的值,却又恰好是某个地址。(请仔细体会这个对比)
所以,我们上面采用外部声明变量的方式。
C函数怎么使用lds文件中的变量abc?
a. 在C函数中声明改变量为extern类型, 比如:
extern int abc;
b. 使用时, 要取址, 比如:
int *p = &abc; // p的值即为lds文件中abc的值
声明的类型可以是int也可以char。此时类型不重要,但是,最好是声明成你想使用的那种类型。
这是官方的参考,所以具体声明成什么类型,虽然不影响,但是最好是声明成你想使用的类型。因为在符号表中,连接脚本的符号表是符号和值,直接存储了,没有再开辟地址,在C语言使用extern声明之前,lds中的符号已经被定义在符号表中了,也就意味着,char或int类型修饰对符号的值没有影响,该值早就在符号表确定了,而我们C语言中之所以要声明一个类型,是为了方便后面要使用lds中这个符号的操作,而且C语言语法也要求标识符必须要有类型修饰。
对lds中的符号取地址,得到就是这个符号的值,而这个值,正好代表了某个地址。
欢迎加入作者的小圈子
扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并获取更多隐藏干货,QQ交流群:816747642 微信公众号:Crystal软件学堂
作者:Crystal软件学堂 bilibili视频教程地址:https://space.bilibili.com/5782182 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。 如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。 文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。 |