重定位

启动过程

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

../../_images/image-20210810085245106.png

例如,如果用户的应用程序链接地址为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
  1. .bin:原始的用户程序;

  2. .imx:开头就是IVT;

    通过100ask_imx6ull_flashing_tool工具(需设置板子USB启动),可以将.imx直接下载到DDR运行或者下载到EMMC、TF卡上。

    注意:通过USB下载方式,可以烧写程序到EMMC、TF卡上,但是并非“直接烧写”。它的过程如下:

    1. 通过USB下载u-boot到内存;

    2. 通过USB下载用户程序到内存;

    3. 通过USB发送命令运行u-boot;

    4. 用u-boot烧写把内存中的用户程序烧写到EMMC、TF卡上。

  3. .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文件中

../../_images/image-20210810091548247.png

链接脚本

在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)
	}

这要分两种情况:

../../_images/image-20210810162003452.png

  1. 如果启动文件使用bl main相对跳转到main函数,程序还会从DDR3的0x80100000处运行,即a点

    • 重定位前:程序正常跑到main函数,之后在main函数里面如果调用.data里面的变量实际是访问链接地址上的.data段所在的位置即d点位置,那么很明显,d点位置内容不确定,这个时候程序就访问不到想要的数据;

    • 重定位后:程序正常跑到main函数,之后在main函数里面如果调用.data里面的变量实际是访问链接地址上的.data段所在的位置即d点位置,而此时d位置的.data段我们已经在bl main前把整个bin拷贝到c点,所以能正常使用。

  2. 如果启动文件使用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

../../_images/image-20210810094348984.png

../../_images/image-20210810094639413.png

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

../../_images/image-20210810171815637.png

源码分析

参考【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地址数据肯定是随机的,所以代码运行效果如下:

../../_images/image-20210814152525007.png

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

../../_images/image-20210814152607211.png

重定位.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;
	}
}
		  			 		  						  					  				 	   		  	  	 	  

实验现象

../../_images/image-20210814152607211.png

重定位.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;
	}
}
		  			 		  						  					  				 	   		  	  	 	  

实验现象

../../_images/image-20210814161911074.png