tag 标签: ld

相关帖子
相关博文
  • 热度 25
    2020-8-18 09:39
    3314 次阅读|
    0 个评论
    内容提要 引言 1. 基于C语言的嵌入式MCU应用工程编译结果的代码和数据段(segment)概述 1.1 .text/.code段 1.2 .bss段 1.3 .data段 1.4 堆(.heap) 1.5 栈(.stack) 2. .text、.bss、.data、.heap和.stack段在嵌入式MCU内存中的空间分布 3. 链接器对全局符号的处理--强符号(Strong Symbols)与弱符号(Weak Symbols) 3.1 什么是强符号(Strong Symbols)和弱符号(Weak Symbols) 3.2 链接器对全局符号的处理规则和注意事项 4. 什么是.COMMON段及其与.bss段的关系 4.1 什么是.common段 4.2 通过一个实例来分析.bss段--未初始化的全局变量与初始为0的全局变量是否等同? 4.3 全局变量定义的注意事项和建议 5. S32DS IDE中print size信息解析 5.1 配置使能S32DS IDE应用工程编译结果的print size输出功能 5.2 S32DS IDE应用工程编译结果print size输出信息解析 5.3 S32DS IDE应用工程编译结果print size输出bss大小构成分析 总结 引言 在嵌入式MCU软件开发中, 链接文件 (Linker File)和 内存映射文件 (memory map,简称map文件)是学习和掌握一个CPU架构(比如ARM Cortex A/R/M和Power e200z0/2/3/4/6/7)系列MCU的基础和关键所在。前者作为 输入 告诉工具链目标MCU的存储器分布和大小消息以及应用程序的代码和数据分配规则/信息,后者为工具链中链接器 输出 结果的总结概括,是当前应用工程,是否满足给定目标MCU链接文件规定内存分配结果的验证。 虽然链接文件和map文件因工具链不同而形式和语法有所不同 ,比如开发 S08/S12/MagniV S12Z系列MCU的CodeWarrior IDE应用工程的链接文件为.prm文件,而开发KEA/S32K/Qorivva MPC57xx/S32x系列MCU的S32DS IDE(实质为GNUGCC工具链)的为.ld文件, 但其中所包含的信息却是大同小异的 。 因此, 如果能够了解和掌握链接文件和map文件的本质---信息构成和功能,即可一通百通,举一反三 。 1. 基于C语言的嵌入式MCU应用工程编译结果的代码和数据段(segment)概述 通常,基于C语言的嵌入式MCU应用工程的编译结果( elf 文件)中都会包含以下代码和数据段: 1.1 .text/.code   .text/.code代码段(text segment/code segment) 是用于存储程序执行代码的一块内存区域。   这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读( 某些架构也允许代码段为可写,即允许修改程序,将SRAM属性定义为只读存储器(read only),也可以用作.text段的存储器 )。 Tips : 代码段中,除了用户程序代码(.code/.text)外, 还包含以下只读的常数数据: .const/.rodata 段:存放const定义的只读数据 .string 段:存放字符串常量; .copy/.init 段:存放有初始化值全局变量( .data 段)的初始化值; 1.2 .bss段 .bss符号起始块段(bss segment) 是用于存储程序中初始化值为0或未初始化的全局变量的一块内存区域。   bss是英文 B lock S tarted by S ymbol的简称。   bss段属于静态内存分配。 1.3 .data段    .data数据段(data segment) 是用于存放程序中已初始化的全局变量的一块内存区域。   数据段属于静态内存分配。其初始化值存储在Flash中,掉电不丢失,但运行时(runtime)地址在SRAM中。 所以.data段占用相同大小的Flash和SRAM存储器地址空间。 .data段在每次MCU复位启动(startup)过程中,由启动代码完成初始化,从而保证,在进入main()函数后,第一次使用.data段中的全局变量时,初始化值是编程时设置的。 1.4 堆(.heap)   堆(.heap 堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。   当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);   当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。 1.5 栈(.stack) 栈(.stack) 又称堆栈,由C语言运行时自动分配和管理(通过嵌入式MCU CPU内核专用堆栈指针寄存器 SP -- S tack P ointor实现),是C语言运行访问最为频繁的内存空间。   在函数中定义的局部变量( 但不包括static声明的变量,static意味着在数据段中存放变量 )通常会被分配带栈中。    除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中返回给调用函数。   栈的 压栈(push) 和 出栈(pop )具有 后进先出 ( LIFO--L ast I n F irst O ut)的特点,其保证了C语言程序函数调用和中断处理时的现场保护/恢复,从而使得嵌入式MCU中的应用程序可以正常工作。   从这个意义上讲,可将堆栈看成一个数据临时寄存和交换的内存区。 2. .text、.bss、.data、.heap和.stack段在嵌入式MCU内存中的空间分布 对于嵌入式MCU来说,其内存(Memory, 也叫存储器)主要包括以下几大类: SRAM :静态随机访问存储器,可按字节读写,写数据先无需擦除,读写速度最快,掉电数据丢失; Flash :闪速存储器(简称闪存)。包括 P-Flash (Program Flash,存储程序)/ C-Flash (Code Flash,存储代码)和 D-Flash (Data Flash,存储数据),数据掉电不丢失,必须先擦除再编程,按sector/block擦除,sector/block尺寸大(典型为512B/1KB/2KB/4KB,16KB/128KB甚至256KB),读写速度慢; EEPROM :电可擦除只读存储器,数据掉电不丢失,必须先擦除再编程,按sector/block擦除,sector尺寸小(典型为2/4字节),读写速度较快; 以上介绍的.text、.bss、.data、.heap和.stack段在嵌入式MCU中的存储器中的典型分配关系如下表: SRAM Flash EEPROM .text段 √(remap重映射) √ √ .bss段 √ × × .data段 √ √(存储初始化值) √(存储初始化值) .heap段 √ × × .stack段 √ × × 3. 链接器对全局符号的处理-- 强符号(Strong Symbols)与弱符号(Weak Symbols) 3.1 什么是 强符号(Strong Symbols)和弱符号(Weak Symbols) 对于链接器来说,所有的全局符号(全局变量和全局函数)可分为两种: 强符号 (Strong Symbols)和 弱符号 (Weak Symbols)。 gcc 的 attribute 中有个 __attribute__((weak)) ,就是用来声明这个符号是弱符号的。 gcc 手册中这样写道: The weak attribute causes the declaration to be emitted as a weak symbol rather than a global. This is primarily useful in defining library functions which can be overridden in user code, though it can also be used with non-function declarations. Weak symbols are supported for ELF targets, and also for a.out targets when using the GNU assembler and linker. GNU GCC手册 一般来说,函数和已初始化的变量是强符号,而未初始化的变量是弱符号 。 3.2 链接器对全局符号的处理规则和注意事项 链接时,全局符号需要满足下列三条规则: 1. 同名的强符号只能有一个。 2. 有一个强符号和多个同名的弱符号是可以的,但定义会选择强符号的。 3. 有多个弱符号时,链接器可以选择其中任意一个/ 选择占用内存空间最大的那个(不同的工具链处理不同) 。 这三条规则看起来很好理解,其实不然,尤其是当这些 弱符号类型和强符号不同时 ! 表面上看起来正确的程序会导致严重的错误 ! 比如下面两个C文件: a.c: #include "stdio.h"int x=7; /*this a strong symbol with initial value*/ void Func1(void) { if(x==7) printf("global variable x = %d\r\n", x) ; else printf("global variable x = %f\r\n", x) ; } b.c: double x;/*this a weak symbol without initial value*/ void Func2(void) { x = 0.5f; } main.c: extern void Func1(void); extern void Func1(void); void main(){ Func1(); Func2(); } 将它们一起编译运行。并且在 Func 2()函数中给x赋值,你会发现,y也改变了!虽然x被看作是double,但其定义会取a.c中的int x,也就是说,在b.c中会把a.c中的int x当double来用!这当然是错误!之所以会这样,就是因为上面的 规则2 。 避免这种错误的一个方法是,给 gcc 加上- fno-common 选项。 当编译器将一个编译单元编译成目标文件的时候,如果该编译单元包含了弱符号(未初始化的全局变量就是典型的弱符号),那么该弱符号最终所占空间的大小此时是未知的,因为有可能其他编译单元中同符号名称的弱符号所占的空间比本编译单元该符号所占的空间要大。所以编译器此时无法为该弱符号在BSS段分配空间,因为所需要的空间大小此时是未知的。但是链接器在链接过程中可以确定弱符号的大小,因为当链接器读取所有输入目标文件后,任何一个弱符号的最终大小都可以确定了,所以它可以在最终的输出文件的BSS段为其分配空间。所以总体来看,未初始化的全局变量还是被放在BSS段。 《程序员的自我修养》 4. 什么是.COMMON段及其与.bss段的关系 在嵌入式MCU的编译结果map文件中,我们经常能够看到 .COMMON 段,它属于我们前面介绍的哪一段呢? 4.1 什么是.common段 前文讲到,.bss段中存放的是未初始化或者初始化值为0的全局变量。其实在很多工具链中, 默认都为未初始化 (定义时没有赋初始化值,比如如定义结构体类型的全局变量) 的全局变量单独分配一个段,那就是 .COMMON 段了 。在链接的最后阶段将其与初始化值为0的全局变量.bss段一起合并放在真正的.bss段中。 因此,在链接文件中,通常.bss段的定义如下(e.g. KEA/S32K的S32DS 应用工程链接文件): /* Uninitialized data section */ .bss : { /* This is used by the startup in order to initialize the .bss section */ . = ALIGN(4); __START_BSS = .; __bss_start__ = .; *(.bss) *(.bss*) *(COMMON) . = ALIGN(4); __bss_end__ = .; __END_BSS = .; SRAM 即编译器对未初始化或初始化值为0全局变量的处理方法如下: 如果初始化的值为0,那么直接将其保存在 .bss 段; 如果没有初始化值,则将其保存在. common 段; 待到链接时再将以上 .bss 段和. common 段合并,统一放入BSS段; 4.2 通过一个实例来分析.bss段-- 未初始化的全局变量与初始为0的全局变量是否等同? 尽管未初始化的全局变量有可能在编译阶段被保存在common段,但是最终还是会放到BSS段。那么我们是否可以将未初始化的全局变量与初始为0的全局变量等同起来呢? 请看下面的测试代码: 文件test1.c: #include int init = 0; void init1() { if (0 == init) { init = 1; printf("init1\n"); } } 文件test2.c: #include int init = 1; void init2() { if (init) { init = 0; printf("init2\n"); } } 在main函数中,调用test1.c和test2.c中的两个初始化函数: 文件main.c void init1(); void init2(); int main(){ init1(); init2(); return 0; } 使用gcc编译链接以上3个文件: gcc -g -c test1 .c gcc -g -c test2 .c gcc -g -o test main .c test1 .o test2 .o 由于test1.c和test2.c中都定义了全局变量init,链接时会报错: test2.o:(.data+0x0) : multiple definition of `init' test1.o:/root/work/test/test1.c:4 : first defined here collect2 : ld returned 1 exit status 但是,如果将文件test1.c中的int init = 0改为int init,对于test1.c来说,这个改动不影响其逻辑,因为init如果未初始化,其值也应该是0。 修改后的test1.c #include int init; void init1(){ if (0 == init) { init = 1; printf("init1\n"); } } 重新编译链接 gcc -g -c test1 .c gcc -g -Wall -o test main .c test1 .o test2 .o 此时即使使用 -Wall 打开所有warning,也没有任何警告和错误,已经生成了输出文件test。 执行test,输出如下: #./test init2 可以看到test1.c中并未打印出期望的init1,这是为什么呢? 第一种情况,当test1.c中的init被初始化为0时,尽管init被放置在.bss段,但是它是一个强符号。而test2.c中,定义了init为1,也是一个强符号,所以引发了错误。 第二种情况,当test1.c中的init不进行初始化,尽管其值仍然为0,但是其被被保存在common段,为一个 弱符号 。当test2.c中定义了init为1一个 强符号 ,那么 在链接的过程中,gcc会用这个强符号覆盖掉弱符号,并不会引起链接冲突错误 。 但是在运行阶段,进入init1时,这个init的值却并不是其所期望的值,因此导致没有打印init1 。 4.3 全局变量定义的注意事项和建议 从以上分析中可以看到:当定义全局变量时,有两点需要注意: 1. 如果只有本文件使用,那么需要添加上static以限制其作用域; 2. 如果不能使用static,那么一定要为该全局变量定义初值,即使这个值就是0。 这样可以保证该变量为强符号,当名字冲突时,可以在链接被发现,而不是被未知的值覆盖。 当然最好能够避免使用全局变量,或者定义一个独一无二的名字 。 另外,在编译阶段,我们还可以通过编译器的 -fno-common 选项来 禁止将未初始化的全局变量放入到common段 。 比如,同样的文件,若添加 -fno-common的 编译选项,则链接时将能够发现全局变量init的重复定义错误: #gcc -g -fno-common -c test1.c #gcc -g -fno-common -c test2. #gcc -g -fno-common -o test main.c test1.o test2.o test2.o:(.data+0x0): multiple definition of `init' test1.o:/root/work/test/test1.c:6: first defined here collect2: ld returned 1 exit status 转载于: https://mp.weixin.qq.com/s?__biz=MzI0MDk0ODcxMw==&mid=2247486096&idx=1&sn=1f2dadd7e71a234cc3ffe92d97ee6b5e&chksm=e9124e16de65c700963547fe9f11d7bae5bef7b8a8490d258c4415827bf6440e15311af880bf&scene=21#wechat_redirect 免责声明版权归原作者所有!
