【C语言之高级编程】如何将指定变量或函数编译至固定的内存区域中?

如何将指定变量或函数编译至固定的内存区域?

  • 1. 内存类型
    • 1.1 bss段(Block Started by Symbol)
    • 1.2 data段(data segment)
    • 1.3 text段(code segment/text segment)
    • 1.4 dec
    • 1.5 堆(heap):
    • 1.6 栈(stack):
  • 2 链接脚本
    • 2.1 ld链接脚本
      • 2.1.1 链接配置
      • 2.1.2 MEMORY
      • 2.1.3 定位符'.'
      • 2.1.4 SECTION
      • 2.1.5 其他常用命令
      • 2.1.6 链接脚本完整示例
    • 2.2 ARM核内如何把指定函数编译到固定内存(使用ld链接脚本)
    • 2.3 lsl链接脚本

1. 内存类型

在我们讨论这个问题之前,需要先了解一下C语言中内存中有哪些类型:
在这里插入图片描述

1.1 bss段(Block Started by Symbol)

bss段属于静态内存分配,通常是指用来存放程序中未初始化的(或初始化为0的)全局变量和静态变量的一块内存区域。在程序执行之前bss段会自动清0,所以,未初始的全局变量在程序执行之前已经成0了。

bss段在执行文件中时候不占磁盘空间,要运行的时候才分配空间并清0。

特点是:可读写的,

1.2 data段(data segment)

数据段通常是指用来存放程序中已初始化的全局或静态变量的一块内存区域。

数据段属于静态内存分配。

1.3 text段(code segment/text segment)

text段通常是指用来存放程序执行代码的一块内存区域。

这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。

在代码段中,也有可能=包含一些只读的常数变量,例如字符串常量等。

1.4 dec

dec 是text,data和bss的算术和。

1.5 堆(heap):

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。

当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);

堆是先进先出(FIFO)数据结构,当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

注意:堆内存需要程序员手动管理内存,通常适用于较大的内存分配,如频繁的分配较小的内存,容易导致内存碎片化。

1.6 栈(stack):

栈又称堆栈,是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。

除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出(FIFO)特点,所以栈特别方便用来保存/恢复调用现场。

从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

注意:由于栈的空间通常比较小,一般 linux 程序只有几 M,故局部变量,函数入参应该避免出现超大栈内存使用,比如超大结构体,数组等,避免出现 stack overflow。

2 链接脚本

什么是链接脚本?链接脚本有什么作用?
链接脚本:用来描述程序是如何在内存空间中分布的(指定.text .data .bss在内存中的地址)。编译器会根据链接脚本输出可执行文件。

ARM编译器用的scat格式的链接脚本,gcc编译器用的是ld格式的脚本,Tricore使用的是lsl格式的链接脚本,不同脚本语法是不一样的。

链接脚本的作用:将多个目标文件(.o)和库文件(.a)链接成一个可执行文件(输出文件),并控制输出文件的内存布局(地址分配)。

注: GCC在编译C语言文件的时候,会分别生成RO、RW、ZI部分。RO是只读段,也就是程序代码段(.text),就是具体函数代码;RW是读写数据段(.data),也就是初始化的全局变量;ZI为未初始化数据段(.bss),也就是那些未赋初值的变量,这个段不占用ROM空间,只有在程序运行的时候在RAM初始化为0。

链接脚本的作用也就是将这些编译出来的段整合到一起。

2.1 ld链接脚本

ld链接脚本由MEMORYSECTIONS,还有一些链接配置组成:

  • 链接配置(有的配置项目是可选的);
  • MEMORY可选的,如果没有,链接器则认为所有输入文件都位于同一个内存区域,并且从0x0开始;
  • SECTIONS是必须的;

2.1.1 链接配置

如一些符号变量的定义,入口地址,输出格式等;

STACK_SIZE  = 0x200
ENTRY(_START)
OUTPUT_ARCH(arm)
OUTPUT_FORMAT(elf32_littlearm)

ENTRY命令
ENTRY(begincode) 代表我们使用 begincode 作为程序入口地址,链接器会默认使用第一个可执行section作为程序入口点,即start。

ENTRY(Reset_Handler)将Reset_Handler函数设为程序的入口点,链接器需要知道程序的入口点,才能正确地组织可执行文件的时序,才能正确地执行编译、链接、优化代码。

