重定位
启动过程
前面已知,imx6会内部bootRom上电后会从启动设备(SD卡、EMMC等)读取镜像到DDR,并运行,过程如下:

例如,如果用户的应用程序链接地址为0x80100000 ,makefile会根据链接地址打包出三个镜像文件:
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
$(OBJCOPY) -O binary -S $(TARGET).elf $@
$(OBJDUMP) -D -m arm $(TARGET).elf > $(TARGET).dis
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d $(TARGET).bin $(TARGET).imx
dd if=/dev/zero of=1k.bin bs=1024 count=1
cat 1k.bin $(TARGET).imx > $(TARGET).img
.bin:原始的用户程序;.imx:开头就是IVT;通过100ask_imx6ull_flashing_tool工具(需设置板子USB启动),可以将
.imx直接下载到DDR运行或者下载到EMMC、TF卡上。注意:通过USB下载方式,可以烧写程序到EMMC、TF卡上,但是并非“直接烧写”。它的过程如下:
通过USB下载u-boot到内存;
通过USB下载用户程序到内存;
通过USB发送命令运行u-boot;
用u-boot烧写把内存中的用户程序烧写到EMMC、TF卡上。
.img:开头是1024字节的0值数据,后面才紧跟IVT。.img文件已经在前面加上Initial Load Region部分,所以是一个完整的镜像文件,我们可以直接用win32diskimager把.img文件烧写到SD卡上
段概念
程序的段包括
代码段(.text):存放代码指令
只读数据段(.rodata):存放有初始值并且const修饰的全局类变量(全局变量或static修饰的局部变量)
数据段(.data):存放有初始值的全局类变量
栈:函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间,后进先出,先进入的最后出来,只在Ram区;
堆:使用malloc 动态分配的变量属于堆空间,只在Ram区。
零初始化段(.bss):存放没有初始值或初始值为0的全局类变量注释段(.comment):存放注释
注意:bss段和注释段不保存在bin/elf文件中

