美文网首页ARM 单片开发
ARM---代码重定位

ARM---代码重定位

作者: ZebraWei | 来源:发表于2018-09-24 22:15 被阅读183次

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

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

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


    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黑洞。


    怎么解决数据段和代码段出现巨大空洞问题?
    • 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---代码重定位

        本文链接:https://www.haomeiwen.com/subject/wiwhoftx.html