程序运行地址和加载地址-为什么使用位置无关指令

转:http://blog.chinaunix.net/uid-28852942-id-3987462.html

 

在嵌入式编程中,我们经常讲程序保存在 nand flash中。但是我们知道,nand flash的接口设计和 RAM 的接口设计是不一样的。
他的 数据线通常都是复用的,所以通常存取都是以块为单位(nor flash带有RAM接口,有足够的地址线来寻址,
所以可以访问内存中每一个字节)  这导致了,nand flash不可以片内执行程序(nor flash可以,因为他能存取内存每一个字节)


对于 s3c2440 来说,当使用 nand flash 启动时,为了解决 nand flash 不能片内执行程序的问题(片内不能执行,那么程序烧进去不是不能运行嘛)
 于是 s3c2440 在内部有一个 叫 Stepping Stone(垫脚石?)的东西,其实他就是一块 4KB 的RAM,当我们以 nand flash启动想运行烧写在上面
 的程序是,s3c2440会自动将 nand flash 前面 4KB 的内容拷贝到 这个叫Stepping Stone的片内内存中。
 然后 pc指针为0 从这个片内内存0地址开始运行。


 但是这个 片内内存只有 4Kb 大小,如果我们烧到 nand falsh中的程序大于 4KB 那么只有一部分被考到了片内内存中去执行。剩下的就不能执行了。
 
 但是我们不是可以接外设吗, s3c2440的BANK6 和 BANK7都可以接最大 128M的SDRAM。SDRAM是一个RAM(内存)
 
 那么当程序大于 4k 的时候,当我们以 nand flash启动后,前面的4Kb 被拷贝到 片内RAM中去执行(自动完成)。
 我们在这前4K的程序中初始化SDRAM(SDRAM 使用前需要初始化) ,然后将剩下的程序拷贝到 SDRAM中(不是只有4kb 被拷贝到片内RAM中执行了嘛)
 然后跳转到 SDRAM中去执行剩下的程序。
 
 那么也就是说 通常当程序大于 4kb的 时候,我们就需要把程序拷贝到SDRAM中去运行。(程序小于4KB 那么也就可以不用拷贝了,以nand flash方式 启动后,程序全被拷贝到 片内4kb的 RAM中去运行。)。
 
 那么,既然程序大于4kb的时候需要从nand flash中拷贝到 SDRAM中去运行。自然可以想到 烧到nand flash中的程序前面一部分代码应该
 是初始化SDRAM(程序最终需要拷贝到SDRAM中去运行)和 将NAND flash中的剩余的程序拷贝到SDRAM中去(全考过去也行,方便点),然后跳转到SDRAM中执行。
 
下面我们就要详细说明,前面这一段跳转到 SDRAM去执行前在片内内存中运行的初始化代码的要点和细节。也就是 关于程序运行地址和加载地址以及位置无关指令的 一些注意点和细节


先来看下程序运行地址和加载地址


看个 随便写的简单的示例,这是一个连接脚本中的一段 first 0x30000000 : AT(0){main.o}


我们只注意 0x30000000 和 AT(4096) 这两处。


如果程序是烧到nand flash中。这句话里面的意思就是  a.o烧到nand flash 中从0地址(当然也可以是其他数)开始的地方,但他的运行地址是在
从0x30000000地址开始的地方。(为什么是0x30000000,应为 s3c2440的 bank6 和bank7 可以接sdram,bank6从地址0x30000000开始,我们的程序最终是要
在SDRAM中运行的)


烧到 nand flash中从地址0 开始的地方应该比较好理解,就是说我程序是存储在nand flash中最开始的地方。那么运行地址呢怎么理解呢
  
 看一下断汇编 
  1 .text
  2 .global _start
  3 _start:
  4         mov     r0, #2
  5 loop:
  6         ldr     pc,=loop




  
 上面这段汇编,我们将 他的运行地址分别设为 0x0 和0x30000000来看看反汇编后的情况
先将运行地址设为0x0
  all: test.c head.s
  2         arm-linux-gcc -c -Wall -o head.o head.s
  3         arm-linux-ld -Ttext 0x0 -o main_elf  head.o
  4         arm-linux-objdump -D -m arm main_elf > main.dis
  5 
  6 clean:
  7         rm -rf *.o main.dis main_elf


