• GNU LD之一LMA和VMA


    MIPS 处理器存储器结构

    项目当中使用的是一颗MIPS CPU,存储空间是标准的MIPS内存分配,内存被划分为几个部分,概括如下:

    Boot room, boot code存储空间;

    iram, code 存储空间;

    dram,data存储空间;

    也就是说code和data有各自独立的存储空间,分开放置。

    我们平常用gcc和ld生成一个可执行文件的时候,例如在命令行输入gcc -o test test.c,生成的可执行文件是一个文件哦,也就是说code和data都在一份可执行文件里面。我们把这份可执行文件烧写到flash里面,然后cpu再从flash里面取指令执行。

    可是在前面,我们明明规划了code和data都各自独立的存储空间啊。有心人会问,是的啊,你讲的没错,可是这是怎么一回事呢?

    两种处理器架构的区别

    好,那我们回到开始,先从为什么会有code和data各自规划一块存储空间的概念。这其实得从“冯诺依曼结构”和“哈佛”结构说起。

    “冯诺依曼结构”,是指程序和数据存储空间并不是分开的,而是在一块存储器里面,所以程序和数据的访问位宽是相等的。

    “哈佛结构”,是指程序和数据存储空间是分开的,各自有一块存储器,所以程序和数据的访问位宽可以不相等。

    现在的处理器基本上都是属于上面2种架构,例如x86, arm, mips等。

    说完这个,一切都清楚了。对了,我所用的MIPS恰好是“哈佛结构”的啰!

    LMA和VMA

    那么“哈佛结构”的处理器,明明生成的可执行文件,也就是通常所说的bin文件,只有一份啊,所以程序和数据都在同一份bin文件的,例如test.bin。我们将test.bin烧入到flash之后。

    在哈佛架构的处理器上,这份可执行文件是怎么执行的呢?

    好问题。

    我们知道对于一个C程序,在其编译链接时,代码会放在text段,常量是存储在rodata段,初始化的全局变量或者初始化的静态变量的值会放在data段,未初始化的全局变量或者静态变量会放在bss段。

    而字符串指针变量例如char *string = "abcdef",字符串"abcdef"是存储在rodata段,string这个指针变量的值为字符串"abcdef"的地址,也就是rodata段中的某个地址, string变量是存储在data段(data段中某个“单元房间”里面放置的是string指针变量的值,也就是字符串常量"abcdef"的地址)。

    text段和rodata段,都是存放在room中,而data和bss开始是存放在bin file中,但在C程序的main函数开始跑之前,是需要被搬运到RAM中的。

    所以我们需要在bootloader中,用汇编语言写一段代码,将bin file中的data段copy到RAM中,bss段不必搬,只需要将bss段在RAM中的地址区间清零就可以了。

    然后再将sp指针指向RAM的最高地址,不过SP指针一般有对齐的要求,例如8byte对齐等。

    在这个搬运的过程中,就会涉及到LMA和VMA的知识了。

    LMA就是load address,也就是加载地址;

    VMA就是virtual address,也就是运行地址。

    具体是什么意思。例如我们刚才讲到的test.bin,那么程序和数据都会按顺序存储在里面啊,顺序请参考http://www.cnblogs.com/ironx/p/4954845.html中的“目标文件在其存储器映像文件中的布局”。

    在那篇文章的对应章节中,描述的就是LMA也就是程序和数据在bin文件中的存储地址,VMA也就是data和bss段在RAM中的运行地址。

    而我们在bootloader中的汇编代码里面,需要将data段copy到RAM中,并清零bss段。

    这个时候,汇编代码会把data段以及sdata段从其LMA处,copy到VMA处,也就是从bin文件的存储地址复制到RAM中的运行地址处。

    一个典型的bootloader搬运代码如下所示:

     1   .extern    _fbss
     2   .extern    _ebss
     3   .extern     main
     4   .section ".boot","ax"
     5   .set noreorder
     6   .set noat 
     7   .globl  _start
     8   .ent    _start    
     9 
    10 #define DRAM_BASE    0xa0000000
    11 #define DRAM_SIZE    0x00001800
    12 
    13 #boot start
    14 _start:
    15     li    s0, 0xffff
    16     li    s1, 0xffff
    17     li    v0, 0xffff
    18     li    v1, 0xffff
    19     li    a0, 0xffff
    20         li      a1, 0xffff
    21     li    a2, 0xffff
    22     li    a3, 0xffff
    23         nop
    24 #copy .data to dram
    25 _copy_data:
    26     li    s0, _fdata
    27     li    s1, _edata
    28     li    v0, DRAM_BASE
    29 1:    lw      v1, 0(s0)
    30     sw    v1, 0(v0)
    31     addiu    s0, 4
    32     addiu    v0, 4
    33     blt    s0, s1, 1b        
    34 #clear bss               
    35 clear_bss:
    36     li    s0, _fbss
    37     li    s1, _ebss
    38     li    v0, 0
    39 1:    sw    v0, 0(s0)
    40     addiu   s0, 4
    41     blt    s0, s1, 1b
    42     nop
    43 clr_num:
    44            li      v0, 0xa0001800
    45            move     sp, v0
    46     jal    main
    47     nop
    48 loop:
    49     la    v0, loop
    50     j       v0    
    51     nop
    52          .set    reorder  
    53           .end    _start

    既然bootloader会用到LMA和VMA,那么LMA和VMA在哪里定义呢,就是在ld脚本中啦,ld脚本就规定程序和数据在bin文件里面是什么存储的,以及运行时在rom和ram中是怎么存储的文件。

    我的ld脚本如下:

     1 OUTPUT_FORMAT("elf32-bigmips","elf32-bigmips","elf32-littlemips")
     2 ENTRY(_start)
     3 MEMORY
     4 {
     5     room : ORIGIN = 0xbfc00000, LENGTH = 0x1000
     6     iram : ORIGIN = 0x90000000, LENGTH = 0x4000
     7     dram : ORIGIN = 0xa0000000, LENGTH = 0x1800
     8     debugsram : ORIGIN = 0xb9000000, LENGTH = 0x10000
     9 }
    10 SECTIONS
    11 {
    12   .boot 0x90000000 :           
    13   { 
    14     *.*(.boot) 
    15   } > iram
    16   
    17   .=ALIGN(0x4);
    18   .text :         
    19   { 
    20     _ftext = .;
    21       *.*(.text) 
    22   } > iram
    23   .rdata  ALIGN(0x4)   :     
    24   { 
    25     *.*(.rdata) 
    26   } > iram  
    27   .rodata  ALIGN(0x4)   :     
    28   { 
    29     *.*(.rodata) 
    30   } > iram
    31   _etext = .;
    32 
    33   .data 0xa0000000:    
    34   { 
    35        *.*(.data) 
    36   } > dram AT>iram
    37   .sdata ALIGN (0x4) :
    38   {
    39     *.*(.sdata)
    40   } > dram AT>iram
    41   .sbss  ALIGN (0x4) :         
    42   { 
    43     _fbss = .; 
    44     *(dynsbss)*(.sbss)*(.sbss.*)*(.scommon) 
    45   } > dram AT>iram
    46   .bss   ALIGN (0x4) :         
    47   { 
    48     *(.dynbss)*(.bss)*(.bss.*) *(COMMON) 
    49   } > dram AT>iram
    50   _fdata = LOADADDR(.data);
    51   _edata = _fdata + SIZEOF(.data) + SIZEOF(.sdata);
    52   _ebss = _fbss + SIZEOF(.sbss) + SIZEOF(.bss); 
    53   _end = .;
    54   PROVIDE (end = .);
    55 }

    上面的boot汇编代码,会引用ld脚本中的LMA和VMA地址,然后进行copy或者清零的动作。

  • 相关阅读:
    如何找到一本好书以及读一本好书
    笔记
    web基础知识(二)
    7RestClient操作索引库
    3索引库操作
    4文档操作
    5实战hotel表创建es映射
    6初始化RestClient
    kafka的生产者学习
    kafka的缺陷
  • 原文地址:https://www.cnblogs.com/ironx/p/4963018.html
Copyright © 2020-2023  润新知