物理地址、链接地址、加载地址、虚拟/逻辑地址的区别

以S3C2440为例进行说明:
物理地址、链接地址、加载地址、虚拟/逻辑地址的区别_第1张图片
NAND有256M(数据总线:16),NOR有2M(16),SDRAM有64M(32)
NOR启动
使用NOR启动时,NOR Flash的基地址为0,片内RAM地址为0x4000,0000
CPU读出NOR上第一个指令(前4个字节)执行。读一个执行一个。
NAND 启动
NAND和CPU之间不是直接连接,而是通过一个NAND Flash控制器连接,所以程序不能再Nand上面运行。
使用NAND启动时,片内SRAM的基地址为0,NOR Flash是不可访问的状态。硬件把NAND前4K内容搬移到片内SRAM中,然后CPU从0地址取出第一条指令执行。

物理地址

就对应上面图中的地址。

链接地址和加载地址

有两个问题:

  1. NOR启动:由于NOR Flash不能被写的特性(只能按块写,不太方便),代码里的变量能被读而不能被写入。
  2. NAND启动:内部SRAM只有4K,若程序大于4K,NAND Flash中的程序也无法执行。

以NOR Flash启动为例
解决
对于NOR启动时变量不能被写:修改Makefile中的数据段地址,将数据段(变量)写到SDRAM的起始地址0x30000000中。

-Tdata 0x30000000

不过这样会造成黑洞,即中间空出来很大一块空的内容,最后生成的bin文件非常大。所以才有了链接脚本。而上面这样仅重定位了数据段,链接脚本为分体式的,比较适合单片机。后面我们直接把代码段和数据端都重定位到SDRAM中执行,即一体式链接脚本,因为嵌入式系统的内存非常大,不用像单片机那样节省空间。
一体式链接脚本参考

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }
   .bss  : { *(.bss) *(.COMMON) }
}

链接脚本有两个概念VMA(即链接地址)和LMA(即加载地址)。通常情况下VMA=LMA,但在嵌入式系统中由于前面空间的原因往往不同。

  • 链接地址:程序实际运行的地址
  • 加载地址:程序被实际加载到内存的位置;程序编译后的存放地址

对于上面的链接脚本的data段:代码段保存在生成的bin文件中的0x800处,烧写bin到NOR时,同样保存在NOR的0x800处。而前0x800代码为重定位代码,需要实现从NOR的0x800(加载地址)复制data段到0x30000000(链接地址,0x30000000是SDRAM的首地址)。(以上针对裸板程序,且仅是重定位了data段,实际上一般重定位所有段。如果有BootLoader的话,它会帮我们做好代码重定位、初始化等工作)
把整个程序复制到SDRAM需要注意哪些细节:

  1. 把程序从Flash复制到运行地址,链接脚本中就要指定运行地址为SDRAM地址;
  2. 编译链接生成的bin文件,需要在SDRAM地址上运行,但上电后却必须先在0地址运行,这就要求重定位之前的代码与位置无关(是位置无关码);
    还没有将代码重定位之前:
    程序还是在NOR/SRAM中执行,汇编一定要写位置无关码:也就是要保证相对跳转,以当前的PC值加上一个offset偏移量决定接下来执行的指令,因为代码结构没有变化,所以相对跳转还是跳到NOR/SRAM中。
    如果用绝对地址跳转的话,会跳转到SDRAM中。而此时我们还没有把代码复制到SDRAM中。
    代码重定位之后:
    使用绝对跳转指令ldr pc,=main,这条汇编指令取出了main函数的地址,这个地址由前面链接脚本指定在SDRAM中了。
    如果bl main 则代码会继续在NOR/SRAM上运行。这条指令会计算main函数到当前的PC+4需要跳转多远,为位置无关码。

虚拟地址(逻辑地址)

2440有64M内存(SDRAM),假设现在有n个APP同时运行,则:
①它们同时保存在SDRAM里;
②它们的地址各不相同;
前面说的链接地址其实就是程序运行时所处地址。假设APP1所处的地址是Add1,APP2所处的地址是Add2,APPn所处的地址是Addn,则编译某个App时,都需要单独指定它的的链接地址,这是不可能完成的任务。因为嵌入式系统应用程序可能有成百上千个,你不可能重新编译这成百上千的应用程序,并且这些应用程序运行时保存的地址,也是不可预料的。
为了解决上述问题,于是就引入了虚拟地址:虽然应用程序保存在内存中的位置各不一样,但对于CPU,它们运行时,都在同一个虚拟地址上
举个例子,如两个hello应用程序,编译后查看反汇编代码,两个程序的起始地址都是0x80B4。CPU运行两个APP时,都会去0x80B4读指令,然后经过MMU转换成Addr1、Addr2。这样,不同的APP可以在任意地址,经过MMU地址转换后,在内存上是不同的地址,互不干扰
注:APP并不是真正的同时运行,CPU是分时操作,学过操作系统的话应该都能理解。
引入虚拟地址的原因

  1. 让APP可以以同样的链接地址来编译
  2. 让大容量APP可以在资源少的系统上运行:2440内存就只有64M,假如有一个APP需要1G的内存。应用程序执行时,不是一次性将所有代码都放入内存,而是将要运行的部分依次放入,当放入的代码指令大于64M后,会先将SDRAM里暂时用不到代码指令先置换出来,再放入需要运行的代码指令。这样尽管SDRAM很小,也可以运行内存需要很大的应用程序,而这个置换管理的工作,就是由MMU完成的。
  3. 权限管理,禁止访问其它空间:此外,不同的APP之间应该相互独立,避免APP1能直接访问到APP2,以防止APP1影响APP2。

你可能感兴趣的:(嵌入式Linux,嵌入式,linux)