当前位置:网站首页>Rt-thread [三] link.lds链接脚本详解
Rt-thread [三] link.lds链接脚本详解
2022-08-04 21:55:00 【民谣唱给爱人听丶】
前言
开一个专题,记录自己学习Rt-thread的过程。许多资料都是来源于之前的笔记和摘抄,有些文档和知识点具体的出处不记得了。如果有您的心血并未写明出处,请联系我。
邮箱:[email protected]
微信:18852982072
以我自己学习Rt-thread的经历来说,这并不是一个很难的IOT-OS。只要有足够的耐心,是很容易入门的。而且对于一款国产RTOS而言。找资料是相对来说比较容易的。
程序编译流程
程序的编译流程如上图所示,因为使用IDE时 大部分的编译工作都被IDE 给屏蔽了。我们最终只能看到一个rtthread.bin的可执行二进制文件。 今天我们来说下这个链接部分的工作。
什么是链接
由汇编程序生成的目标文件(*.s)并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:
静态链接
在这种链接方式下,函数的代码将从其所在的静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。
对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
基本概念
来自官网的内存基础介绍:
程序运行之前,需要有文件实体被烧录到 STM32 的 Flash 中,一般是 bin 或者 hex 文件,该被烧录文件称为可执行映像文件。如下图左边部分所示,是可执行映像文件烧录到 STM32 后的内存分布,它包含 RO 段和 RW 段两个部分:其中 RO 段中保存了 Code、RO-data 的数据,RW 段保存了 RW-data 的数据,由于 ZI-data 都是 0,所以未包含在映像文件中。
STM32 在上电启动之后默认从 Flash 启动,启动之后会将 RW 段中的 RW-data(初始化的全局变量)搬运到 RAM 中,但不会搬运 RO 段,即 CPU 的执行代码从 Flash 中读取,另外根据编译器给出的 ZI 地址和大小分配出 ZI 段,并将这块 RAM 区域清零。
还记得我们上一章说过的启动流程中的程序的data段和bss段相关操作么。
RO Size 包含了 Code 及 RO-data,表示程序占用 Flash 空间的大小;
RW Size 包含了 RW-data 及 ZI-data,表示运行时占用的 RAM 的大小;
ROM Size 包含了 Code、RO-data 以及 RW-data,表示烧写程序所占用的 Flash 空间的大小;
程序内存布局:
栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。
其操作类似于数据结构中的栈。
堆区(heap):一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
全局区(静态区static):存放全局变量、静态数据、常量。程序结束后由系统释放。
全局区分为已初始化全局区(data)和未初始化全局区(bss)。
常量区(文字常量区):存放常量字符串,程序结束后有系统释放。
代码区:存放函数体(类成员函数和全局区)的二进制代码。
程序的内存分段如上图所示: 那么在系统一启动之后 就会把ROM中的RW RO分段的加载到内存中来。也就是我们在上一章看到的 startup_stm32f407xx.S 在entry函数之前所做的那一部分操作。
那么程序是如何分段加载这些文件到内存的各个区域的呢?
这时候我们就要看链接脚本了。
链接脚本的对象是.o 文件。但是其实文件分段的操作在汇编时由一个个.s中就已经做好了。也就是说链接脚本的作用只不过是把已经分好段的程序 打包排序起来放好。
RT-thread stm32f407 link.lds链接脚本详解
/* * linker script for STM32F407VG with GNU ld */
/* Program Entry, set to mark it as "used" and avoid gc */
//用MEMORY命令让在SECTIONS命令内*未*引用的selection分配在程序地址空间内的某个存储区域内。
MEMORY //内存
{
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 1024k /* 1024K flash */
RAM (rw) : ORIGIN = 0x20000000, LENGTH = 128k /* 128K sram */
}
//程序的入口地址
ENTRY(Reset_Handler)
//栈地址
_system_stack_size = 0x400;
//SECTIONS命令告诉ld如何把输入文件的sections映射到输出文件的各个section:
//如何将输入section合为输出section;
//如何把输出section放入程序地址空间(VMA)和进程地址空间(LMA).
SECTIONS
{
.text : //程序代码段
{
. = ALIGN(4); //. 是代表定位器 ALIGN(4) 4字节对齐方式
_stext = .; //_stext 代码段起始地址 这个在.s 文件中有定义
KEEP(*(.isr_vector)) /* Startup code */
. = ALIGN(4);
*(.text) /* remaining code */
*(.text.*) /* remaining code */
*(.rodata) /* read-only data (constants) */
*(.rodata*)
*(.glue_7)
*(.glue_7t)
*(.gnu.linkonce.t*)
/* section information for finsh shell */
. = ALIGN(4);
__fsymtab_start = .;
// 在连接命令行内使用了选项–gc-sections后,连接器可能将某些它认为没用的section过滤掉,
// 此时就有必要强制连接器保留一些特定的 section,可用KEEP()关键字达此目的。
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
/* section information for utest */
. = ALIGN(4);
__rt_utest_tc_tab_start = .;
KEEP(*(UtestTcTab))
__rt_utest_tc_tab_end = .;
/* section information for at server */
. = ALIGN(4);
__rtatcmdtab_start = .;
KEEP(*(RtAtCmdTab))
__rtatcmdtab_end = .;
. = ALIGN(4);
/* section information for initial. */
. = ALIGN(4);
__rt_init_start = .;
KEEP(*(SORT(.rti_fn*)))
__rt_init_end = .;
. = ALIGN(4);
PROVIDE(__ctors_start__ = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE(__ctors_end__ = .);
. = ALIGN(4);
_etext = .;
} > ROM = 0 //将这个段放入ROM 中
/* .ARM.exidx is sorted, so has to go in its own output section. */
__exidx_start = .;
.ARM.exidx :
{
*(.ARM.exidx* .gnu.linkonce.armexidx.*)
/* This is used by the startup in order to initialize the .data secion */
_sidata = .;
} > ROM //将这个段放入ROM 中
__exidx_end = .;
/* .data section which is used for initialized data */
.data : AT (_sidata) //数据段
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_sdata = . ;
*(.data)
*(.data.*)
*(.gnu.linkonce.d*)
PROVIDE(__dtors_start__ = .);
KEEP(*(SORT(.dtors.*)))
KEEP(*(.dtors))
PROVIDE(__dtors_end__ = .);
. = ALIGN(4);
/* This is used by the startup in order to initialize the .data secion */
_edata = . ;
} >RAM //将这个段放入RAM memroy中
.stack :
{
. = ALIGN(4);
_sstack = .; //栈的起始地址
. = . + _system_stack_size; //栈大小
. = ALIGN(4); //4字节对齐
_estack = .; //栈结束地址
} >RAM //将这个段放入RAM memroy中
__bss_start = .; //bss 起始起始
.bss :
{
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
/* This is used by the startup in order to initialize the .bss secion */
_ebss = . ;
*(.bss.init)
} > RAM //将这个段放入RAM memroy中
__bss_end = .;
_end = .; //结束地址
/* Stabs debugging sections. */
.stab 0 : {
*(.stab) }
.stabstr 0 : {
*(.stabstr) }
.stab.excl 0 : {
*(.stab.excl) }
.stab.exclstr 0 : {
*(.stab.exclstr) }
.stab.index 0 : {
*(.stab.index) }
.stab.indexstr 0 : {
*(.stab.indexstr) }
.comment 0 : {
*(.comment) }
/* DWARF debug sections. * Symbols in the DWARF debugging sections are relative to the beginning * of the section so we begin them at 0. */
/* DWARF 1 */
.debug 0 : {
*(.debug) }
.line 0 : {
*(.line) }
/* GNU DWARF 1 extensions */
.debug_srcinfo 0 : {
*(.debug_srcinfo) }
.debug_sfnames 0 : {
*(.debug_sfnames) }
/* DWARF 1.1 and DWARF 2 */
.debug_aranges 0 : {
*(.debug_aranges) }
.debug_pubnames 0 : {
*(.debug_pubnames) }
/* DWARF 2 */
.debug_info 0 : {
*(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev 0 : {
*(.debug_abbrev) }
.debug_line 0 : {
*(.debug_line) }
.debug_frame 0 : {
*(.debug_frame) }
.debug_str 0 : {
*(.debug_str) }
.debug_loc 0 : {
*(.debug_loc) }
.debug_macinfo 0 : {
*(.debug_macinfo) }
/* SGI/MIPS DWARF 2 extensions */
.debug_weaknames 0 : {
*(.debug_weaknames) }
.debug_funcnames 0 : {
*(.debug_funcnames) }
.debug_typenames 0 : {
*(.debug_typenames) }
.debug_varnames 0 : {
*(.debug_varnames) }
}
资料
边栏推荐
- 【线性代数02】AX=b的2种解释和矩阵乘法的5种视角
- Altium Designer 19.1.18 - 保护锁定的对象
- PowerBI真经连续剧
- LeetCode143:重排链表
- 数字重塑客观世界,全空间GIS发展正当其时
- # #ifndef/#define/#endif使用详解
- docker 搭建mysql 主从复制
- Re24:读论文 IOT-Match Explainable Legal Case Matching via Inverse Optimal Transport-based Rationale Ext
- The upgrade and transformation plan of the fortress machine for medium and large commercial banks!Must see!
- 智能盘点钢筋数量AI识别
猜你喜欢
SPSS-unary regression practice
ue unreal 虚幻 高分辨率无缩放 编辑器字太小 调整编辑器整体缩放
零基础都能拿捏的七夕浪漫代码,快去表白或去制造惊喜吧
Win11如何开启Telnet客户端?
数电快速入门(四)(组合逻辑电路的分析以及设计的介绍)
如何根据“前序遍历,中序遍历”,“中序遍历,后序遍历”构建按二叉树
ctfshow终极考核web654
论文解读(PPNP)《Predict then Propagate: Graph Neural Networks meet Personalized PageRank》
Open source summer | Cloud server ECS installs Mysql, JDK, RocketMQ
Redis中的LRU算法
随机推荐
# #ifndef/#define/#endif使用详解
看看XDOC如何做Word文档预览
立即升级!WPS Office 出现 0day 高危安全漏洞:可完全接管系统,官方推出紧急更新
智能盘点钢筋数量AI识别
EasyGBS接入最新版海康摄像头后无法传递告警信息该如何解决?
炽热如初 向新而生|ISC2022 HackingClub白帽峰会圆满举办!
unity2D横版游戏教程9-对话框dialog
硬件开发定制全流程解析
OC-协议
信创是什么意思?涉及哪些行业?为什么要发展信创?
The use and principle of CountDownLatch
七夕特制:《牛郎会织女》
可视化工作流引擎开发OA系统,让企业少花冤枉钱
Yolov7:Trainable bag-of-freebies sets new state-of-the-art for real-time objectdetectors
unity2D横版游戏教程8-音效
基于声卡实现的音频存储示波器,可作为电磁学实验的测量仪表
In which industries is the PMP certificate useful?
VSCode - common shortcut keys (continuous recording
puzzle(022.1)黑白迭代
LeetCode: 406. 根据身高重建队列