反汇编 main.dis如下:
 6 00000000 <_start>:
  7    0:   e3a00002        mov     r0, #2  ; 0x2
  8 
  9 00000004 <loop>:
 10    4:   e51ff004        ldr     pc, [pc, #-4]   ; 8 <.text+0x8>
 11    8:   00000004        andeq   r0, r0, r4

 将     arm-linux-ld -Ttext 0x0 -o main_elf  head.o
 改为    arm-linux-ld -Ttext 0x30000000 -o main_elf  head.o
 也就是将运行地址设定为0x30000000 再看看它的反汇编
  6 30000000 <_start>:
  7 30000000:       e3a00002        mov     r0, #2  ; 0x2
  8 
  9 30000004 <loop>:
 10 30000004:       e51ff004        ldr     pc, [pc, #-4]   ; 30000008 <.text+0x8>
 11 30000008:       30000004        andcc   r0, r0, r4

 
 注意最左边的数字,这就代表程序的运行时地址。也就是说程序的代码的地址是以运行地址为基址来标示的。什么意思呢,
 
 看 head.s中的  ldr     pc,=loop  这段,这是想让 程序调回到 loop处(死循环)
 当运行地址设定为 0x0 时 从汇编代码我们看到 loop标号代表的地址为0000004 
 也就是说  ldr     pc,=loop 反汇编为 ldr     pc, [pc, #-4] 即pc值为pc-4地址里面放的值(4)。就是跳转到 00000004 地址去
 
 那么把运行地址设定为0x30000000时, 从反汇编代码中我们看到 这时 loop表号代表的地址是0x30000000
 那么 ldr     pc,=loop 就是跳转到 地址 0x30000000
 
简单的理解就是 程序运行地址 就是 计算机认为程序运行时应该处于的地址。
所以 运行地址设置为0 时,程序中的所有代码的中的标号都是以0x0为基址的。计算机认为他运行的时候的地址是从 0x0开始的。
运行地址设置为0x30000000 时,程序中的所有代码的中的标号都是以0x30000000为基址的。计算机认为他运行的时候的地址是从0x30000000开始的
 
 
 
明白了 运行地址 的概念后,我们来看看程序的启动过程


假设现在程序的 加载地址(烧到nand flash中的位置)为0 运行地址为0x30000000
前面说过程序是烧写在 nand flash中从 0地址开始的地方,那么以nand flash方式启动后,该程序被拷贝到 片内ram中。这时候pc为0。并开始运行程序,也就是说,计算机现在从 片内内存地址0开始运行程序进行一些初始化操作并将sdram初始化后再跳转到sdram中去运行。
但是我们上面不是说,程序运行地址被设置成了 0x30000000(SDRAM的起始地址)吗。

但是程序在跳转到sdram之前却是在0x0地址开始运行。这就造成了前面这段还未跳转到sdram(从0x30000000开始)的程序的实际程序运行地址和设定的运行地址不符合。


如果程序中没有用到 地址有关代码,和全局变量静态变量之类地址有关变量,那么这段 在初始化SDRAM之前 的程序 其实还是可以运行下去的
并在初始化SDRAM后,跳转到SDRAM中去运行,一切正常
但是如果程序中用了这些代码。就不会运行成功了。


原因很简单,应为现在我们设定的 运行地址为 0x30000000,那么当我们执行地址有关代码 如 ldr pc,=A (A是个标号或函数名)
就是使用了 绝对地址,那么 pc = 0x3000000+x (x为标号A相对于起始也就是前面设定的运行地址的偏移) 。

那么这条指令执行之后,pc 指针将跳转到 0x30000000后的某处(SDRAM中)。但是 sdram 现在 还未初始化! 
这就 造成了 错误。
同样 如果有全局变量 或静态变量也是。


所以我们需要使用 b bl类的 位置无关指令。  如  b  A
b bl跳转是基于 pc的 跳转。即相对跳转 ,比如执行 b A 这条指令时,假设 现在pc =5. A标号相对当前的位置为2(在之后)
那么 b A 后 pc =pc +2    即计算机不管当前pc指针是多少,他执行的是相对于当前位置的跳转。也就避免了 向上面的那样跳转到了 0x30000000之后的未初始化的地址中去

posted @ 2013-11-15 08:57  dolinux  阅读(738)  评论(0编辑  收藏  举报