运行地址与加载地址估计大部分人没弄明白~

本文为【单片机步入嵌入式Linux】系列文章的第二篇,主要是跟大家讲解一下链接过程中几个地址的区分与理解~

1 单片机存储分配

在玩单片机(以stm32为例)的时候会有RAM空间和ROM空间,RAM空间主要是用于数据的访问,而ROM空间用于存放烧录的固件,当然固件也可以直接加载到RAM中运行,只是说每次上电都需要重新加载。

如上图所示ROM为FLASH地址,而RAM为SRAM地址,毋庸置疑生成的单片机固件会烧录到Flash上,这样才能保证每次上电都有可以正常运行。

对于很多初学者该有疑问了,明明全局变量等等都是分配到RAM上的呀,怎么说固件放到Flash上的呢?

其实并不矛盾,程序指令中访问变量都是访问变量的地址也就是内存的地址,所谓的分配到RAM上,仅仅只是说相应的变量占据了对应的RAM地址,并不能理解为这个变量存在于RAM里面。

可能你还会继续问 : 暂且认同上面的说法,那对这些变量的初值该如何解释呢?

可以肯定的是,这些全局变量的初值并不是来源于RAM,因为RAM掉完电以后数据就丢失了,而在程序正常运行过程中,不管怎么上下电其初值都是我们程序中规定的,也就是在编译中确定的。

所以这些初值要保存只可能存在ROM中,这中间肯定有这样一种机制 : 在上电以后把ROM中存储的这些变量初值来重新初始化到对应的RAM地址,以便后续程序指令访问,这种机制通常叫分散加载。

2 简述分散加载



上图是一种简单的分散加载机制,映像文件由不同的段组成,通常都有代码段(.text)、已初始化数据段(.data)、未初始化及初始化为0的数据段(.bss)等等,而且他们具有不同的属性RO,RW,ZI等等。

为了便于大家理解,整个系统的存储区分为ROM和SRAM,左边Load View表示的是程序存储地址空间分布情况,也就是程序烧录到ROM以后的空间分配情况。

固件烧录到ROM区域并且分为RW区和RO区,RW区域为可读可写区域而RO区域为只读区,分这两个区域并不是说RW区域存储地址区域以后就用来数据的读写,而是为了上电过程中的copy/decompress(复制或者解压)过程做好标记,这个过程会把一些非零全局变量(或者静态变量等)的SRAM地址(实际的运行地址)处赋予初始值。

ZI区域是零填充区域,主要是.bss段的一些初始化为0或者未初始化的全局或者静态变量分布区域,这些数据没有必要保存到固件中,所以由加载机制自行清零即可。

一切准备就绪就形成了右侧的execution View的运行空间视野,由于ROM中程序运行所涉及到的全局变量等的访问都是SRAM地址的访问,而这些地址恰好在程序编译链接过程中已经分配到SRAM里面,经过前面的该部分地址的重新定位,运行空间的程序就可以正确访问到这些变量的初值等等。

3 stm32启动流程



很多刚玩MCU的朋友,都会以main函数作为程序的开始运行处,不过几乎所有的C程序在执行前都会使用汇编指令,通过汇编指令构建C语言运行环境,并运行C程序,所以在C程序执行前做了非常多的工作,其中非常重要的就是堆栈指针的设置,这也是从汇编到C运行环境一定要做的一件事了。

那么stm32的启动大致流程是怎样的?这里小哥就简述一下:

当然还有一些小细节,这里就不展开了,stm32的Flash可以直接运行程序,采用分散加载,只需要把相应的数据区域加载到运行地址处便可以正常的访问,这个与前面的所说是类似的。

4  uboot部署Linux

在进行Linux系统开发过程中,一切从Bootloader开始,而bootloader本质上就是一个单任务的裸机程序,和单片机程序是一样的,而在众多bootloader中最为常用和广泛的就是uboot了,他就是为了部署Linux环境而生的,下载、烧录、运行Linux映像、文件系统等等。

uboot都可以搞定,所以它对地址是非常敏感的,程序、参数等等应该存储在什么地址,在什么地方运行都是需要确定好的,而这些地址在编译链接的过程中,链接脚本已经确定好了这一切,uboot的工作就是把这些固件放在编译链接所规定的运行地址处进行运行即可。