ld有多种指定程序入口方式(优先级逐渐降低):
a、ld命令 -e选项;
b、连接脚本中ENTRY(symbol)的命令;
c、如果定义了_start符号,使用_start符号的值;
d、如果存在.text段,则使用.text段的第一字节的地址;
e、使用地址0x00000000;

2.1.2 MEMORY

MEMORY为链接器提供系统内存的布局信息,并确定内存区域的访问权限。链接器根据MEMORY的信息,将编译生成的.o目标文件中的代码、数据、符号等分配到不同的内存区域。

脚本格式如下:

MEMORY
{/*标准格式如下*/mem_name [(attr)] : ORIGIN = origin, LENGTH = lenRAM0 (xrw)  : ORIGION = (0x00000000), LENGTH = 2MRAM1 (xrw)  : ORIGION = (0x30000000), LENGTH = 128M
}

mem_name 是一块内存的名称,自定义,但不得重复,仅在ld文件中生效。

attr字符串是可选的属性列表:

RRead-only section只读段
WRead/write section读写段
XExecutable section可执行段
AAllocatable section可分配段
IInitialized section初始化段
LInitialized section同上
!Invert the sense of any of the preceding attributes反转任何前述属性的含义

关键字ORIGION : 区域的开始地址
关键字LENGTH : 区域的大小

而后,链接脚本可声明将指定的代码放到对应的memory区域——以下链接脚本代码将.text段存放到标号为RAM的内存区域,将.data段存放到标号为FLASH的内存区域:

SECTIONS
{.text >RAM   /*.text也可以后面再细分*/.data >FLASH
}

2.1.3 定位符’.’

‘.’ 表示当前地址,它可以被赋值,也可以赋值给某个变量。

如下就是将当前地址赋值给某个变量(链接器是按照SECTIONS里段顺序排列的,前面排完之后才能计算出当前地址)

RAM_START  =  . ;

如下就是将段放在特定的地址中:

SECTIONS
{. = 0x10000; /*将所有目标文件的text段从0x10000地址开始存放 */.text :{*(.text)}. = 0x80000000;.data : {*(.data)}
}

2.1.4 SECTION

SECTION的基本命令语法如下:

SECTIONS
{	/*标准格式如下*/section-name [address] [(type)] :[AT(lma)][ALIGN(section_align)][SUBALIGN(subsection_align)][constraint]{contentscontents...} [>region] [AT>lma_region] [:phdr :phdr ...] [=fillexp]
}

这么多参数中,只有section-name和contents是必须的,链接脚本的本质是描述输入和输出的关系。secname表示输出文件的段,即输出文件中有那些段,而contents就是描述输出文件的这个段从那些文件里抽取出来的,即输入文件。

region指的是mem_name即内存名(VMA地址)。

输出段名
section-name输出段名必须符合输出格式的约束,例如a.out 输出格式包含’.text’ ‘.data’ ‘.bss’ 三个输出段名。

输出段属性(节点属性)
NOLOAD 该部分应标记为不可加载,以便在程序运行时不会将其加载到内存中。
DSECT
COPY
INFO
OVERLAY 支持这些类型名称以实现向后兼容,并且很少使用。 它们都具有相同的效果:该段应标记为不可分配,以便在程序运行时不为该段分配内存。

输出段地址
每个段都有LMA(加载内存地址)和VMA(虚拟内存地址)
LMA = load memory address
VMA = vitual memory address

LMA就是程序放置的地址,VMA就是运行时的地址。
如果程序是在ram里运行,但程序是存储在flash里,则运行地址指向ram,而加载地址指向flash。

下述例子中VMA为RAM,LMA为FLASH;
[>region] 指定输出段分布在内存上的地址。
[AT(lma)] 指定该段的加载地址。另外,您也可以使用[AT>lma_region]表达式指定
在这里插入图片描述
如果未为输出段指定AT或AT>,则链接器将设置当前段的VMA和LMA与同一区域中的先前输出段的设置相同。 如果没有前面的输段或该段不可分配,则链接器会将LMA与VMA设置为相同的值。

输入段 描述
输入段描述由一个文件名(可选)和一个圆括号内的段名称列表组成。

