作为嵌入式软件工程师,应该要清楚程序的每一条指令在哪里,什么时候会被加载到内存,什么时候会被执行。链接脚本会明确告诉你程序的代码和数据在内存中的分布。精确控制代码和数据在内存中的分布是高效利用内存资源的前提。自定义链接脚本是资深嵌入式软件工程师的必备技能,更是嵌入式架构师的最基本要求。此外,灵活定制链接脚本在编程方面有更高级的应用。

一、编译链接原理

简单讲述编译链接的基本原理有助于后面内容的理解。

a. 简单点说,一个可执行程序包括文件头、代码段(.text)、数据段(.bss)、符号段等信息,在Linux GCC工具链中,该执行程序文件的格式是elf,而在elf格式中,段称为section。现加上某个程序有file1.c和file2.c两个文件。

b. 一个file1.c文件中有代码(函数),也有数据(全局变量,假设都没有初始化的),因此在编译(arm-linux-gcc)之后就会产生一个对应的file1.o,在该文件中会有产生.text section,其会将file.c中所有的函数代码编译后的指令放到该区域,假设长度是0x100字节;同样会产生.bss section,会将所有的全局变量定义到该区域,假设长度是0x20字节。这时,file1.o是可重定位的文件,即.text段是从地址0开始的,其真正的虚拟运行地址还需要在链接阶段重定位完成。

c. 对于另一个file2.c文件,同样会产生对应的file2.o,而该文件中会有对应的.text section(假设长度是0x200字节)和.bss section(假设长度是0x40字节)。其.text段同样是从地址0开始。

d. 链接的最终结果就是产生唯一的一个可执行文件result,而该文件只有一个.text段和一个.bss段,而且.text段会按照指定的链接地址重定位好,即.text段不再是从0开始。那么从这个结果来看,链接的过程应该包括这两个步骤:

d1. 合并file1.o的.text和file2.o的.text到result的.text,其长度为0x100+0x200=0x300字节;合并file1.o的.bss和file2.o的.bss到result的.bss,长度是0x20+0x40=0x60字节

d2. 根据指定的链接地址重定位result的.text段和.bss段,即.text段从指定的地址开始,各个函数的起始地址也会根据该地址进行重新定位。

二、链接脚本基本语法

这里以最基础的链接脚本语法作为示例,GCC工具链下的链接脚本后缀一般是.lds。

ENTRY(_start)//指定_start为程序的第一条指令

SECTIONS//指定内存分布

{

//顿号是地址标识符,这里指定为0x40000000是虚拟运行地址

. = 0x40000000;

//即.text的地址是从0x40000000开始,.text的内容包括file1.o和file2.o的.text

.text :

{

file1.o (.text)//先合并file1.o的.text

file2.o (.text) //再合并file2.o的.text

}

//此时地址是:0x40000000+0x300=0x40000300

//地址标识符可以进行表达式计算,

//则.bss的起始地址是0x40000300+0x400=0x40000700

. = . + 0x400;

.bss:

{

file1.o(.bss)

file2.o(.bss)

}

}

三、关键的链接命令参数

链接命令程序是:arm-linux-ld,通过在命令行传入-T参数来指定自定义的链接脚本。如–Tfile.lds 代表链接时使用自定义的file.lds脚本,而不是系统默认的链接脚本。

四、可变长数组的实现

介绍完编译链接原理,我们开始讲述链接脚本在编程中的第一个高级运用--可变长数组。可变数组我们一般见于C++或者JAVA等高级语言,其是指在运行过程中动态地改变数组的大小,往往是通过链表队列的方式来实现变长需求。我们这里所阐述的可变长数组严格意义上是指静态可变长数组,或者更严格一点讲,在静态链接时数组大小也已经固定,只是在编程形式上看起来是可变长的。

我们在下面将要介绍的uboot启动引导模块中有一个命令模式,即uboot会响应用户输入的命令以达到修改系统参数、显示板级系统信息、加载指定操作系统等目的。Uboot原则上可以支持任意多的命令,只要编程人员愿意添加。我们很容易想到,uboot是根据用户在命令行中输入的命令字符串在事先设置好的命令字符串数组中匹配目标命令的,也很容易想到这个匹配的过程就是for循环中strcmp的过程。既然是for循环,那肯定有一个大小的问题,就是这个数组的大小到底是多少。我们很有可能这样实现:

Struct cmd_type cmd[]={cmd1,cmd2,cmd3,…};

