0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

从文件角度,了解Cortex-M开发(二)

454398 来源:alpha007 作者:alpha007 2022-11-25 17:59 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

在前一节课《源文件(.c/.h/.s)》里,痞子衡给大家系统地介绍了 source 文件,source 文件是嵌入式工程里典型的 input 文件,那么还有没有其他类型的 input 文件?既然痞子衡这么提问了,那答案肯定是有啦。今天痞子衡要讲的 linker 文件就属于另一种 input 文件。


linker 文件顾名思义就是嵌入式工程在链接阶段所要用到的文件,source 文件在编译过程完成之后(此时已经是机器可识别的二进制机器码数据),需要再经过链接器从而将二进制数据有序组织起来形成最终的二进制可执行文件,该二进制文件最终会被下载进芯片内部非易失性存储器里。linker 文件就是用来指示链接器如何组织编译生成的二进制数据。


linker 文件是跟 IDE 息息相关的,本文以 IAR EWARM 为例介绍 linker 文件,其他 IDE 下的 linker 文件可触类旁通。


一、 嵌入式系统中的 section

在讲 linker 文件之前,痞子衡必须先跟大家理清一个嵌入式系统中很重要的概念 -section。那么什么是 section?我们写的 C 或者汇编 source 文件里都是各种应用代码,这些代码按功能可以分为很多种类,比如常量、变量、函数、堆栈等,而相同类型的代码的集合便是一个 section,链接器在链接时组织数据的基本单元便是 section。那么一个典型的嵌入式系统中到底有多少种 section 呢?下面列出了 IAR 里默认的所有 section,那些常见 section 在后续介绍 linker 文件里会被提到。


// 常见 Section


.bss // Holds zero-initialized static and global variables.


CSTACK // Holds the stack used by C or C++ programs.


.data // Holds static and global initialized variables.


.data_init // Holds initial values for .data sections when the linker directive initialize is used.


HEAP // Holds the heap used for dynamically allocated data.


.intvec // Holds the reset vector table


.noinit // Holds __no_init static and global variables.


.rodata // Holds constant data.


.text // Holds the program code.


.textrw // Holds __ramfunc declared program code.


.textrw_init // Holds initializers for the .textrw declared section.

// 较冷僻 Section


.exc.text // Holds exception-related code.


__iar_tls.$$DATA // Holds initial values for TLS variables.


.iar.dynexit // Holds the atexit table.


.init_array // Holds a table of dynamic initialization functions.


IRQ_STACK // Holds the stack for interrupt requests, IRQ, and exceptions.


.preinit_array // Holds a table of dynamic initialization functions.


.prepreinit_array // Holds a table of dynamic initialization functions.


Veneer$$CMSE // Holds secure gateway veneers.

// 更冷僻 Section


.debug // Contains debug information in the DWARF format


.iar.debug // Contains supplemental debug information in an IAR format


.comment // Contains the tools and command lines used for building the file


.rel or .rela // Contains ELF relocation information


.symtab // Contains the symbol table for a file


.strtab // Contains the names of the symbol in the symbol table


.shstrtab // Contains the names of the sections.


Note:上述 section 的详细解释请查阅 IAR 软件安装目录下 /IAR Systems/Embedded Workbench

xxx/arm/doc/EWARM_DevelopmentGuide.ENU.pdf 文档里的 Section reference 一节。


二、解析 linker 文件

知道了 section 概念,那便可开始深入了解 linker 文件,什么是 linker 文件?linker 文件是按 IDE 规定的语法写成的用于指示链接器分配各 section 在嵌入式系统存储器中存放位置的文件。大家都知道嵌入式系统存储器主要分为两类:ROM(非易失性),RAM(易失性),所以相应的这些 section 根据存放的存储器位置不同也分为两类属性:readonly, readwrite。实际上 linker 文件的工作就是将 readonly section 放进 ROM,readwrite section 放进 RAM。


那么到底该如何编写工程的 linker 文件呢?正如前面所言,linker 文件也是有语法的,而且这语法是由 IDE 指定的,所以必须要先掌握 IDE 制定的语法规则,linker 文件语法规则相对简单,最常用的关键字就是如下 8 个:


// 动词类关键字


define // 定义各种空间范围、长度


initialize // 设置 section 初始化方法


place in // 放置 section 于某 region 中(具体地址由链接器分配)


place at // 放置 section 于某绝对地址处