例如:
*(EXCLUDE_FILE (*crtend.o) .ctors) EXCLUDE_FILE(文件列表)表示剔除指定的输入文件,即不包含这些文件的指定段。
*(.text .rdata) 这种方法两个段顺序是不定的。
*(.text) *(.rdata) 这种方法两个段顺序是固定的。
data.o(.data)指定某个文件的某个段。

注: " * "符号在前一般是取址符,在后则是指通配符。

2.1.5 其他常用命令

强制对齐 ALIGN
强制输出对齐ALIGN(n),n一般是2的幂次方;
强制输入对齐SUBALIGN, 指定的值会覆盖输入段给出的任何对齐方式。

PROVIDE
PROVIDE关键字可以被用来定义一个符号(相当于定义了一个全局变量),比如’etext’, 这个定义只在它被引用到的时候有效,而在它被定义的时候无效。PROVIDE定义的符号,允许C语言中重定义(但是不能带前导下划线),重定义后优先使用C中的定义。

SECTIONS
{.text :{*(.text)PROVIDE(etext = .);}
}

KEEP
KEEP(*(.Vectors))
作用是防止垃圾收集机制把重要的节排除在外(防止被优化),也保证了KEEP对象在段中的位置处于最顶端。
ABSOLUTE
PROVIDE(_bss_over = ABSOLUTE(.));
将当前地址的绝对值赋值给_bss_over

2.1.6 链接脚本完整示例

ENTRY(Reset_Handler)_Min_Heap_Size = 0x200;      /* required amount of heap  */
_Min_Stack_Size = 0x1500; /* required amount of stack */MEMORY
{FLASH (rx)      : ORIGIN = 0x8000000, LENGTH = 128KRAM (xrw)      : ORIGIN = 0x20000000, LENGTH = 32K
}SECTIONS
{.isr_vector :{. = ALIGN(4);KEEP(*(.isr_vector)) /* Startup code */. = ALIGN(4);} >FLASH.text :{. = ALIGN(4);*(.text)           /* .text sections (code) */*(.text*)          /* .text* sections (code) */*(.glue_7)         /* glue arm to thumb code */*(.glue_7t)        /* glue thumb to arm code */*(.eh_frame)KEEP (*(.init))KEEP (*(.fini)). = ALIGN(4);__fsymtab_start = .;KEEP(*(FSymTab))__fsymtab_end = .;. = ALIGN(4);__vsymtab_start = .;KEEP(*(VSymTab))__vsymtab_end = .;. = ALIGN(4);__rt_init_start = .;KEEP(*(SORT(.rti_fn*)))__rt_init_end = .;. = ALIGN(4);_etext = .;        /* define a global symbols at end of code */_exit = .;} >FLASH.rodata :{. = ALIGN(4);*(.rodata)         /* .rodata sections (constants, strings, etc.) */*(.rodata*)        /* .rodata* sections (constants, strings, etc.) */. = ALIGN(4);_shell_command_start = .;KEEP (*(shellCommand))_shell_command_end = .;} >FLASH.ARM.extab   : { *(.ARM.extab* .gnu.linkonce.armextab.*) } >FLASH.ARM : {__exidx_start = .;*(.ARM.exidx*)__exidx_end = .;} >FLASH.preinit_array     :{PROVIDE_HIDDEN (__preinit_array_start = .);KEEP (*(.preinit_array*))PROVIDE_HIDDEN (__preinit_array_end = .);} >FLASH.init_array :{PROVIDE_HIDDEN (__init_array_start = .);KEEP (*(SORT(.init_array.*)))KEEP (*(.init_array*))PROVIDE_HIDDEN (__init_array_end = .);} >FLASH.fini_array :{PROVIDE_HIDDEN (__fini_array_start = .);KEEP (*(SORT(.fini_array.*)))KEEP (*(.fini_array*))PROVIDE_HIDDEN (__fini_array_end = .);} >FLASH_sidata = LOADADDR(.data);.data : {. = ALIGN(4);_sdata = .;        /* create a global symbol at data start */*(.data)           /* .data sections */*(.data*)          /* .data* sections */. = ALIGN(4);_edata = .;        /* define a global symbol at data end */} >RAM AT> FLASH. = ALIGN(4);.bss :{/* This is used by the startup in order to initialize the .bss secion */_sbss = .;         /* define a global symbol at bss start */__bss_start__ = _sbss;*(.bss)*(.bss*)*(COMMON). = ALIGN(4);_ebss = .;         /* define a global symbol at bss end */__bss_end__ = _ebss;} >RAM/* User_heap_stack section, used to check that there is enough RAM left */._user_heap_stack :{. = ALIGN(4);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;_estack = .;. = ALIGN(4);} >RAM/* Remove information from the standard libraries *//DISCARD/ :{libc.a ( * )libm.a ( * )libgcc.a ( * )}.ARM.attributes 0 : { *(.ARM.attributes) }
}

