单片机进入linux的运行地址和加载地址各个地址的区别和理解~01单片机存储分配玩单片机的时候会有RAM空间和ROM空间(以stm32为例),RAM空间主??要是用于数据存取,ROM空间用于存放烧写固件,当然也可以直接将固件加载到RAM中运行,但每次上电都需要重新加载。如上图所示,ROM是FLASH的地址,RAM是SRAM的地址。毫无疑问,生成的MCU固件会被烧写到Flash中,以保证每次上电都能正常运行。对于很多初学者来说,应该会有疑惑。很明显,全局变量等等都是分配给RAM的。怎么说固件是放在Flash上??的?其实并不矛盾。程序指令中的访问变量是访问变量的地址。它是内存的地址。所谓分配到RAM只是指对应的变量占用对应的RAM地址,并不能理解为这个变量存在于RAM中。也许你会继续问:如果你暂时同意上面的说法,这些变量的初值怎么解释呢?可以肯定的是,这些全局变量的初始值并不是来自RAM,因为RAM断电后数据会丢失。它是丢失的,在程序正常运行的过程中,不管它是如何上电和断电的,它的初始值都是在我们的程序中指定的,也就是在编译时确定的。因此,这些初始值只能存储在ROM中。肯定有这样一种机制:上电后,将这些存储在ROM中的变量的初始值重新初始化为相应的RAM地址,供后续程序指令访问。这种机制通常称为分散加载。02分散加载简介上图是一个简单的分散加载机制。图像文件由不同的段组成,通常有代码段(.text)、初始化数据段(.data)、未初始化和初始化为0的数据段(.bss)等,它们具有不同的属性RO、RW、ZI等。为了方便大家理解,将整个系统的存储区分为ROM和SRAM。左边的LoadView显示了程序存储地址空间的分布情况,即程序烧录到ROM后的空间分配情况。固件烧入ROM区,分为RW区和RO区。RW区为可读写区,RO区为只读区。这两个区域的划分并不意味着RW区域存储地址区域后将用于数据。读取和写入,但要标记上电时的copy/decompress(复制或解压缩)过程。这个过程会给一些非零的全局变量(或静态变量等)的SRAM地址(实际运行地址)赋初值。ZI区是一个填零区,主要是.bss段中一些初始化为0或未初始化的全局或静态变量分布区。这些数据不需要保存在固件中,加载机制可以自行清除。一切准备就绪后,右边的执行View的运行空间视图就形成了。由于在ROM中运行的程序所涉及的全局变量的访问是对SRAM地址的访问,所以这些地址已经分配到SRAM中,在对前一部分地址进行重定位后,运行中的程序空间可以正确访问这些变量的初始值等等。03stm32启动过程很多刚接触单片机的朋友都会用main函数作为程序的开始,但是几乎所有的C程序在执行前都会使用汇编指令,通过汇编指令搭建一个C语言运行环境,运行C程序,所以在C程序执行之前做了很多工作,其中最重要的就是栈指针的设置,这也是从汇编到C运行环境必须要做的事情。那么启动stm32的大致过程是怎样的呢?在这里,我简单介绍一下:当然,还有一些小细节,这里就不展开了。stm32的Flash可以直接运行程序,采用分散加载,只需要将相应的数据区加载到运行地址后就可以正常访问了,和前面说的差不多。04uboot部署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处执行,如果存储地址和运行地址一样去编译,最后烧录的文件会很大,有大地址中间的区域。无效的。那么有什么办法可以解决这个无效区域来减小我们固件的体积呢?先了解位置无关指令。05与位置无关的指令既然有与位置无关的指令,那么也就有与位置相关的指令。简单来说,执行的指令是否与仓位相关就可以达到目的。可以用绝对路径和相对路径来类比。使用相对路径,可以将程序放在任意文件夹下,编辑器可以根据工程文件的路径找到每隔一个文件,而绝对路径则不行。一旦文件夹发生变化,基本上是不可能定位到每个具体文件的。所以位置无关,相当于相对路径。数据的访问和函数的调用几乎是相对的。可以执行的任何位置,所以位置无关代码的运行与链接地址没有直接联系。例如跳转指令BBL等跳转指令使用PC+offset,因此是位置无关指令;而如果我们使用ldrr0,=标记,这些标记就是链接过程中确定的实际运行地址,所以Instructions是position-relatedinstructions;全局变量基本上是位置相关的,而局部变量是位置无关的;所以对于位置无关代码区,跳转一般使用B指令,从位置无关代码区跳转到位置相关指令代码区的执行需要使用位置相关的跳转指令。06加载地址与运行地址不同。当存储地址与链接地址不同时,多数情况下会因为使用了位置相关的指令而出现问题。最常见的是PC指针所取的绝对地址。此时绝对地址处没有存储,导致程序飞走。现在我们有了一个位置无关的程序,我们可以把它作为位置相关部分后面的搬运工。一旦需要运行位置相关代码,相关部分就会通过位置无关代码复制到运行地址。然后就直接跳转执行,这样可以让整个程序非常连续,中间几乎没有无效区域。搬家的过程通常称为搬迁。07addresssetting大多数ARM处理器及其PC都是从地址0开始执行的,所以在地址0处要么有正在运行的程序,要么有bootloader。如果没有这两个,你的程序就无法烧录到其他地方。跑步。对于S3C2440芯片,可以支持NorFlash和NandFlash启动。NorFlash可以直接在上面运行,NandFlash启动不能直接在上面运行。芯片会使用内部SRAM作为地址0,将NandFlash的前4K代码拷贝到SRAM中运行。.因为我们最终希望所有的程序都运行在SDRAM中,所以考虑使用全部重定位的方法,在链接脚本中确定程序的存储地址和运行地址。上图是在GUNlinker中截取的section描述格式,来源:http://ftp.gnu.org/old-gnu/Manuals/ld-2.9.1/html_mono/ld.html详细解读可以参考到上面的链接,我们来看几个常用的。可执行文件由各种段组成:1.secname段名,一般用data段.data段,code段.text段等。2、AT(ldadr)表示段的存储地址,即加载地址。3.contents表示目标文件(如.o目标文件)中的哪些段放在这个段中,也可以把整个目标文件放在这个段中。4.start表示该段的链接(或运行)地址。如果不使用AT(ldadr),这一段存放的地址也是start,也就是说存放地址等于运行地址。通过上述段描述格式,可以在链接过程中确定程序的运行地址和加载地址,以方便后续重定位地址的使用。下面举一个简单的例子来说明:1//...Format2SECTIONS{3...4secnamestartBLOCK(align)(NOLOAD):AT(ldadr)5{contents}>region:phdr=fill6...7}8//.....示例9SECTIONS{10...11.text0x30000:AT(0x0000)12{*(.text)}1314.data0x3FFFF:AT(0xFFFF)15{*(.data)}16...17这样,固件的代码段存储地址为0,数据段存储地址为0xFFFF,运行地址分别为0x30000和0x3FFFF。最后的重定位部分根据链接脚本中的符号获取对应的地址,然后把对应的部分“移动”到运行地址中。比如加载地址在NandFlash上??,那么在重定位过程中需要初始化NandFlash控制器,然后读取NandFlash上??的数据,“移动”到运行地址上。在嵌入式linux中,这些地址往往需要我们自己确认和设置,否则linux内核无法启动或加载相应的程序,单片机开发中使用IDE工具,所以一般人不会涉及太多~本文转载自微信公众号“嵌入式情报局”,可通过以下二维码关注。转载本文请联系嵌入式智能局公众号。