命令个数=sizeof(cmd)/sizeof(cmd_type)

这样的做法是可行的,但是它不够灵活,至少每次添加一条命令都要到该数组中去签到,如果真支持100个命令,那估计得用一个专门的文件来定义这些命令的初始化了。有没有一种比较优雅的方法去灵活支持命令的扩展呢?软件其实也是一种艺术,我们来看看uboot是怎么做的!

1. uboot命令的格式

-include/command.h

struct cmd_tbl_s {

char *name; //命令名称

int maxargs; //最多有几个参数

int repeatable;//是否支持回车即重复命令

int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);//执行命令

char *usage;//命令使用示例

char *help; //命令使用帮助信息

};

typedef struct cmd_tbl_s cmd_tbl_t;

2. 非常重要的宏定义

-include/command.h

#define Struct_Section __attribute__ ((section (".u_boot_cmd")))

#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \

cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

3. 如何定义一条命令

-Common/command.c

U_BOOT_CMD(

version, 1, 1, do_version,

"version - print monitor version\n",

NULL

);

用户在命令行输入version,那uboot就会调用do_version这个函数来打印uboot的版本。看看它宏展开之后是啥样的:

struct cmd_tbl_s __u_boot_cmd_version __attribute__ ((section(".u_boot_cmd"))) = \

{ version, 1, 1, do_version, "version - print monitor version\n", NULL};

这里的__attribute__ ((section(".u_boot_cmd")))是啥来的,嗯嗯,说了这么多,终于跟链接脚本扯上关系了。__attribute__属性是GCC工具链支持的特性语法,其表示该结构变量在编译后会存放到.u_boot_cmd这个section,这个section是自定义的,而像.text存放代码,.bss存在数据是默认产生的section. 好,咱们的链接脚本该出场了吧。

4. uboot链接脚本

SECTIONS

{…

__u_boot_cmd_start = .;

.u_boot_cmd : { *(.u_boot_cmd) }

__u_boot_cmd_end = .;

…}

-include/command.h中有以下定义:

extern cmd_tbl_t __u_boot_cmd_start;

extern cmd_tbl_t __u_boot_cmd_end;

即__u_boot_cmd_start代表命令数组的开始,而__u_boot_cmd_end代表命令数组的结束。.u_boot_cmd : { *(.u_boot_cmd) }中*代表所有.o文件的.u_boot_cmd内容都合并到可执行文件的.u_boot_cmd中,即所有文件中通过U_BOOT_CMD宏定义的命令都会被合并到该section.

5. 重新看看for循环怎么找到命令的

-common/command.c

就不解释了吧。只说一点,cmdtp是cmd_tbl_t型指针,cmdtp++代表指向下一个cmd_tbl_t结构体,即下一条uboot命令。

六、Linux呢?

有人会问,Linux是否也应用了该特性呢?废话,Linux应该是将C语言的编程技巧运用到登峰造极的地步了,怎么会放过这个武器呢。自己学着总结一下。

还是随便点一下,每个Linux驱动模块的入口是什么啊,记起来不?是

module_init(XXX_init)

看看人家的宏展开,没晕吧~~

既然有链接脚本在编程中的高级运用之一,就会有之二、之三。我们接下来会讲述怎么利用链接脚本来做内存的分时复用的,还会讲讲C++编译器怎么支持类对象的构造和析构函数的。期待吧~~

请关注本人微信公众号:嵌入式企鹅圈
百分百原创,分享嵌入式和Linux相关的经验总结,谢谢!

我们追求:

1.忠于Linux源码,百分百原创。

2.从上电第一行代码、系统第一行代码、模块第一行代码、应用第一行代码,深入讲解嵌入式软件生命周期。
3 深刻理解硬件体系, 聚焦软件层次设计、模块设计和框架设计。

请关注我们,谢谢!