// 名词类关键字


symbol // 各种空间范围、长度的标识


memory // 整个 ARM 内存空间的标识


region // 在整个 ARM 内存空间中划分某 region 空间的标识


block // 多个 section 的集合块的标识


Note:上述 linker 语法的详细解释请查阅 IAR 软件安装目录下 /IAR Systems/Embedded Workbench

xxx/arm/doc/EWARM_DevelopmentGuide.ENU.pdf 文档里的 The linker configuration file 一节。


到这里我们已经可以开始愉快地写 linker 文件了,是不是有点按捺不住了?来吧,只需要三步走,Let's do it。


此处假设 MCU 物理空间为:ROM(0x0 - 0x1ffff)、RAM(0x10000000 - 0x1000ffff),痞子衡要写的 linker 要求如下:


中断向量表必须放置于 ROM 起始地址 0x0,且必须 256 字节对齐


STACK 大小为 8KB,HEAP 大小为 1KB,且必须 8 字节对齐


SATCK 必须放置在 RAM 起始地址 0x10000000


其余 section 放置在正确的 region 里,具体空间由链接器自动分配


2.1 定义物理空间

第一步我们先定义 3 块互不重叠的空间 ROM_region、RAM_region、STACK_region,其中 ROM_region 对应的是真实的 ROM 空间,RAM_region 和 STACK_region 组合成真实的 RAM 空间。


// 定义物理空间边界


define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;


define symbol __ICFEDIT_region_ROM_end__ = __ICFEDIT_region_ROM_start__ + (128*1024 - 1);


define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;


define symbol __ICFEDIT_region_RAM_end__ = __ICFEDIT_region_RAM_start__ + (64*1024 - 1);


define symbol __ICFEDIT_intvec_start__ = __ICFEDIT_region_ROM_start__;

// 定义堆栈长度


define symbol __ICFEDIT_size_cstack__ = (8*1024);


define symbol __ICFEDIT_size_heap__ = (1*1024);

// 定义各 region 具体空间范围


define memory mem with size = 4G;


define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__];


define region STACK_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_start__ +

__ICFEDIT_size_cstack__ - 1];


define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ + __ICFEDIT_size_cstack__ to

__ICFEDIT_region_RAM_end__];


2.2 定义 section 集合

第二步是自定义 section 集合块,细心的朋友可以看到右边花括号里包含的都是上一节介绍的系统默认 section,我们会把具有相同属性的 section 集合成到一个 block 里,方便下一步的放置工作。


// 定义堆栈块及其属性


define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };


define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };

// 定义 section 集合块


define block Vectors with alignment=256 { readonly section .intvec };


define block CodeRelocate { section .textrw_init };


define block CodeRelocateRam { section .textrw };


define block ApplicationFlash { readonly, block CodeRelocate };


define block ApplicationRam { readwrite, block CodeRelocateRam, block HEAP };


有朋友可能会疑问,为何要定义 CodeRelocate、CodeRelocateRam 这两个 block?按道理说这两个 block 对应的 section 可以分别放进 ApplicationFlash 和 ApplicationRam,那为何多此一举?仔细上过痞子衡前一节课 source 文件的朋友肯定就知道答案了,在那节课里介绍的 startup.c 文件里有一个叫 init_data_bss()的函数,这个函数会完成初始化 CodeRelocateRam 块的功能,它找寻的就是 CodeRelocate 段名字,这个名字比系统默认的 textrw 名字看起来更清晰易懂。


2.3 安置 section 集合

第三步便是处理放置那些 section 集合块了,在放置集合块之前还有 initialize manually 语句,为什么会有这些语句?还是得结合前面提及的 startup.c 文件里的 init_data_bss()函数来说,这个函数是开发者自己实现的 data,bss 段的初始化,所以此处需要通知 IDE,你不需要再帮我做初始化工作了。


// 设置初始化方法


initialize manually { readwrite };


initialize manually { section .data};


initialize manually { section .textrw };


do not initialize { section .noinit };

// 放置 section 集合块


place at start of ROM_region { block Vectors };


//place at address mem:__ICFEDIT_intvec_start__ { block Vectors };


place in ROM_region { block ApplicationFlash };


place in RAM_region { block ApplicationRam };


place in STACK_region { block CSTACK };


当然如果你希望 IDE 帮你自动初始化 data,bss,textrw 段,那么可以用下面语句替换 initialize manually 语句。