比如全局变量在什么地址,函数在什么地址,当程序运行的过程中就会从这些确切的地址处取数据,如果你把全局函数指针变量的地址分配到了NANDFlash上,那么程序在访问的过程中就有可能跑飞。

程序运行最重要的两个地址加载地址运行地址

加载地址也常被大家成为存储地址,即实际固件存储的位置,其实该地址也只是一个相对的概念,就相当于单片机中bin文件烧录在什么位置一样的道理。

运行地址也叫链接地址,即程序的绝对地址。全局变量等等都是以该地址为基础,来确定程序的运行状态的各部分的地址布局。

当然Linux以上各部分直接烧写到RAM也是也可以直接运行的,不过还是那个问题,一旦掉电则全部丢失,所以最终每个部分都会写入到Flash上(当然在前期调试的时候可以直接下载到RAM中,减少对Flash的反复擦写),但对于大部分Flash都是无法直接运行程序的,即使能够运行,比如Norflash也是非常的慢,且不能够直接写入,所以Linux内核等都会加载到RAM来运行,以获得更快的执行速度,那么前面介绍的那种单片机方式只重定位数据段的方式不太适用了。

在嵌入式Linux平台上,首先执行的就是bootloader,而它只是一个顺序执行的程序,它有一个重要的工作就是把Linux内核搬运到RAM中运行,由于我们的内核兼容不同的单板,uboot也会传递给内核一些配置参数以配置内核。

往往RAM分配的地址比较高,而整个程序往往都是0地址开始执行了的,如果让存储地址与运行地址相同来进行编译,会导致最终烧录文件非常之大,并且中间有一大片地址区域是无效的。

那么有什么办法来解决这个无效区域以缩小我们的固件大小呢?先了解下位置无关指令。

5 位置无关指令

既然有位置无关指令就有位置有关指令,简单的说所执行的指令是不是与位置相关才能达到目的。

可以类比与绝对路径与相对路径,相对路径你可以把程序放在任何文件夹下面,编辑器均可以根据工程文件路径找到其他每一个文件,而绝对路径却不行,一旦文件夹换了,基本上就是定位不到具体的每个文件了。

所以位置无关就相当于相对路径,数据的访问、函数的调用几乎都是相对的,为什么说是几乎呢?因为有些情况下访问绝对地址也是与位置关系不大的,可以把这段程序放在可以执行的任何位置,所以位置无关码的运行与链接地址也没有直接的联系。

比如跳转指令B BL等这些跳转指令采用PC+偏移量,所以为位置无关指令;而如果我们采用ldr r0, =标记,而这些标记都是实际在链接过程中确定的运行地址,所以该指令为位置有关指令;并且全局变量基本上都是位置有关,而局部变量为位置无关;所以对于位置无关代码区域,跳转一般都使用B指令,而从位置无关代码区域跳转到位置有关指令代码区域去执行就需要借助位置有关跳转指令。

6 加载与运行地址不同


当存储地址与链接地址不同时,多数情况下由于采用位置有关指令会出问题,最常见的就是PC指针取的绝对地址,而此时该绝对地址处无存储,导致程序飞掉。

既然有了位置无关的程序,那么我们就可以把其当作一个搬运工放在位置有关部分的后面,一旦需要运行位置有关码,那么就会通过位置无关码把有关部分拷贝到运行地址处,然后跳转执行即可,这样整个的程序就可以做得非常的连续且中间几乎没有无效区域,该搬运的过程就是常说的重定位

  

 7 地址的设置


大部分ARM处理器其PC都是从0地址开始执行,所以在0地址处要么是运行程序,要么就是引导程序,如果没有这两样,你的程序烧录到其他位置均无法得到运行。

对于S3C2440芯片能够支持NorFlash和NandFlash启动,其中NorFlash上可以直接运行,而NandFlash启动由于其程序无法直接在上面运行,芯片会把内部SRAM作为0地址处,并且把NandFlash前4K代码拷贝到SRAM上运行。

因为这里最终想让所有的程序都在SDRAM里面运行,考虑使用全部重定位的办法,在链接脚本中确定好程序的存储地址和运行地址。

上图是GUN linker中截取的段描述格式,来源于:

http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html

具体详细解读大家可以参考上面的链接,下面看看几个常用的。

可执行文件由各个段组成,:

1、secname段名,一般使用数据段.data段,代码段.text段等等。

