linux系列目录:
linux基础篇(一)——GCC和Makefile编译过程
linux基础篇(二)——静态和动态链接
ARM裸机篇(一)——i.MX6ULL介绍
ARM裸机篇(二)——i.MX6ULL启动过程
ARM裸机篇(三)——i.MX6ULL第一个裸机程序
ARM裸机篇(四)——重定位和地址无关码
ARM裸机篇(五)——异常和中断
linux系统移植篇(一)—— linux系统组成
linux系统移植篇(二)—— Uboot使用介绍
linux系统移植篇(三)—— Linux 内核使用介绍
linux系统移植篇(四)—— 根文件系统使用介绍
linux驱动开发篇(一)—— Linux 内核模块介绍
linux驱动开发篇(二)—— 字符设备驱动框架
linux驱动开发篇(三)—— 总线设备驱动模型
linux驱动开发篇(四)—— platform平台设备驱动
一、将程序重映射到RAM
在上一节中,我们将第一个裸机程序编译后,然后将映像文件重定位到了DDR3内存上,其中.bin文件的起始地址为0x80000000。重定位结束后,CPU会从这个地址读取第一条指令开始执行程序。
在链接之前查看代码.text段的地址信息如下:可以发现所有段的起始地址都为0
链接之后的.text段地址:.text段的地址被重映射到了0x80000000.
这一节我们将代码重映射到RAM中执行.
- 修改链接脚本
SECTIONS {
_load_addr = 0X87800000;
. = 0x900000;
. = ALIGN(4);
.text :
{
*(.text)
}
. = ALIGN(4);
.rodata : { *(.rodata) }
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(.COMMON) }
__bss_end = .;
}
- 修改汇编部分:
.text
.align 2 //设置字节对齐
.global _start
_start:
/* 设置栈 */
ldr sp,=0x80200000
/* 重定位text, rodata, data段 */
bl copy_data
/* 清除bss段 */
bl clean_bss
/* 跳转到主函数 */
// bl main /* 相对跳转,程序仍在DDR3内存中执行 */
ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */
halt:
b halt
- 修改C部分
#define CCM_CCGR1 (volatile unsigned long*)0x20C406C //时钟控制寄存器
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 (volatile unsigned long*)0x20E006C//GPIO1_04复用功能选择寄存器
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 (volatile unsigned long*)0x20E02F8 //PAD属性设置寄存器
#define GPIO1_GDIR (volatile unsigned long*)0x0209C004 //GPIO方向设置寄存器(输入或输出)
#define GPIO1_DR (volatile unsigned long*)0x0209C000 //GPIO输出状态寄存器
#define uint32_t unsigned int
void copy_data (void)
{
/* 从链接脚本中获得参数 _start, __bss_start, */
extern int _load_addr, _start, __bss_start;
volatile unsigned int *dest = (volatile unsigned int *)&_start; //_start = 0x900000
volatile unsigned int *end = (volatile unsigned int *)&__bss_start; //__bss_start = 0x9xxxxx
volatile unsigned int *src = (volatile unsigned int *)&_load_addr; //_load_addr = 0x80100000
/* 重定位数据 */
while (dest < end)
{
*dest++ = *src++;
}
}
void clean_bss(void)
{
/* 从lds文件中获得 __bss_start, __bss_end */
extern int __bss_end, __bss_start;
volatile unsigned int *start = (volatile unsigned int *)&__bss_start;
volatile unsigned int *end = (volatile unsigned int *)&__bss_end;
while (start <= end)
{
*start++ = 0;
}
}
/*简单延时函数*/
void delay()
{
static uint32_t delay_time = 0x1FFFF;
do{
delay_time --;
}
while(delay_time);
delay_time = 0x1FFFF;
}
int main()
{
*(CCM_CCGR1) = 0xFFFFFFFF; //开启GPIO1的时钟
*(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04) = 0x5;//设置PAD复用功能为GPIO
*(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04) = 0x1F838;//设置PAD属性
*(GPIO1_GDIR) = 0x10;//设置GPIO为输出模式
*(GPIO1_DR) = 0x0; //设置输出电平为低电平
while(1)
{
*(GPIO1_DR) = 0x0;
delay();
*(GPIO1_DR) = 1<<4;
delay();
}
return 0;
}
- 编译烧录
可以发现程序定位到片内RAM后,LED的闪烁速度明显变快。因为CPU读取RAM的速度比读取DDR的速度快的多。
二、 代码分析
对led.elf进行反汇编,生成反汇编文件imx6ull.lds。
查看上述程序的反汇编发现,在重定位函数copy_data执行之前,已经涉及到了片内RAM上的地址,但此时片内RAM上并没有任何程序,那为什么程序还能正常运行呢?
查看led.dis反汇编文件:
dis文件中左边的90000xx是链接地址,表示程序运行“应该位于这里”。但是实际上,我们一上电,boot ROM把程序放到0X87800000去了。所以一开始运行这些指令时,它们是位于DDR里的。
第9行的bl命令,并不是跳到0x90001c。这要根据当前的PC值来计算,在dis里写成0x90001c,这只是表示“如果程序从0x900000开始运行的话,第19行就会跳到0x90001c”。现在程序被boot ROM复制到0X87800000,从0X87800000开始运行,我们需要根据机器码来计算出实际跳转的地址。
pc = 当前地址 + 8 = 0X87800004+ 8 = 0X8780000C
offset=机器码“eb000004”里的bit[23:0]4=0x0044=0x10
新PC=PC + offset = 0X8780000C + 0x10 = 0X8780001C
在0X8780001C 这个位置,确实存有copy_data函数,所以:即使程序并不在链接地址0x900000上,它也可以运行。因为blx是相对跳转指令,它用的不是链接地址,它是“位置无关”的。使用“位置无关码”写出的代码,它可以在任何位置上运行,不一定要在链接地址 上运行。
下面我们来分析一下实际板子上电后,程序是如何执行的:
- 程序被boot ROM重定位到0X87800000,并从这个地址开始执行第一条指令:此时pc = 0X87800000+ 8 = 0X87800008。
- 执行到第2条指令“eb000004”时,根据上述算法,它跳到地址0X8780001C 去执行copy_data函数
- 在执行完copy_data和clean_bss函数后,片内RAM 0x900000上已经有程序了。
- 执行绝对跳转命令“ldr pc, =main”,它是一条伪指令,真实指令是“ldr pc, [pc, #4] ; 900018 <halt+0x8>”:
注意:
- 重定位之前,不可使用绝对地址
a) 不可访问全局类变量(全局变量或static修饰的局部变量)
b) 不可访问有初始值的数组(初始值放在rodata里,需要绝对地址来访问) - 重定位之后,使用ldr pc = xxx,跳转到绝对地址(runtime address)
- c)如果写的代码不是地址无关的,那么使用正点原子的imxdownload工具,只能将链接地址链接到0X87800000。
三、 总结
重定位:
编译器和汇编器生成从地址 0 开始的代码和数据段。链接器通过把每个符号定义与一个内存位置关联起来,从而重定位这些段,然后修改所有对这些符号的引用,使得它们指向这个内存位置 。
地址无关代码:
利用相对跳转指令,根据当前PC值自动计算出跳转地址,而不是利用的链接地址,所以这类代码都是地址无关的,它可以在任何位置上运行。
编译器还利用代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量,与代码段和数据段的绝对内存位置无关这个事实,制作位置无关的共享库。
扩展阅读:linux基础篇(二)——静态和动态链接