initialize by copy { readwrite, section .textrw };


设置好初始化方法后,便是放置 section 集合块了,放置方法主要有两种,place in 和 place at,前者用于指定空间块放置(不指定具体地址),后者是指定具体地址放置。


至此一个基本的 linker 文件便大功告成了,是不是 so easy?


番外一、自定义 section

有耐心看到这里的朋友,痞子衡必须得放个大招奖励一下,前面讲的都是怎么处理系统默认段,那么有没有可能在代码里自定义段呢?想象一下你有这样的需求,你需要在你的应用里开辟一块 1KB 的可更新的数据区,你想把这个数据区指定到地址 0x18000 - 0x183ff 的范围内,你需要在应用里定义 4 Byte 的只读 config block 常量指向这个可更新数据区首地址(这段 config block 只会被外部 debugger 或者 bootloader 更新),如何做到?


// C 文件中


/////////////////////////////////////////////////////


// 用@操作符指定变量 myConfigBlock[4]放进自定义 .myBuffer section


const uint8_t myConfigBlock[4] @ ".myBuffer" = {0x00, 0x01, 0x02, 0x03};

// Linker 文件中


/////////////////////////////////////////////////////


// 自定义指定的 mySection_region,并把 .myBuffer 放到这个 region


define region mySection_region = mem:[from 0x0x18000 to 0x183ff];


place at start of mySection_region { readonly section .myBuffer };


上面做到了将代码中的常量放入自定义段?,那么怎么将代码中的函数也放进自定义段呢?继续看下去


// C 文件中


/////////////////////////////////////////////////////


// 用#pragma location 指定函数 myFunction()放进自定义 .myTask section


#pragma location = ".myTask"


void myFunction(void)


{


__NOP();


}

// Linker 文件中

/////////////////////////////////////////////////////

// 把 .myTask 放到 mySection_region


place in mySection_region { readonly section .myTask };


看起来大功告成了,最后还有一个注意事项,如果 myConfigBlock 在代码中并未被引用,IDE 在链接的时候可能会忽略这个变量(IDE 认为它没用,所以优化了),那么怎么让 IDE 强制链接 myConfigBlock 呢?IAR 留了个后门,在 options->Linker->Input 选项卡中的 Keep symbols 输入框里填入你想强制链接的对象名(注意是代码中的对象名,而非 linker 文件中的自定义段名)即可。


Note:关于番外内容的更多细节请查阅 IAR 软件安装目录下 /IAR Systems/Embedded Workbench

xxx/arm/doc/EWARM_DevelopmentGuide.ENU.pdf 文档里的 Pragma directives 一节。


至此,嵌入式开发里的 linker 文件痞子衡便介绍完毕了,掌声在哪里~~~