2、AT(ldadr)表示该段存储地址,也就是加载地址。

3、contents表示目标文件(比如.o目标文件)中的哪些段放在本段,也可以是整个目标文件全部放在这个段内。

4、start表示本段链接(或者称为运行)的地址,如果没有使用AT(ldadr),本段存储的地址也是start,也就是说存储地址与运行地址相等。

通过上面的段描述格式就可以在链接过程中确定好程序的运行地址和载入地址,以方便后续的重定位地址的使用。

下面以一个简单的实例说明一下:

 1//....格式2SECTIONS {3...4secname start BLOCK(align) (NOLOAD) : AT ( ldadr )5  { contents } >region :phdr =fill6...7}8//.....示例9SECTIONS {
10...
11.text 0x30000 : AT ( 0x0000 )
12  { *(.text) }
13
14.data 0x3FFFF : AT ( 0xFFFF )
15  { *(.data) }
16...
17}

这样固件的代码段的存储地址为0,数据段存储地址为0xFFFF,而运行地址分别为0x30000和0x3FFFF,最终重定位部分就根据这链接脚本中的符号获得相应地址,然后把相应的部分"搬运"到运行地址处运行处,比如如果载入地址在NandFlash上,那么重定位的过程中就需要初始化NandFlash控制器,然后读取NandFlash上的数据并"搬运"到运行地址处。

在嵌入式linux中很多时候这些地址都需要我们自己确认和设置的,不然Linux内核无法启动或者加载相应程序,而在单片机开发中用惯了IDE工具,所以大部分人涉及得不多~

enjoy~

素材源于:嵌入式情报局

版权归原作者所有。仅供技术的传播和学习讨论,如涉及作品版权问题,请联系我进行删除。


推荐阅读:

专辑|Linux文章汇总

专辑|程序人生

专辑|C语言

我的知识小密圈

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

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

相关文章

我妈在深圳的这些日子

今天送了我丈母娘回家,平时在家里,我会叫妈。下面文章中写到的我爸、我妈、指的是我老丈人和丈母娘。上个周末,我跟我妈说,谢谢你过来帮忙我们照顾楠哥,辛苦你了。我说了两遍,可能她听的不是很清楚&#xf…

【腾讯面试题】兔子试毒

大家好,我是牛牛,经过了忙碌的一周,终于盼来了周五。今天给大家分享一道有趣有料的算法题,希望能让大家开启周末的好心情。01故事起源有1000瓶药水,其中有一瓶是毒药,只要喝上一滴,一天之后就必…

分享一个剪切板的小软件CopyQ

我是最近在工作的时候经常需要复制一些命令,而且这些命令如果用手敲的话会超级麻烦,所以体验了几个剪切板的小软件,这个是我体验之后觉得最不错的一个,分享给大家。软件链接地址https://github.com/hluk/CopyQ/releases软件图标我…

GridView自定义分页

有时候的只是需要一些简单的但却是自定义的分页功能,但是又舍不得objectdatasource的排序功能,那就只有把pageddatasoure和objectdatasour结合起来, 由于pageddatasource实现的是IEnumberable,直接把objectdatasource赋给它是不行…

在朋友圈求助的NTP问题~

之前朋友圈求助的问题最后是我一个朋友尝试了一天的配置「这个配置尝试的过程需要技术基础,但是不管如何的技术基础都是需要去不断的尝试的」,终于找到了方法,所以~我给他们送了秋天的第一杯奶茶~「是他们是因为他们都是一群我很喜欢的同事」…

聊聊身边的嵌入式,英语学习利器点读笔

家里有小孩的朋友,可能对下面的这款产品不陌生。点读笔,一个会发声的电子产品,我当时为了给孩子做英语启蒙,买了小达人点读笔(上图最下方那个,另外两个分别是宝玩英语和巧虎配套的点读笔),用了好几年了&…

EasyUI_datagrid

案例一丶jquery.easyui.min.js:10631 Uncaught TypeError: this.renderEmptyRow is not a function 解决方法:datagrid记录为空就会报错。是easyui里没处理return 空的方法。需要进行添加一些东西。具体我也不清楚, 案例二丶清除datagrid右侧空白区域 查…

三枚硬币自制收音机

