ARM---代码重定位

版权声明:本文为小斑马学习文章,技术来源于韦东山著作,转载请注明出处!

一、段的概念__重定位的引入

2440开发版:芯片内部有CPU、内存控制器、4K的配对内存SRAM。还有NandFlash控制器。NandFlash控制器可以直接连接NandFlash。

ARM---代码重定位_第1张图片

CPU 能直接到达SDRAM Norflash SRAM Nand Flash控制器 但不能直接到达NandFlash。如果直接把程序烧写到NandFlash CPU不能直接运行该程序。那为什么我们还能设置NANDFlash启动呢? 这里有个机制 1.NandFalsh启动时,硬件将前4K复制到SRAM中。2.CPU从0运行,0对应的就是SRAM。
问题:如果在NandFlash程序超过4K怎么办? 前4K的代码 需要把整个程序读出来 放到SDRAM。这就是重定位, 即重新确定程序的地址。

.使用NorFlash启动时,CPU看到的0地址在NorFlash上面,SRAM 配对内存的基地址就是0x40000000 NandFlash地址0, NandFlash不可以访问的。
NorFlash的特点:像内存一样读,但不能像内存可以直接写。

比如 mov R0,#0
LRR R1,[R0]
STR R1,[R0]该指令无效的。
问题:如果程序中含有要写的变量==全局变量/静态变量
他们在bin中,写在NORFlash中,直接修改变量会无效。所以需要重定位放到SDRAM中。

#include "s3c2440_soc.h"
#include "uart.h"
#include "init.h"

char g_Char = 'A';
const char g_Char2 = 'B';
int g_A = 0;
int g_B;

int main(void)
{
 uart0_init();

while (1)
{
    putchar(g_Char);
    g_Char++;         /* nor启动时, 此代码无效 */
    delay(1000000);
}


return 0;
}

对于NORFlash不能直接的写。

程序包含: 代码段 数据段(全局变量) data rodate(const全局变量) bss(初值为0 无初值的全局变量)commen(注释)后面的两个不保存在bin的文件中。

二、链接脚本的引入与简单测试

因为NORFlash不能写入数据,在设置修改MakeFile将数据段写入到SDRAM中,但出现bin文件超大的问题有800多M。问题的根源在于代码段和数据段出现巨大的间隔。成为HOLO黑洞。

ARM---代码重定位_第2张图片

怎么解决数据段和代码段出现巨大空洞问题?

  • 1 将bin文件里面的代码段和数据段拼接在一起,烧写在NORFlash里面去,在运行时,将数据段重定位到SDRAM中 地址:0x30000000。
  • 2 将bin文件里面的代码段写在一个地址中,数据段写到另外一个地址中,烧写在NORFlash里面去,在运行时,将数据段和数据段重定位到SDRAM中 地址:0x30000000。
    如何将数据段和代码段拼接在一起呢?
    这要使用到链接脚步。

链接脚本的格式:

 SECTIONS {
     .text   0  : { *(.text) } //先发所有的.text代码段
     .rodata  : { *(.rodata) } //紧接着放 所有的.rodata只读的数据段
     .data 0x30000000 : AT(0x800) { *(.data) }  // 依次是所有文件的数据段 数据放在0x30000000 要加AT
     .bss  : { *(.bss) *(.COMMON) } 
 }

设置链接脚本后,运行时!要将数据段重定位到0x30000000中去。

bl sdram_init   //初始化SDRAM

/* 重定位data段 */
mov r1, #0x800
ldr r0, [r1]
mov r1, #0x30000000
str r0, [r1]

bl main 

通用的重定位代码:解决多个全局变量和未知数据段存储地址的情况下

SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data); //获取数据段的存储地址
      data_start = . ;  //.即是当前位置 0x30000000
     *(.data) 
      data_end = . ; //data_end的值 有data的大小来决定
   }
  .bss  : { *(.bss) *(.COMMON) }
}


/* 重定位data段 */
ldr r1, =data_load_addr  /* data段在bin文件中的地址, 加载地址 */
ldr r2, =data_start      /* data段在重定位地址, 运行时的地址 */
ldr r3, =data_end        /* data段结束地址 */

cpy:
   ldrb r4, [r1]
   strb r4, [r2]
   add r1, r1, #1
   add r2, r2, #1
   cmp r2, r3
   bne cpy
三、 链接脚本的解析

SECTIONS {
...
secname start BLOCK(align)(NOLOAD) : AT (ldadr)
{ contents } >region:phdr =fill
...
}
secname:段名:
start:起始地址:运行时地址runtime addr,重定位地址 relocate addr