相关资源
  • 所需E币: 1
    时间: 2022-7-23 11:09
    大小: 154.55KB
    上传者: Argent
    AOIforLDScaleValue
  • 所需E币: 0
    时间: 2020-12-23 23:19
    大小: 210.27KB
    上传者: czd886
    基于ARM的高精度可调谐LD温度控制器
  • 所需E币: 4
    时间: 2020-1-2 01:22
    大小: 196.5KB
    上传者: 微风DS
    LD工作原理作为光纤传感器中关键的光学元件之一的光源,其稳定度直接影响着光纤传感器的准确度。本文所涉及的光纤传感器采用的是半导体激光器光源,半导体激光器具有单色性好、方向性好、体积小、光功率利用率高等优点,但是,光功率输出受外界环境变化的影响较大。因此,本文针对半导体激光光源的工作原理和特性,设计了一种简单可行的自动功率控制(APC)驱动电路,通过背向监测光电流形成反馈,实现恒功率控制。并且,引入了慢启动电路,防止电源电压的干扰,使激光器不会受到每次开启电源时产生的过流冲击,延长了激光器的使用寿命。经实验验证,该电路解决了激光器在使用中输出功率不稳定的问题,其稳定度优于0.5%,达到了较好的稳流效果。1光源的工作原理和特性目前,实际应用的光源有表面光发射二极管(LED)、激光二极管(LD)、超辐射二极管(SLD)、超荧光光源(SFS)等。随着光纤传感技术的迅速发展,体积小、质量轻、功耗小、容易与光纤耦合的LD等半导体光源应用越来越广泛。本文主要研究半导体LD的驱动设计。1.1LD发光机理分析LD的基本结构为:垂直于PN结面的一对平行平面构成法布里-珀罗谐振腔,它们可以是半导体晶体的解理面,也可以是经过抛光的平面。其余两侧面则相对粗糙,用以消除主方向外其他方向的激光作用。当半导体的PN结加有正向电压时,会削弱PN结势垒,迫使电子从N区经PN结注入P区,空穴从P区经过PN结注入N区,这些注入PN结附近的非平衡电子和空穴将会发生复合,从而发射出波长为λ的光子,其公式λ=hc/Eg,(1)式中h为普朗克常数;c为光速;Eg为半导体的禁带宽度。如果注入电流足够大,则会形成和热平衡状态相反的载流子分布,即粒子数反转。当有源层内的载流子在大量反转情况下,少量自发辐射产生的光子由于谐振腔两端面往复反射而产生感应辐射,造成选频谐振正反馈,或者说对某……