2.2 ARM核内如何把指定函数编译到固定内存(使用ld链接脚本)

问: 基于ARM核 ,把指定函数 MyFunction 编译到固定的内存区域中(起始地址为0x7D000000,大小为0x2000)?
我的答案如下:

/* ld file start */ 
MEMORY 
{EXT_RAM   : ORIGIN = 0xa0000000,       LENGTH = 0x8000EXT_ROM   : ORIGIN = 0x7E680000,       LENGTH = 0x16180EXT_BSS   : ORIGIN = 0x7FC98000,       LENGTH = 0x3C00MY_SECTION : ORIGIN = 0x7D000000,      LENGTH = 0x2000;
}SECTIONS 
{. = 0x7D000000
.text : 
{*(.text.*)} > MY_SECTION
}/* ld file end */ /* C file start */ 
Void MyFunction(void) __attribute__((section(".text.myfunci")));
Void MyFunction(void){//implement
};
/* C file end */ 

2.3 lsl链接脚本

参见TC3xx完整工程搭建之链接文件还有基于Tricore的Tasking链接文件解读, 和上述的ld链接脚本有异曲同工之处,但有的地方也更加复杂一点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/44615.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为模拟器ensp中USG6000V防火墙web界面使用

防火墙需要配置 新建拓扑选择USG6000V型号 在防火墙中导包 忘记截图了 启动设备 输入用户名密码 默认用户名:admin 默认密码:Admin123 修改密码 然后他会提示你是否要修改密码,想改就改不想改就不改 进入命令行界面 进入系统视图开启web…

7个外贸网站模板

Nebula独立站wordpress主题 Nebula奈卜尤拉wordpress主题模板,适合搭建外贸独立站使用的wordpress主题。 https://www.jianzhanpress.com/?p7084 Starling师大林WordPress独立站模板 蓝色橙色风格的WordPress独立站模板,适合做对外贸易的外贸公司搭建…

【C++】类和对象--类,实例化,this指针

文章目录 前言一、类1.1 类的定义1.2 类的书写和使用1.3 访问限定符1.4 类域 二、实例化2.1 实例化概念2.2 对象大小 三.this指针总结 前言 前面的几篇文章我们介绍了命名空间,inline,nullptr等C 中常见的的基础概念。今天的文章我们来介绍一些C中类与对…

数据结构 —— BellmanFord算法

数据结构 —— BellmanFord算法 BellmanFord算法检测负权值环BellmanFord和Dijkstra思想上的区别Dijkstra算法的思想Bellman-Ford算法的思想思想上的对比 我们今天来看一个算法BellmanFord算法,我们之前的Dijkstra算法只能用来解决正权图的单源最短路径问题。 Bell…

C语言入门基础题:奇偶 ASCII 值判断(C语言版)和ASCII码表,什么是ASCII码,它的特点和应用?

1.题目描述: 任意输入一个字符,判断其 ASCII 是否是奇数,若是,输出 YES ,否则,输出 NO例如,字符 A 的 ASCI 值是 65 ,则输出 YES ,若输入字符 B(ASCII 值是 66)&#xff…

数据库的学习(5)