AT(ldadr) Load Addr:加载地址 不写时,LoadAddr = runtime addr 如果没有加AT 它的的加载地址就等于链接时的起始地址。

使用链接脚本时
arm-linux-ld -T sdram.lds start.o led.o uart.o init.o main.o -o sdram.elf

把.o文件生成了.elf格式的程序。elf格式程序含有地址信息。再将elf格式的文件生成bin文件 bin文件里面不含有地址信息。
arm-linux-objcopy -O binary -- sdram.elf sdram.bin

elf格式的程序:1.链接得到elf文件,含有地址信息(LoadAddr)2.使用加载器将elf程序读入内存(读到LoadAddr)调试工具。对于APP 加载器也是APP。
3.运行程序 4.如果LoadAddr != RuntimeAddr 程序本身要重定位。

核心:程序运行时,应该位于RuntimeAddr 也可以称为relocate Addr 或者链接地址
对于裸版 1.elf--> 2.硬件机制启动 3.如果bin所在地址 != runtime Addr 程序本身实现重定位

SECTIONS {
   .text   0  : { *(.text) }  //LoadAddr = Runtime Addr  =0 不需要重定位
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) LoadAddr = 0x800 RuntimeAddr = 0x30000000
   { 
      data_load_addr = LOADADDR(.data); //获取数据段的存储地址
      data_start = . ;  //.即是当前位置 0x30000000
      *(.data) 
      data_end = . ; //data_end的值 有data的大小来决定
   }
 .bss  : { *(.bss) *(.COMMON) } runtime = 0x300000000
}

bin文件 left文件都不存bss段 bss段(未初始化的变量和注释) 程序运行时 把bss段对应的空间清零。

 SECTIONS {
   .text   0  : { *(.text) }
   .rodata  : { *(.rodata) }
   .data 0x30000000 : AT(0x800) 
   { 
      data_load_addr = LOADADDR(.data);
      data_start = . ;
      *(.data) 
      data_end = . ;
   }

  bss_start = .;
 .bss  : { *(.bss) *(.COMMON) }
  bss_end = .;
}

/* 清除BSS段 */
ldr r1, = bss_start
ldr r2, = bss_end
mov r3, #0
clean:
strb r3, [r1]
add r1, r1, #1
cmp r1, r2
bne clean
四、拷贝代码和链接脚本的改进

拷贝代码:把数据和代码从NorFlash或者NandFlash中复制到SDRAM去。

cpy:
   ldrb r4, [r1]  //从Flash中读取出一个字节
   strb r4, [r2]  //写到SDRaAM中
   add r1, r1, #1
   add r2, r2, #1
   cmp r2, r3
   bne cpy

bl main

这种效率非常的低。
原因:在2440芯片中有CPU和内存控制器。外接有32bit的SDRAM 和 16bit的NORFlash。如果使用ldrb从NORFalsh中读到数据,使用strb写数据到SDRAM。如果要复制16字节,ldrb要执行16次,访问NORFlash要16次。strb要执行16次,访问SDRAM16次。硬件访问要32次,效率非常的低。

CPU写数据给SDRAM中,先将地址和数据发送到内存控制器,内存控制把32位的数据和数据屏蔽信号DQM 发送给SDRAM。
改进的方法:使用ldr从NORFlash读取 用str写数据到SDRAM去。读取和写入各执行四次。其从NORFlash读取数据时访问硬件为8次,写数据到SDRAM中访问的硬件为4次。总共12次。效率提高。

cpy:
    ldr r4, [r1]
    str r4, [r2]
    add r1, r1, #4
    add r2, r2, #4
    cmp r2, r3
    ble cpy


/* 清除BSS段 */
   ldr r1, =bss_start
   ldr r2, =bss_end
   mov r3, #0
clean:
   str r3, [r1]
   add r1, r1, #4
   cmp r1, r2
   ble clean

bss 清零的时候,要向四取整。否则会出现地址相同,把其他变量的值也清零了。

SECTIONS {
    .text   0  : { *(.text) }
    .rodata  : { *(.rodata) }
    .data 0x30000000 : AT(0x800) 
    { 
        data_load_addr = LOADADDR(.data);
        . = ALIGN(4);
        data_start = . ;
       *(.data) 
        data_end = . ;
    }

. = ALIGN(4);
bss_start = .;
.bss  : { *(.bss) *(.COMMON) }
bss_end = .;
}

你可能感兴趣的:(ARM---代码重定位)