审核编辑黄昊宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5186

    文章

    20143

    浏览量

    328669
  • Linker
    +关注

    关注

    0

    文章

    3

    浏览量

    1909
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    Cortex-M产品的特色

    开发支持:设计人员可从ARM生态系统的许多工具中选择,或者使用单一、全面的工具链支持所有Cortex-M器件。 架构扩展:ARM还提供了一系列的架构扩展用于满足下一代处理器的需求,如DSP扩展、SIMD指令、浮点单元和Helium技术,为特定应用场景提供了
    发表于 11-26 07:22

    Cortex-M内核中的精确延时的方法

    使用 CYCCNT寄存器来测量执行某个任务所花的周期数,这也可以用作时间基准相关的目的(操作系统中统计 CPU使用率可以用到它)。” Cortex-M中的DWT它有一个32位的寄存器叫CYCCNT
    发表于 11-21 07:51

    为什么cortex-M0+功耗低?

    1. 流水线层级简化 Cortex-M0:采用 3级流水线(取指、解码、执行)。 更多流水线层级导致每个时钟周期需激活更多硬件单元,动态功耗较高。 流水线冲突(如分支预测错误)需刷新流水线,浪费
    发表于 11-19 08:15

    Cortex-M级别的转换

    一、 简述 Cortex-M 里面有特权级别的概念,不同级别可以设定不同的权限,如何转换特权级别基本是本章的内容。 、操作模式 ARM M 核操作模式有两个: 线程(Thread)模式:在复位时或
    发表于 11-19 07:32

    明晚8点|睿擎文件系统实战:开发到发布全流程解析

    文件操作到镜像发布,一次直播掌握完整开发流程!在嵌入式系统开发中,文件系统是数据存储、配置管理和资源访问的核心基础。然而在实际
    的头像 发表于 11-11 11:53 365次阅读
    明晚8点|睿擎<b class='flag-5'>文件</b>系统实战:<b class='flag-5'>从</b><b class='flag-5'>开发</b>到发布全流程解析

    【直播预告】下周三晚8点|睿擎文件系统实战:开发到发布全流程解析

    文件操作到镜像发布,一次直播掌握完整开发流程!在嵌入式系统开发中,文件系统是数据存储、配置管理和资源访问的核心基础。然而在实际
    的头像 发表于 11-06 18:05 1669次阅读
    【直播预告】下周三晚8点|睿擎<b class='flag-5'>文件</b>系统实战:<b class='flag-5'>从</b><b class='flag-5'>开发</b>到发布全流程解析

    睿擎派文件系统指南:开发到发布全流程实践 | 技术解析

    在嵌入式系统开发中,文件系统扮演着至关重要的角色,它负责数据的持久化存储、配置文件管理和资源访问等核心功能。睿擎平台提供了一套完整的文件系统解决方案,
    的头像 发表于 11-05 18:13 7686次阅读
    睿擎派<b class='flag-5'>文件</b>系统指南:<b class='flag-5'>从</b><b class='flag-5'>开发</b>到发布全流程实践 | 技术解析

    【RA-Eco-RA6M4开发板评测】使用ULINK2开发瑞萨MCU

    · 支持 ARM7、ARM9、Cortex-M、8051 和 C166 设备 · JTAG 速度高达 10MHz · 针对基于 ARM Cortex-M 的设备的串行线调试 (SWD) 支持 · 适用于
    发表于 09-25 23:08

    如何 MCU/MPU 角度保护物联网应用?

    如何 MCU/MPU 角度保护物联网应用?
    发表于 09-08 07:33

    请问NuMicro® Cortex-M® 系列芯片是否支持 I2C 监视器功能?

    NuMicro® Cortex-M® 系列芯片是否支持 I2C 监视器功能?
    发表于 08-21 06:04

    嵌入式开发入门指南:从零开始学习嵌入式

    (设备驱动、内核编译) 4. 推荐的学习资源书籍:《嵌入式系统软件设计基础》《ARM Cortex-M系列嵌入式开发》在线课程:慕课网、B站嵌入式教学视频实践平台:Arduino、STM32开发
    发表于 05-15 09:29

    i.MX8MMini中的Cortex-M4不支持SDIO吗?

    的 FreeRTOS,我们 NXP 论坛获取了信息,并正在努力为 RTOS 构建环境 我们注意到,在构建完成后移植无线驱动程序时。 RTOS 的当前源代码不包括 SDIO 驱动程序。 i.MX8MMini 中的 Cortex-M4 不支持 SDIO 吗? 如果支持,您能
    发表于 04-03 06:45

    瑞萨RA8快速上手指南:Cortex-M85内核瑞萨RA8开发环境搭建 并点亮一个LED

    因为Cortex-M内核,瑞萨RA8系列单片机支持多种市面上常见的开发环境,像Keil MDK、IAR EWARM等,而本文讲述的是瑞萨自家官方的IDE(e2 studio)。
    的头像 发表于 03-17 14:35 1556次阅读
    瑞萨RA8快速上手指南:<b class='flag-5'>Cortex-M</b>85内核瑞萨RA8<b class='flag-5'>开发</b>环境搭建 并点亮一个LED

    Arm Cortex-A320 CPU助力嵌入式设备实现高能效AI计算

    ,要确定适合特定 AI 应用的处理器,系统开发者需要通过比较基于 Arm Cortex-A、Arm Cortex-M 和 Arm Ethos-U NPU 的设备及其可能的搭配进行决策。除了成本的考量,
    的头像 发表于 02-27 17:17 1141次阅读
    Arm <b class='flag-5'>Cortex</b>-A320 CPU助力嵌入式设备实现高能效AI计算

    Cortex-M3/M4F指令集技术用户手册

    电子发烧友网站提供《Cortex-M3/M4F指令集技术用户手册.pdf》资料免费下载
    发表于 12-23 16:31 9次下载
    <b class='flag-5'>Cortex-M</b>3/<b class='flag-5'>M</b>4F指令集技术用户手册