链接脚本
在makefile中会指定程序的链接脚本如下
$(TARGET).bin : $(OBJS)
$(LD) -Timx6ul.lds -o $(TARGET).elf $^
imx6ul.lds文件分析
SECTIONS{
. = 0x80100000;
. = ALIGN(4);
.text :
{
obj/start.o
*(.text)
}
. = ALIGN(4);
.rodata :
{
*(.rodata*)
}
. = ALIGN(4);
.data :
{
*(.data)
}
. = ALIGN(4);
__bss_start = .;
.bss :
{
*(.bss)
*(COMMON)
}
__bss_end = .;
}
清除bss段
之前提到过bin文件中并不会保存bss段的值,因为这些值都是0,保存这些值没有意义并会使得bin文件臃肿。当程序运行涉及到bss段上的数据时,CPU会从bss段对应的内存地址去读取对应的值,为了确保从这段内存地址上读取到的bss段数值为0,在程序运行前需要将这一段内存地址上的数据清零,即清除bss段。如果不清除,那么程序运行起来后,这个值是随机的。
在汇编中清除bss段示例:
.text
.global _start
_start:
/* 设置栈 */
ldr sp,=0x80200000
/* 清除bss段 */
bl clean_bss
/* 跳转到主函数 */
bl main
halt:
b halt
clean_bss:
ldr r1, =__bss_start //将链接脚本变量__bss_start变量保存于r1
ldr r2, =__bss_end //将链接脚本变量__bss_end变量保存于r2
mov r3, #0
clean:
strb r3, [r1] //将当前地址下的数据清零
add r1, r1, #1 //将r1内存储的地址+1
cmp r1, r2 //相等:清零操作结束;否则继续执行clean函数清零bss段
bne clean
mov pc, lr
重定位引入
有些arm芯片(比如S3C2440),上电后会从Nand或者Norflash上先读取4k的代码到DDR中运行,在这4k代码中,完成把Nand或者Norflash上的所有代码拷贝到DDR中,然后跳转到指定的链接地址去运行代码。而imx6ull内部自带的bootRom完成了这部分功能,板子上电后Boot Rom会将映像文件从启动设备(TF卡、eMMC)自动拷贝到DDR3内存上。上述拷贝代码的过程就是重定位。
思考:如果makefile中指定的程序的入口地址和链接文件的地址不一致,如下所示,会出现什么情况?
# 告诉bootRom程序的入口地址为0x8010 0000
./tools/mkimage -n ./tools/imximage.cfg.cfgtmp -T imximage -e 0x80100000 -d $(TARGET).bin $(TARGET).imx
注意:imximage.cfg.cfgtmp文件原始所在位置为uboot的board/freescale/mx6ullevk目录下,如下:
ebf_6ull_uboot$ cd ./board/freescale/mx6ullevk/
ebf_6ull_uboot/board/freescale/mx6ullevk$ ls
built-in.o imximage.cfg imximage.cfg.cfgtmp imximage_lpddr2.cfg Kconfig MAINTAINERS Makefile mx6ullevk.c mx6ullevk.o mx6ullevk.su plugin.S README
SECTIONS{
. = 0x80101000; # 链接地址为0x8010 1000 和上面的指定的入口地址0x8010 0000不一样
. = ALIGN(4);
.text :
{
obj/start.o
*(.text)
}
这要分两种情况:

如果启动文件使用
bl main相对跳转到main函数,程序还会从DDR3的0x80100000处运行,即a点重定位前:程序正常跑到main函数,之后在main函数里面如果调用
.data里面的变量实际是访问链接地址上的.data段所在的位置即d点位置,那么很明显,d点位置内容不确定,这个时候程序就访问不到想要的数据;重定位后:程序正常跑到main函数,之后在main函数里面如果调用
.data里面的变量实际是访问链接地址上的.data段所在的位置即d点位置,而此时d位置的.data段我们已经在bl main前把整个bin拷贝到c点,所以能正常使用。
如果启动文件使用
ldr pc, =main绝对跳转到main函数,程序会从链接地址DDR3的0x80101000处运行,即c点。重定位前:c点内容什么都没有,可想而知接下来就是死机。
重点位后:c点我们在
.S启动文件中ldr pc, =main执行前,将整个bin文件重新copy到c点,所以程序是能正常运行的。前提是在ldr pc, =main前面的代码都是位置无关的代码指令,比如不能使用初始化的全局变量,因为初始化的全局变量位于链接地址d处,拷贝前d处不知道什么内容。
这里值得注意的是,上面重点位是把整个bin文件(Text+rodata+data)段整体拷贝到链接地址处,其实针对imx6ull没有这么做的必要,我们只需保证makefile告诉bootRom的入口地址和程序里面链接文件.lds的链接地址一致即可,bootRom会自动把我们的程序整体拷贝到DDR中。
费这么大劲讲解重定位,是因为有些arm芯片,只会拷贝前4k数据到DDR中,在这4k里面,需要用户自己把对应Text+rodata+data拷贝到指定的链接地址去。故而这里大费周章的将重定位。
重定位.data段到芯片内部RAM 汇编代码实现
.data段存放初始化全局变量,我们知道RAM的访问速度肯定比DDR3的访问速度快,如果把.data段重定位到RAM区,那么访问速度会加快很多,下面就做个重定位.data到芯片内部RAM的试验。
参考imx6ull芯片手册得到片内RAM的地址为:0x900000 ~ 0x91FFFF。将.data段重定位后的地址设置为0x900000


重定位后程序在芯片内存中分布如下:

源码分析
参考【04.relocate_data_asm】代码
imx6ul.lds
SECTIONS{
. = 0x80100000;
. = ALIGN(4);
.text :
{
obj/start.o
*(.text)
}
. = ALIGN(4);
.rodata :
{
*(.rodata)
}
. = ALIGN(4);
data_load_addr = .;
.data 0x900000 : AT(data_load_addr) #重定位data段到0x900000
{
data_start = . ;
*(.data)
data_end = . ;
}
. = ALIGN(4);
__bss_start = .;
.bss :
{
*(.bss)
*(COMMON)
}
__bss_end = .;
}
start.S
/* ~~~~~~~~~~~~~省略N行~~~~~~~~~~~~~*/
ldr sp, =0x80200000 /* .lds中设置链接地址为0x8010 0000,设置栈顶0x8020 0000 故预留1M的空间 */
/* 重定位data段 */
bl copy_data
/* 清除bss段 */
bl clean_bss /* 切记:代码里面必须有bss段(即存在未初始化的全局变量),否则这里只要调用就死机*/
/* 跳转到主函数 */
ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */
halt:
b halt
copy_data:
/* 重定位data段 */
ldr r1, =data_load_addr /* data段的加载地址 (0x8010....) */
ldr r2, =data_start /* data段重定位地址, 0x900000 */
ldr r3, =data_end /* data段结束地址(重定位后地址 0x90....) */
cpy:
ldr r4, [r1] /* 从r1读到r4 */
str r4, [r2] /* r4存放到r2 */
add r1, r1, #4 /* r1+1 */
add r2, r2, #4 /* r2+1 */
cmp r2, r3 /* r2 r3比较 */
bne cpy /* 如果不等则继续拷贝 */
mov pc, lr
clean_bss:
/* 清除bss段 */
ldr r1, =__bss_start
ldr r2, =__bss_end
mov r3, #0
clean:
str r3, [r1]
add r1, r1, #4
cmp r1, r2
bne clean
mov pc, lr
main.c
#include "bsp_clk.h"
#include "bsp_delay.h"
#include "bsp_led_rgb.h"
#include "bsp_uart.h"
#include "stdio.h"
uint32_t a = 0; // bss段
uint32_t b = 1; // data段
uint32_t c; // bss 段
const uint32_t d=1; // rodata 段
int main(void)
{
clk_enable(); /* 使能所有的时钟 */
rgb_led_init(); /* 初始化led */
uart_init(); /* 初始化串口,波特率115200 */
printf("relocate data asm demo\r\n");
while(1)
{
printf("globle var b=%d addr=0x%x\r\n",b,&b); // 打印data段数据 a的值和 地址
LED_RGB_RED_ON();
LED_RGB_BLUE_OFF();
LED_RGB_GREE_OFF();
delay(1000);
LED_RGB_BLUE_ON();
LED_RGB_RED_OFF();
LED_RGB_GREE_OFF();
delay(1000);
LED_RGB_GREE_ON();
LED_RGB_BLUE_OFF();
LED_RGB_RED_OFF();
delay(1000);
}
return 0;
}
实验现象
如果我们屏蔽掉start.S文件的bl copy_data代码,即链接文件指定了data段的链接地址为0x900000,但是我们并没有拷贝data段到该地址,所以0x900000地址数据肯定是随机的,所以代码运行效果如下:

如果不屏蔽bl copy_data,那么程序会在跳转到main前,把data段数据拷贝到0x900000处,所以,运行结果是正常的如下:

重定位.data段到芯片内部RAM C代码实现 汇编传入参数
上面的清除bss段和拷贝data段都是汇编语言实现,现在我们实现C语言版本,代码参考【05.relocate_data_c】
源码分析
start.S
/* ~~~~~~~~~~~~~省略N行~~~~~~~~~~~~~*/
/* 重定位data段 */
ldr r0, =data_load_addr /* data段的加载地址 (0x8010....) */
ldr r1, =data_start /* data段重定位地址, 0x900000 */
ldr r2, =data_end /* data段结束地址(重定位后地址 0x90....) */
sub r2, r2, r1 /* r2的值为data段的长度 */
bl copy_data /* 跳转到函数copy_data并将r0,r1,r2作为函数参数传入 */
/* 清除bss段 */
ldr r0, =__bss_start
ldr r1, =__bss_end
bl clean_bss /* 跳转到函数clean_bss并将r0, r1作为函数参数传入*/
/* 跳转到主函数 */
ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */
halt:
b halt
init.c
/**********************************************************************
* 函数名称: copy_data
* 功能描述: 将[地址src]上的的数据拷贝到[地址dest] ,拷贝的数据长度为len
* 输入参数: src, dest, len
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void copy_data (volatile unsigned int *src, volatile unsigned int *dest, unsigned int len) /* src, dest, len */
{
unsigned int i = 0;
while (i < len)
{
*dest++ = *src++;
i += 4;
}
}
/**********************************************************************
* 函数名称: clean_bss
* 功能描述: 清除.bss段上的数据
* 输入参数: start, end
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void clean_bss (volatile unsigned int *start, volatile unsigned int *end) /* start, end */
{
while (start <= end)
{
*start++ = 0;
}
}
实验现象

重定位.data段到芯片内部RAM C代码实现改进 直接引用链接脚本变量
上面我们在清除bss段和copy数据段的时候都是在汇编中传入参数,现在我们实现直接在C语言里面调用链接文件变量。参考代码【06.relocate_data_c_fix】
源码分析
start.S
.text //代码段
.align 4 //设置字节对齐
.global _start //定义全局变量
_start: //程序的开始
b reset //跳转到reset标号处
reset:
mrc p15, 0, r0, c1, c0, 0 /*读取CP15系统控制寄存器 */
bic r0, r0, #(0x1 << 12) /* 清除第12位(I位)禁用 I Cache */
bic r0, r0, #(0x1 << 2) /* 清除第 2位(C位)禁用 D Cache */
bic r0, r0, #0x2 /* 清除第 1位(A位)禁止严格对齐 */
bic r0, r0, #(0x1 << 11) /* 清除第11位(Z位)分支预测 */
bic r0, r0, #0x1 /* 清除第 0位(M位)禁用 MMU */
mcr p15, 0, r0, c1, c0, 0 /* 将修改后的值写回CP15寄存器 */
ldr sp, =0x80200000 /* .lds中设置链接地址为0x8010 0000,设置栈顶0x8020 0000 故预留1M的空间 */
bl copy_data /* 跳转到C函数copy_data并将r0,r1,r2作为函数参数传入 */
bl clean_bss /* 跳转到C函数clean_bss并将r0, r1作为函数参数传入*/
/* 跳转到主函数 */
ldr pc, =main /* 绝对跳转,程序在片内RAM中执行 */
halt:
b halt
init.c
/**********************************************************************
* 函数名称: copy_data
* 功能描述: 将.data段数据从DDR3内存重定位到片内RAM
* 输入参数: 无
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
void copy_data (void)
{
/* 从链接脚本中获得参数 data_load_addr, data_start, data_end */
extern int data_load_addr, data_start, data_end;
volatile unsigned int *dest = (volatile unsigned int *)&data_start;
volatile unsigned int *end = (volatile unsigned int *)&data_end;
volatile unsigned int *src = (volatile unsigned int *)&data_load_addr;
/* 重定位数据 */
while (dest < end)
{
*dest++ = *src++;
}
}
/**********************************************************************
* 函数名称: clean_bss
* 功能描述: 清除.bss段
* 输入参数: start, end
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
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;
}
}
重定位所有段到芯片内部RAM C代码实现
下面实现把所有段都重定位到内部ram中,参考代码【07.relocate_data_all】
代码分析
在【06.relocate_data_c_fix】基础上我们修改链接文件和init.c文件
imx6ull.lds
SECTIONS{
_load_addr= 0x80100000;
. = 0x900000;
. = ALIGN(4);
.text :
{
obj/start.o
*(.text)
}
. = ALIGN(4);
.rodata :
{
*(.rodata)
}
. = ALIGN(4);
.data :
{
*(.data)
}
. = ALIGN(4);
__bss_start = .;
.bss :
{
*(.bss)
*(COMMON)
}
__bss_end = .;
}
init.c
/**********************************************************************
* 函数名称: copy_data
* 功能描述: 将整个程序(.text, .rodata, .data)从DDR3重定位到片内RAM
* 输入参数: 无
* 输出参数: 无
* 返 回 值:
***********************************************************************/
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++;
}
}
/**********************************************************************
* 函数名称: clean_bss
* 功能描述: 清除.bss段
* 输入参数: start, end
* 输出参数: 无
* 返 回 值: 无
***********************************************************************/
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;
}
}
实验现象
