连接和重定位是嵌入式C中很重要的部分,对于这一块掌握的越精细越好。程序员
指令分为两种:web
在程序设计编译连接过程会给程序一个运行地址,并且必须给编译链接器指定这个地址,最后获得的二进制程序是和指定的连接地址相关的,这个地址叫作”连接地址”。
因此咱们在程序编译时其实就已经知道程序未来运行时的地址,这个地址叫作”运行地址”,运行地址和连接地址相关,可是不必定是同一个,程序执行时必须放在指定的连接地址下,不然不能运行,这些程序指令就是位置相关代码,咱们以前使用的ld连接器指定的“-Ttext 0x0”就是这个做用,意味着这个程序未来会放在0x0地址去运行。
可是有个别的指令能够和连接地址无关,这些代码在实际运行时放在哪里均可以正常运行,这些指令就是位置无关指令。
连接地址和运行地址可能相同也可能不一样,例如咱们的“-Ttext 0x0”指望在0x0地址运行,可是实际上咱们程序是在内存地址0xd0020010位置运行的,这两个地址看起来不一样,但其实是同一个,由于S5PV210内部作了映射,把SRAM0xd0020000位置映射到了0x0这个地址,由于咱们把代码烧写在了这个内存区域,可是大部分的指令都是位置相关代码,这就决定了运行地址和连接地址必须相同,不然必定出错。
在S5PV210中,三星推荐的启动方式,bootloader必须小于96K,而且必须大于16K,若是bootloader是80K,则启动过程为:svg
在Uboot启动过程当中,Uboot的大小是没有限制的,假设Uboot是200K,则启动过程为:函数
连接地址和运行地址有时候不能相同,并且不能所有使用位置无关指令,则须要重定位来解决该问题,oop
运行地址是在程序运行时决定的,在编译连接阶段没有权利也没有办法决定程序的运行地址。
连接地址是程序在编译连接阶段由-Ttext或者lds连接文件决定,程序在编译链接阶段,程序员指望程序运行在一个合适的地址,就把这个地址做为程序的连接地址。ui
程序段是程序在编译以后由源码获得的程序的组成部分,每一个二进制程序被分红了若干段,并对每一个段命名,段名分为两种:
- 内置段名:由编译链接器内部的命名规则对段的命名
- 自定义段名:由程序员本身命名的段名编码
内置段名通常有如下几种:spa
连接脚本指定了连接的地址和规则,整个连接操做都按照连接脚本指定的规则进行,程序员经过连接脚原本指挥连接器来处理.o目标代码的段,将其连接到合适的位置,从而造成可执行的程序,连接脚本中关键内容有:
- 段名:定位.o目标代码中的段
- 地址:做为连接地址的内存地址,设计
将指定的段名防止到指定的链接地址,就完成了该段的连接.
链接脚本示例:code
SECTIONS
{
. = 0xd0024000; // .表明当期位置
.text : {
start.o
* (.text)
}
.data : {
* (.data)
}
bss_start = .;
.bss : {
* (.bss)
}
bss_end = .;
}
链接脚本由一个或者多个SECTIONS{}组成:
. = 0xd0024000;
表示将当前位置的地址设置为0xd0024000,.text : {}
表示该段为代码段,下面的.data : {},.bss : {}等同start.o
表示start.o在前面,因此start.S会被首先执行*(.text)
匹配模式,表示剩余的代码都属于代码段并依次排列bss_start = .;
表示将bss_start的值设置为当前地址,bss_end等同,这两个符号之因此定义在这里是为了能够在别的文件中引用,以知道bss段的起止地址目标:将代码从0xd0020010重定位到0xd0024000,
代码原本是从0xd0020010地址开始运行的,由于BL1从这里开始运行,可是由于特殊缘由,咱们又须要代码从0xd0024000开始执行,这时候就须要重定位,在某些状况下,重定位是必须的,例如Uboot.
长跳转也就是一个跳转,在ARM中经常使用分支指令(b,bl)来完成短跳转,跳转一般是指令经过给PC寄存器赋值,从而完成代码跳转执行,长跳转和普通跳转的区别在于长跳转的目标地址和当前地址的距离较大,范围较宽
代码重定位以后,咱们须要去代码被拷贝的目的地的代码区域执行,这个 目标地址一般会距离咱们当前地址较远,因此才须要使用重定位,长跳转示例:ldr pc,=led_blink
,这样咱们就会跳转到目的地的led_blink函数来执行了,这里不能使用短跳转(b,bl),短跳转会跳转到运行地址附近的led_blink函数,只有使用长跳转,才能跳转到连接地址上的led_blink
重定位其实就是,在运行地址位置执行一段位置无关代码,这段代码将整个程序拷贝到连接地址上,而后使用一个长跳转,跳转到连接地址的对应函数上去,在连接地址上继续执行,从而实现重定位以后的无缝链接。
重定位代码以下:
// 重定位开始
adr r0,_start // adr 用于加载_start运行地址到r0中
ldr r1,=_start // ldr 加载_start的连接地址到r1中
// 获取BSS段起始地址,等于重定位代码的结束地址
ldr r2,=bss_start // bss_start是在lds连接脚本中指定的
cmp r0,r1 // 比较_start的运行地址和连接地址
beq clean_bss // 若是相等就直接跳转到clean_bss函数
// 若是上一句不相等,则进入代码拷贝阶段
copy_loop:
ldr r3,[r0],#4 // 拷贝源
str r3,[r1],#4 // 存储到目的,长度是bss_start - _start,代码段+数据段的长度
cmp r1,r2 // 比较拷贝是否完成,也就是到没到重定位代码的结束地址
bne copy_loop // 拷贝没有完成就继续循环
// 若是拷贝完成,进入clean_bss函数,该函数用于清理bss
clean_bss:
ldr r0,bss_start // 为了知足C语言的要求,须要清理BSS的
ldr r1,bss_end // 通常是不须要手动清理的,这里咱们作了重定位,因此须要手动清理
cmp r0,r1 // 比较bss段起始地址和结束地址
beq run_on_dram // 若是相等则直接进入run_on_dram函数
mov r2,#0 // 不相等的话就把r2设置为0
clear_loop: // 开始清理bss
str r2,[r0],#4 // 把r2赋值给r0而后r0+=4
cmp r0,r1
bne clear_loop // 循环,直到bss_end
// 从这一句开始就是位置相关代码了,必须在指定的链接地址开始执行
// 因此在这一句以前,必须保证代码已经拷贝完毕
run_on_dram:
// 长跳转到led_blink开始第二阶段
ldr pc,=led_blink // 长跳转
// 重定位完毕
重定位放置在icache开关以后。
SDRAM是DRAM的一种,前面的S表明Sync,表示具备可同步性,常见的DDR就是SDRAM的一种,SDRAM属于动态内存,须要进行初始化以后才能使用,这点和SRAM是有很大的不一样。
SDRAM属于SOC外部外设,经过地址总线和数据总线和SOC通讯。
SDRAM使用以前必需要初始化,S5PV210共有两个内存端口,分别是DRAM0和DRAM1,地址范围分别是:
能够看到整个S5PV210最多支持1.5G内存,X210开发板上共有512MB内存,DRAM0/1上各有256MB,因此能够获得X210上内存地址为:
DDR初始化完毕以后,这些地址都是可用的,其余地址则不可用。
每一个DDR端口都有三类总线组成,分别是地址总线ADDR14个,控制总线,数据总线32个。
X210开发板共使用了4片内存,每片128MB,数据总线为16bit,经过两个内存之间进行并联,从而实现了数据总线32bit。X210上的每片内存分为8个Bank,CPU经过DDR端口中的BA0-BA2来选择Bank,每一个Bank有128Mbit的范围,经过行地址和列地址进行寻址,寻址范围是2的24次方(16MB = 128Mbit)。
在进行重定位以前,先去执行SDRAM初始化,能够直接跳转到SDRAM的初始化函数:
// 初始化SDRAM
bl sdram_asm_init // bl短跳转到sdram_asm_init函数进行SDRAM初始化
SDRAM的初始化须要先初始化DRAM0,而后初始化DRAM1,步骤在手册的1.2.1.3
DRAM初始化以后可使用DRAM进行重定位,步骤和以前彻底一致。