题目: 1、新增员工表emp和部门表dept create table dept (deptl int,dept name varchar(11)) charsetutf8; create table emp (sid int,name varchar(11),age int,worktime start date,incoming int,dept2 int) charsetutf8; insert into dept values (101,财务), (…

Matlab中如何添加OptiluX?

1、打开Matlab,依次点击“新建”,“工程”,“从SVN”。 2、存储库路径输入: p/optilux/code - Revision 80: /trunk 同时在“源代码控制集成”菜单中选择“SVN (1.9)” 3、沙盒选择一个自己建的文件夹即可。 来源:Opt…

特征值究竟体现了矩阵的什么特征?

特征值究竟体现了矩阵的什么特征? 简单来说就是x经过矩阵A映射后和自己平行 希尔伯特第一次提出eigenvalue,这里的eigen就是自己的。所以eigenvalue也称作本征值 特征值和特征向量刻画了矩阵变换空间的特征 对平面上的任意向量可以如法炮制,把他在特征…

集创北方ICN6202 低功耗MIPIDSI转2 PORT LVDS 支持1080P分辨率,成熟批量产品

ICN6202描述: ICN6202是一个接收MIPIDSI输入和发送LVDS输出的桥接芯片。MIPIDSI最多支持4个车道,每个车道的最大运行频率为1Gbps;总最大输入带宽为4Gbps;并且还支持MIPI定义的ULPS(超低功耗状态)。ICN6202…

Elasticsearch集群搭建

集群概念 在单台 ES 服务器上,随着一个索引内数据的增多,会产生存储、效 率、安全等问题。 因此引入集群 我们需要将索引拆分成多份,分别放入不同的服务器中,此时这几台服务器维护了同一个索引,我们称这几台服务器为一…

计算机毕业设计Python深度学习游戏推荐系统 Django PySpark游戏可视化 游戏数据分析 游戏爬虫 Scrapy 机器学习 人工智能 大数据毕设

本论文的主要研究内容如下: 了解基于Spark的TapTap游戏数据分析系统的基本架构,掌握系统的开发方法,包括系统开发基本流程、开发环境的搭建、测试与运行等。 主要功能如下: (1)用户管理模块&#xff1a…

初阶数据结构—排序

第一章:排序的概念及其运用 1.1 排序的概念 排序:所谓排序,就是使一串记录,按照其中的某个或某些关键字的大小,递增或递减的排列起来的操作。 稳定性:假定在待排序的记录序列中,存在多个具有…

LIO-SAM编译ubuntu20.04 Noetic

一、下载 mkdir -p ~/lio_sam_ws/src cd ~/lio_sam_ws/src git clone https://github.com/TixiaoShan/LIO-SAM.git cd ..二、编译&&解决报错 catkin_make报错如下 解决方案: 第一步: sudo add-apt-repository ppa:borglab/gtsam-release-4…

数学建模美赛经验小结

图片资料来自网络所听讲座,感谢分享!

网络编程的学习之udp

Udp编程过程 Sento不会阻塞 实现聊天室效果 上线 聊天 下线 服务端需要一个地址,去保留名字和ip地址 交互的时候发结构体 下面这个宏只能在c语言里使用 ser.sin_port htons(50000); 上面是端口号50000以上,两边要一样 这里是不要让udp发的太快&am…

Unity Shader学习笔记

Shader类型 类型详情Standard Surface Shader标准表面着色器,基于物理的着色系统,用于模拟各种材质效果,如石头、木材、玻璃、塑料和金属等。Unlit Shader最简单的着色器,不包含光照但包含雾效,只由最基础的Vertex Sh…

30. 梯度下降法及其应用

1. 引言 在深度学习中,损失函数的求解是一个关键步骤。损失函数通常没有解析解,因此需要通过最优化算法来逼近求解。其中,梯度下降法是最常用的优化算法之一。本文将详细介绍梯度下降法的基本概念、理论基础、及其在深度学习中的应用。 2. …

甄选范文“论基于构件的软件开发方法及其应用”,软考高级论文,系统架构设计师论文

论文真题 基于构作的软件开发 (Component-Based Software Development,CBSD) 是一种基于分布对象技术、强调通过可复用构件设计与构造软件系统的软件复用途径。基于构件的软件系统中的构件可以是COTS (Commercial-Off-the-Shelf)构件,也可以是通过其它途径获得的构件(如自…

2970.力扣每日一题7/10 Java(暴力枚举)

博客主页:音符犹如代码系列专栏:算法练习关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ 目录 解题思路 解题方法 时间复杂度 空间复杂度 Code 解题思路 incre…

图论---无向图中国邮路的实现

开始编程前分析设计思路和程序的整体的框架,以及作为数学问题的性质: 程序流程图: 数学原理: 本质上是找到一条欧拉回路,考虑图中的边权重、顶点的度数以及如何通过添加最少的额外边来构造欧拉回路,涉及到欧…