LDRPC, [PC, OFFSET_TO_LPOOL]
LPOOLDCD main
可见,虽然LDR是把基于PC的一个存储单元LPOOL的内容加载到PC中,但该存储单元中保存的却是链接时所决定的main函数入口的绝对地址,所以main函数实际所在的段不是位置无关。
② 位置无关的常量访问。在应用程序中,经常要读写相关寄存器以完成必要的硬件初始化。为增强程序的可读性,利用EQU伪指令对一些常量进行赋值,但在访问过程中,必须实现位置无关性。下面以PXA270的GPIO初始化介绍位置无关的常量访问方法。
可见,LDR伪指令实际上使用基于PC的偏移量来对符号常量GPIO_BASE和init_GPDR0进行引用,因而是位置无关的。由此可以得出如下结论:使用LDR伪指令将一个常量读取到非PC的其他通用寄存器中可实现位置无关的常量访问;但将一个地址值读取到PC中进行程序跳转时,跳转目标则是位置相关的。
(2) 程序设计规范2
其他被ROPI段中的代码引用的必须是绝对地址,或者是基于可读写位置无关(RWPI)段的静态基址寄存器的可写数据。
使用绝对地址只能引用被重定位到特定位置的代码段中的符号,通过在位置无关代码中引入绝对地址,可以让程序跳转到指定位置。例如,假设Bootloader的阶段1将其自身代码拷贝到链接时所指定的SDRAM地址空间后,当要跳转到阶段2的C程序入口时,可以使用指令“LDRPC, =main”跳转到程序在SDRAM中的main函数入口地址开始执行。这是因为程序在编译链接时给main函数分派绝对地址,系统通过将main函数的绝对地址直接赋给PC实现程序跳转。如果使用相对跳转指令“Bmain”,那么只会跳转到启动ROM内部的main函数入口。 2 位置无关代码在Bootloader设计中的应用
在使用GNU工具开发Bootloader时,程序在链接时会通过一个链接脚本(linker script)来设定映像文件的内存映射。一个简单的链接脚本结构如下:
这里不再介绍链接脚本的语法。需要指出的是,链接脚本中所描述的输出段地址为虚拟地址VMA(Virtual Memory Address)。这里的“虚拟地址”仅指映像文件执行时,各输出段所重定位到相应的存储地址空间,与内存管理无关。因此,上面的链接脚本实际上指定了Bootloader映像在执行时,将被重定位到BOOTADDR开始的存储地址空间,以保证在相关位置对符号进行正确引用,使程序正常运行。
ARM处理器复位后总是从0x0地址取第1条指令,因此只需把BOOTADDR设置为0,再把编译后生成的可执行二进制文件下载到ROM的0x0地址开始的存储空间,程序便可正常引导;但是,一旦在链接时指定映像文件从0x0地址开始,那么Bootloader就只能在0x0地址开始的ROM空间内运行,而无法拷贝到SDRAM空间运行实现快速引导。当然,对PXA270等具有MMU功能的微处理器来说,虽然可以先将Bootloader映像整个拷贝到SDRAM中,再使用MMU功能将SDRAM空间映射到0x0地址,进而继续在SDRAM中运行;但这样一方面会使得Bootloader的设计与实现复杂化,另一方面在一些必须屏蔽MMU功能的应用中(例如引导armlinux系统),无法使用MMU进行地址重映射。
利用ARM的基于位置无关的程序设计可以解决上述问题。只需在程序链接时,将BOOTADDR设置为SDRAM空间的地址(一般情况下利用SDRAM中最高的1 MB存储空间作为起始地址),这样ARM处理器上电复位后Bootloader仍然可以从地址0开始执行,并将自身拷贝到指定的__boot_start起始的SDRAM中运行。实现上述功能的链接脚本所对应的启动代码架构如下:
程序入口为_start,即复位异常,所有其他异常向量都使用相对跳转指令B来实现,以保证位置无关特性。在完成基本的硬件初始化后,利用链接脚本传递过来__boot_start和__boot_end的参数,将Bootloader映像整个拷贝到指定的SDRAM空间,并清零.bss段,初始化堆栈后,程序将main函数入口的绝对地址赋给PC,进而跳转到SDRAM中继续运行。程序在跳转到main函数之前,所有的代码都在ROM中运行,因而必须要保证代码的位置无关性,所以在调用初始化GPIO、存储系统和堆栈等子程序时,都使用相对跳转指令来完成。 使用位置无关设计Bootloader程序有如下优点:
① 简化设计,方便实现系统的快速引导。位置无关代码可以避免在引导时进行地址映射,并方便地跳转到SDRAM中实现快速引导。
② 实现复位处理智能化。由于位置无关代码可以被加载到任意地址空间运行,因此其运行时的当前地址与链接时所指派的地址并不一定相同。利用这一特性,可以在复位处理程序中使处理器进入SVC模式并关闭中断后加入如下代码,便可根据当前运行时的地址进行不同的复位处理:
上述代码中的ADR指令读取的_start标号地址由指令的执行地址决定。若是从SDRAM中的Bootloader启动,则上述比较结果相等,程序直接跳转到clear_bss标号地址处执行,这样可以避免存储系统的重新初始化和Bootloader的拷贝过程;若是上电或硬件复位,程序从ROM启动,则上述比较结果不等,程序便进行包括系统初始化和Bootloader拷贝等过程的全面复位处理操作。
③ 便于调试。Bootloader的调试通常也是一个繁琐的过程,使用位置无关代码,则可以将映像文件加载到SDRAM中进行调试,这既能真实地反映程序从ROM中进行系统引导的情况,又可以避免频繁烧写程序存储器。 3 结论
本文所介绍的基于位置无关的程序设计是通过基于PC或基址寄存器的符号引用规范来实现的。这种方法在实际系统开发中应用广泛,既能用于引导程序的设计,也可用于一般的应用程序或嵌入式共享库的开发。而在Bootloader的设计中引入位置无关代码,可以使程序结构更为简单清晰,并能避免地址重映射并从SDRAM进行快速系统引导;引用位置无关的设计方法使Bootloader的复位处理功能更为灵活,还使得在SDRAM中和在ROM中进行程序调试具有相同的效果。 参考文献
[1] 杜春雷. ARM体系结构与编程[M]. 北京:清华大学出版社,2003.
[2] 陈文智. 嵌入式系统开发原理与实践[M]. 北京:清华大学出版社,2005.
[3] ARM Limited. ARM Architecture Reference Manual. 2nd ed,2000.
[4] ARM Limited. ADS Developer Guide. Release 1.2,200111.
[5] ARM Limited. ADS Assembler Guide. Release 1.2,200111.
[6] Red Hat Inc. Using ld, the GNU linker. Version 2.14.