大家好,我是记得诚。我们称这个收音机为三个硬币收音机是因为我们使用了三个硬币作为锚点,连接收音机各个部件,这将使我们的制作过程变得极为简单。在这个收音机中我们使用了一个特殊的10晶体管集成电路,使最后做出的收音机效果非…

双重检查锁实现单例模式的线程安全问题

一、结论 双重校验锁的单例模式代码如下: public class Singleton {   private static Singleton singleton; private Singleton() {} public static Singleton getSingleton() {     if (singleton null) { // 1       synchronized (Singleton.clas…

摇杆控制方向原理_为工业安全守好”门”!各种方向的控制阀原理图大集合

单向的、换向的....你想了解的方向控制阀都在这里了!方向控制阀门液压阀是用来控制液压系统中油液的流动方向或调节其流量和压力的。方向控制阀作为液压阀的一种,利用流道的更换控制着油液的流动方向。单向型方向控制阀是只允许气流沿一个方向流动的方向…

对于新生代农民工,你有什么想说的?

昨晚上这个新闻很多人转,但是可能很少有人知道他的链接出处,链接来自于http://www.mohrss.gov.cn/SYrlzyhshbzb/jiuye/gzdt/202108/t20210816_420736.html我记得我还在上小学的时候,我们家有干不完的农活,暑假每天都要下田干活&am…

谁是经营之神

—北京维富友携手北京服装学院开展服装ERP沙盘大赛时间4月21日,地点北京服装学院,参加人数35人,参加人员北京维富友软件公司高级讲师、服装学院教师和同学共35人。目的:为了让学生更好的掌握服装企业管理和盈利管控,北…

在Android初次的前期学习中的二个小例子(2)

Hello13:SQLite数据库 一、简述SQLite的概念和主要特性SQLite是一个轻量级的关系型数据库,运算速度快,占用资源少,使用非常方便,支持SQL语法标准和数据库事务原则。相对于SharedPreferences使用文件保存数据,SQLite具有…

access 增加字段 工具_Java效率工具之Lombok

作者:LiWenD正在掘金来源:https://juejin.im/post/5b00517cf265da0ba0636d4b上一篇:数据库查询速度优化之解决技巧还在编写无聊枯燥又难以维护的POJO吗?洁癖者的春天在哪里?请看Lombok!在过往的Java项目中&…

一文读懂 | 进程并发与同步

并发 是指在某一时间段内能够处理多个任务的能力,而 并行 是指同一时间能够处理多个任务的能力。并发和并行看起来很像,但实际上是有区别的,如下图(图片来源于网络):concurrency-parallelism上图的意思是&a…

rust卡领地柜权限_RFID智能医疗耗材柜,上海智能高值耗材柜,国药智能医用耗材柜...

近几年因为我们的医疗改革一直在进步并改革,国家对我们的医疗方面的补助也有了明显的加大投入,与此同时让各种公立私立医院如雨后春笋般层出不穷,各大医院为了在医疗市场占有一席之地,都在各个方面开始想办法提升自己医院的水准。…

刚接触电子时,有过哪些百思不得其解的问题?

青少年时期,刚接触电子时,出于好奇,对这方面的东西也比较关注,但同时也衍生了一些百思不得其解的疑问,比如...01物理书里说大地是导体,那为什么我的小灯珠却不亮!?02初三时学了物理的…

建立管理SQL Server登录帐户

1、打开SQL Server 2005的管理工具,选择以windows身份验证模式登陆。然后右击服务器选择属性。2、在打开的服务器属性页面中,选择“安全性”做如下图设置:3.在windows上新建三个组:ReceptionEmployees,ITEmployees。4、然后在SQL …

c++ doxygen 注释规范_利用Doxygen给C程序生成注释文档

利用Doxygen为C程序生成注释文档一、Doxygen工具的安装利用Doxygen工具生成API帮助文档需要下载安装以下三个软件:(1)Doxygen:可以从一套归档源文件开始,生成HTML格式的在线类浏览器,或离线的LATEX、RTF参考手册。本文中所使用的版…

【2021新版】一线大厂 Go 面试题合集

秋天到了,又到了工程师们躁动不安,蠢蠢欲动的季节~这不,金九银十已然到了家门口,现在后台就有不少人问我:现在外边大厂面试都问啥想去大厂又怕面试挂面试应该怎么准备Go 开发前景如何啥样的后端适合切 Go 技术栈...面试…