从Nand Flash 启动CPU的时候,内部硬件自动将Nand Flash 开始的4KB 数据复制到4KB的内部RAM“Steppingstore”(起始地址为0)中,cpu是从pc寄存器指向的地址取指令执行的,pc指向的地址应该存放有对应的指令才能正确执行。刚开始的时候pc寄存器是指向0地址的,CPU从0地址取指令开始执行。
链接地址是我们写Makefile的时候指定了程序运行的时候应该保存在什么位置,但运行时程序具体存储在哪里由硬件或我们写程序的时候把程序本身加载在哪里来决定。可能分段存储,可能重复存储在多处。例如nand启动,不管链接地址是哪里,硬件上自动把nand前4kb 加载到steppingstore,后面又可能把程序从nand复制到其他地方去,往往复制到程序的链接地址那里去,这样那些位置相关码才可以正确执行。
不管链接地址,加载地址是哪里,总之cpu是从pc寄存器指向的地址取指令执行的,只要pc指向的地址存放有对应的指令就能正确执行。这句话很重要。
位置无关码与位置相关码对pc寄存器的值的影响:
位置无关:pc = 当前 pc值(当前指令实际存储地址或加载地址) + 相对偏移量
位置相关:pc = 程序链接地址 + 绝对偏移量
注意:我说的程序链接地址与指令链接地址是不一样的,程序的链接地址指的是第一条指令的链接地址。
名词解释:
1、相对偏移量:下一条要执行的指令的实际存储地址(即加载地址) - 当前指令的实际存储地址(即加载地址)
或者:下一条要执行的指令的链接地址 - 当前指令的链接地址
2、绝对偏移量:下一条要执行的指令的实际存储地址(即加载地址) - 程序的实际存储地址(即加载地址)
或者:下一条要指向的执行的指令的链接地址 - 程序的链接地址
一句话,无论是实际存储地址(即加载地址),还是链接地址,相对偏移是 指令与指令之间的偏移,绝对偏移 是指令相对于程序起始位置的偏移。
当程序的实际存储地址(即加载地址)为0的时候,对于位置无关码有:
pc = 当前 pc值(当前指令实际存储地址或加载地址) + 相对偏移量
= 当前 pc值(当前指令实际存储地址或加载地址) +下一条要执行的指令的实际存储地址(即加载地址) -
当前指令的实际存储地址(即加载地址)
= 下一条要执行的指令的实际存储地址(即加载地址)
即此时pc与链接地址无关,这就是为什么程序加载到steppingstore(0地址)的时候,即使程序的链接地址不为0,位置无关码仍可以在这里正常执行的原因。
实例分析:
@*************************************************************************
@ File:head.S
@ 功能:设置SDRAM,将程序复制到SDRAM,然后跳到SDRAM继续执行
@*************************************************************************
.equ MEM_CTL_BASE, 0x48000000
.equ SDRAM_BASE, 0x30000000
.text
.global _start
_start:
bl disable_watch_dog @ 关闭WATCHDOG,否则CPU会不断重启
bl memsetup @ 设置存储控制器
bl copy_steppingstone_to_sdram @ 复制代码到SDRAM中
ldr pc, =on_sdram @ 跳到SDRAM中继续执行
on_sdram:
ldr sp, =0x34000000 @ 设置堆栈
bl main
halt_loop:
b halt_loop
disable_watch_dog:
@ 往WATCHDOG寄存器写0即可
mov r1, #0x53000000
mov r2, #0x0
str r2, [r1]
mov pc, lr @ 返回
......
......
其Makefile:
sdram.bin : head.S leds.c
arm-linux-gcc -c -o head.o head.S
arm-linux-gcc -c -o leds.o leds.c
arm-linux-ld -Ttext 0x30000000 head.o leds.o -o sdram_elf
arm-linux-objcopy -O binary -S sdram_elf sdram.bin
arm-linux-objdump -D -m arm sdram_elf > sdram.dis
clean:
rm -f sdram.dis sdram.bin sdram_elf *.o
其sdram.dis文件部分内容:
30000000 <_start>:
30000000: eb000005 bl 3000001c <disable_watch_dog>
30000004: eb000010 bl 3000004c <memsetup>
30000008: eb000007 bl 3000002c <copy_steppingstone_to_sdram>
3000000c: e59ff090 ldr pc, [pc, #144] ; 300000a4 <mem_cfg_val+0x34>
30000010 <on_sdram>:
30000010: e3a0d30d mov sp, #872415232 ; 0x34000000
30000014: eb000032 bl 300000e4 <main>
Makefile 中arm-linux-ld -Ttext 0x30000000 head.o leds.o -o sdram_elf指明了:
程序的链接地址是0x30000000,是sdram的起始地址,程序运行时本应在地址0x3000000处开始存放着这个程序,但是程序被烧到nand,从nand启动,被加载到steppingstore,对于刚开始的几条位置无关码,由于此时实际加载地址为0,则 pc = 下一条要执行的指令的实际存储地址(即加载地址),cpu根据pc值能找到对应的指令,能正确执行。
执行bl copy_steppingstone_to_sdram之后,0x30000000处已经存放有程序了,接着要到“bl copy_steppingstone_to_sdram”的下一条指令“ldr pc, =on_sdram”,执行这条指令的时候,cpu是从0xc( 0+3*0x4,一条指令的大小是0x4)处取这条指令执行的,并不是在0x30000000+ 3* 0x4 = 0x3000000c处取指令,执行完“ldr pc, =on_sdram”之后pc就指向标号“On_sdram"指向的地址了,这个"on_sdram"指向的地址是位置相关地址,与链接地址有关,是绝对地址,
=链接地址+绝对偏移量,具体是0x30000000+ 4* 0x4 = 0x30000010,
(反汇编文件中可以看到这个,确实是=链接地址+绝对偏移量
30000010 <on_sdram>:
30000010: e3a0d30d mov sp, #872415232 ; 0x34000000
//对应第五条指令ldr sp, =0x34000000 @ 设置堆栈
)
pc指向了0x30000010,对应的指令也由copy_steppingstone_to_sdram复制到了这里了,后面CPU就从这里取指令执行。