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

本文为【单片机步入嵌入式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,一经查实,立即删除!

相关文章

foxpro:将表写入excel

oleApp CREATEOBJECT("Excel.Application")oleapp.workbooks.addoleapp.visible.t.SELECT ls*写入表的标题oleapp.cells(1,10).value"这是我的表"*oleapp.cells(1,10).font.fontname"黑体"oleapp.cells(1,10).font.size24nfieldcountAFIELDS(al…

adb打开网页_android 使用指定浏览器打开网页

梳理下流程:枚举对应浏览器包名到数组中数组循环根据包名找到对应的LaunchIntent通过LaunchIntent找到对应的LaunchActivity的包名Intent通过设置activity的包名类名/*** 工具类*/public class CheckApkExist {private static String ucPkgName "com.uc.brows…

博客目录列表(C与Linux部分)

一、C语言 1、C语言——关键字 2、C语言——位操作 3、C语言——数组、函数、指针 4、C语言——结构体 5、C语言——预编译 6、C语言——宏定义 7、C语言——字符串函数 8、C语言——可变参数 9、C语言——回调函数 10、数据结构——链表 11、数据结构——堆栈 12、数据结构——…

python-greenlet模块(协程)

12345678910111213141516from greenlet import greenletdef test1():print(12)gr2.switch()print(34)gr2.switch()def test2():print(56)gr1.switch()print(78)gr1 greenlet(test1)#启动一个协程gr2 greenlet(test2)#启动一个协程gr1.switch()#switch是协程切换高并发&#x…

我妈在深圳的这些日子

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

2010-04-25 搞定aftr

今天终于完全地把aftr给搞定了。 刚开始的时候,建了三台机器,甲,乙,丙,甲和乙通过ipv6相连,甲的ipv6地址为2001:0:0:1::2/64,乙的ipv6地址为2001:0:0:1::1/64,乙和丙通过ipv4相连&am…

linux下解包bin二进制文件_linux下如何使用docker二进制文件安装_docker离线安装

1,下载二进制文件https://download.docker.com/linux/static/stable/x86_64/docker-18.03.1-ce.tgz2,解压二进制文件tar xzvf docker-18.03.1-ce.tgz3,复制二进制文件到/usr/bin目录下cp docker/* /usr/bin/4,检查是否安装docker versionClient:Version: 18.03.1-ceAPI ve…

【腾讯面试题】兔子试毒

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

git clone 指定分支的内容

使用Git下载指定分支命令为:git clone -b 分支名仓库地址 使用Git下载v.2.8.1分支代码,使用命令:git clone -b v2.8.1 https://git.oschina.net/oschina/android-app.git转载于:https://www.cnblogs.com/pansidong/p/9284967.html

大型网站架构

一个小型的网站,比如个人网站,可以使用最简单的html静态页面就实现了,配合一些图片达到美化效果,所有的页面均存放在一个目录下,这样的网站对系统架构、性能的要求都很简单,随着互联网业务的不断丰富&#…

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

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

python2编码问题解决了吗_Python2编码问题

以下内容说的都是 python 2.x 版本简介基本概念Python “帮”你做的事情推荐姿势1、基本概念我们看到的输入输出都是‘字符’(characters),计算机(程序)并不能直接处理,需要转化成字节数据(bytes),因为程序只能处理 bytes 数据。例如&#xf…

GridView自定义分页

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

当年年仅18岁韩寒舌战群儒,受尽冷嘲热讽!

https://weibo.com/3251967895/Gk4nNu9Fr转载于:https://www.cnblogs.com/bakblog/p/9287258.html

在朋友圈求助的NTP问题~

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

JRE和JDK 1.3、1.4、1.5(5.0)、6.0 各版本下载地址大全(J2SDK,JavaSE JavaEE)

本文转载(http://hi.baidu.com/y66901356/blog/item/7d32bf0abf7d7c3ab0351d39.html)本人绝对支持原创!!!Java SE Development Kit(JDK)和Java Runtime Envirnment(JRE)1.3、1.4、1.5(5.0)、6.0 各版本下载地址大全 (J…

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

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

gps84转换gcj02公式_百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换...

//定义一些常量var x_PI 3.14159265358979324 * 3000.0 / 180.0;var PI 3.1415926535897932384626;var a 6378245.0;var ee 0.00669342162296594323;/*** 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换* 即 百度 转 谷歌、高德* param bd_lon* param bd_lat* returns {…

javascript 15位和18位身份证的正则表达式及其验证

1、简单的正则表达式: (1)preg_match("/^(\d{18,18}|\d{15,15}|\d{17,17}x)$/",$id_card)(2)preg_match("/^(\d{6})(18|19|20)?(\d{2})([01]\d)([0123]\d)(\d{3}) (\d|X)?$/",$id_card)&#x…

EasyUI_datagrid

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