链接脚本在编程中的高级运用之一:可变长数组相关推荐

  1. C++中的可变长数组(vector)

    变长数组vector vector译为向量,一般来说也叫变长数组,也就是长度可以任意变化的数组,有些题目需要开很多数组,往往造成内存超限,使用vector简单方便,还可节省空间. 头文件 vector ...

  2. C++中 二维可变长数组,vector维度的获取

    最近在leetcode练习算法,想用C但是很多简便的功能C都没有, 于是用C++,但是leetcode中,数组给的都是vector,哪怕是二维 就给vector<vector<int> ...

  3. activiti脚本任务_Activiti中的高级脚本:自定义配置注入

    activiti脚本任务 脚本任务可能是Activiti代码库中"最古老的"类之一,但是我认为它仍然未被许多人使用. (感知到的)缺点当然是性能(解释还是编译),并且从IDE角度来 ...

  4. Python中函数的参数传递与可变长参数

    1.Python中也有像C++一样的默认缺省函数 1 def foo(text,num=0): 2 print text,num 3 4 foo("asd") #asd 0 5 f ...

  5. python函数中可变参数的传递方式是_Python中函数的参数传递与可变长参数

    1.Python中也有像C++一样的默认缺省函数 1 def foo(text,num=0):2 printtext,num3 4 foo("asd") #asd 0 5 foo( ...

  6. python中可变长度参数_Python的可变长参数

    一.*args和**kwargs的概念 1.可以接收所有的实参,就是万能参数,也叫可变长参数,比如: *args,**kwargs 2.*的使用定义 函数定义时: *代表聚合 合数调用时: *代表打散 ...

  7. linux c99 可变长数组,C中不支持可变长度数组C99(Variable length arrays C99 not supported in C)...

    C中不支持可变长度数组C99(Variable length arrays C99 not supported in C) 在Visual Studio 2005中,我正在尝试编译.c文件: int ...

  8. 多核编程中的负载平衡难题

    多核编程中的负载平衡难题 作者:周伟明 相关文章链接: 多核编程中的锁竞争难题 多核编程的几个难题及其应对策略(难题一) OpenMP并行程序设计(二) OpenMP并行程序设计(一) 双核CPU上的 ...

  9. RISC-V MCU ld链接脚本说明

    1.什么是ld链接脚本? 通常,程序编译的最后一步就是链接,此过程根据"*.ld"链接文件将多个目标文件(.o)和库文件(.a)输入文件链接成一个可执行输出文件(.elf).涉及到 ...

  10. C/C++编程知识分享:C++ 手把手教你实现可变长的数组

    01 实现自定义的可变长数组类型 假设我们要实现一个会自动扩展的数组,要实现什么函数呢?先从下面的main函数给出的实现,看看有什么函数是需要我们实现的. 输出结果: 要实现的方式,要做哪些事情呢?我 ...

最新文章

  1. NLP通用模型诞生?一个模型搞定十大自然语言常见任务
  2. 安徽一个班37人考进清华北大,老师发来一则短信,家长沉默了
  3. 滴滴魅族手机人脸识别没有反应_手机UI颜值排名榜单,小米MIUI排到第九,魅族没有上榜...
  4. TODO:从数据库中随机抽取一条记录
  5. suse linux 查看内存,Suse linux查询内存大小的指令是什么?
  6. python两列数据生成邻接矩阵_用python实现邻接矩阵转换为邻接表,python语言实现...
  7. erp采购总监个人总结_《用友 ERP 培训教程:财务核算/供应链管理/物料需求计划》ERP概述 : ERP基础知识...
  8. pymysql获取数据
  9. TensorFlow 莫烦python
  10. 如何在文件夹中打开cmd命令窗
  11. 基于jsp的博客系统
  12. 计算机硬件的五大逻辑部分,计算机硬件系统主要由五个部分组成?
  13. 服务器硬盘gpt,硬盘采用GPT分区非常重要
  14. 【地平线旭日X3派试用体验】基于MIPI Camera的目标检测 web 端展示,全流程(第三节)
  15. 多目标优化算法之SPEA2
  16. xml与json格式互转
  17. iOS 屏幕适配浅谈
  18. 谷歌浏览器的本地收藏夹在什么位置?
  19. GPU Memory Problems in PyTorch(显卡爆炸与利用率不足)
  20. Java开发人员必须掌握的Linux命令(二)

热门文章

  1. iis部署网站 html文件路径,iis发布网页
  2. oracle 中的.dbf,郑怡:请问各位大师:oracle中dbf文件是什么文件?
  3. GPRS DTU是什么?其工作原理是什么? (转自aerkate)
  4. python中oserror_[python] 解决OSError: Address already in use
  5. Windows 10注册表损坏该如何修复?
  6. 五年程序员一般多少工资?网友:能活下来我都觉得是庆幸的!
  7. 电脑常用快捷键大全(收藏)
  8. 谷歌语法和FOFA常用语法总结
  9. 25匹赛马,没有秒表,五条跑道。用最少的比赛场次找出三匹跑得最快的马。过程分析
  10. 辩证统一和有机统一的比较