当前位置:网站首页>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) }
}
资料
边栏推荐
- boostrap多选PID查找端口 window
- 基于声卡实现的音频存储示波器,可作为电磁学实验的测量仪表
- QT 子窗口—>主窗口 信号和槽的交互
- LocalDateTime的详细使用方法
- UnicodeDecodeError: ‘utf-8‘ codec can‘t decode byte 0xd6 in position 120: invalid continuation byte
- CountDownLatch使用及原理
- 关于std::vector<std::string>的操作
- Re24:读论文 IOT-Match Explainable Legal Case Matching via Inverse Optimal Transport-based Rationale Ext
- ROS播包可视化
- Altium Designer 19.1.18 - 画多边形铜皮挖空时,针对光标胡乱捕获的解决方法
猜你喜欢

论文解读(PPNP)《Predict then Propagate: Graph Neural Networks meet Personalized PageRank》

LeetCode 199: 二叉树的右视图

Chapter7 : Network-Driven Drug Discovery

如何为Web3.0世界启动完美的DAO

Redis中的LRU算法

数电快速入门(一)(BCD码和三种基本逻辑运算的介绍)

How to solve the problem that the alarm information cannot be transmitted after EasyGBS is connected to the latest version of Hikvision camera?

Codeforces Round #811 (Div. 3)

Flutter 实现背景图片毛玻璃效果

ROS packages visualization
随机推荐
Milvus configuration related
七夕特制:《牛郎会织女》
如何在项目中正确使用WebSocket
y87.第五章 分布式链路追踪系统 -- 分布式链路追踪系统起源(一)
mysql基础
VSCode—常用快捷键(持续记录
LeetCode 199: 二叉树的右视图
软件测试外包公司怎么样?有什么好处和坏处?为什么没人去?
【SQL之降龙十八掌】01——亢龙有悔:入门10题
numpy关于两个array叠加操作
ue unreal 虚幻 高分辨率无缩放 编辑器字太小 调整编辑器整体缩放
快速web开发框架——learun framework
热力学相关的两个定律
MySQL查询为啥慢了?
AtCoder Beginner Contest 262 D - I Hate Non-integer Number
com.jacob.com.ComFailException: Invoke of: ActiveDocument
Altium Designer 19.1.18 - 保护锁定的对象
docker 部署redis集群
Moke, dynamic image resource package display
【uiautomation】微信好友列表获取(存储到txt中)