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中的符号取地址,得到就是这个符号的值,而这个值,正好代表了某个地址。

posted @ 2017-12-19 22:34  Crystal_Guang  阅读(1183)  评论(0编辑  收藏  举报