ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

时间:2024-05-31 17:06:31

KEIL MDK 分散加载的结构

    

1、我们先来解剖一只麻雀    

    很多人会说我做项目时没用过分散加载啊,可能有些人甚至都不知道它的存在。事实上,开发环境会默认生成一个分散加载文件(或者叫链接器描述文件),你使用的可能就是这个默认的分散加载文件,先来看一下Keil默认生成的分散加载文件,使用LPC54608随便找了一个示例代码用Keil生成了一个,如下图所示:

    ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

    这个分散加载是keil自己生成的分散加载,可以说是最简单的分散加载,简单到RO、RW、ZI基本都是随意摆放的;里面红色字体的是各个段的起始地址以及大小,这些段的起始地址以及大小是由下图的红框框定的部分决定的,你可以把上图以及下面这张图合起来看,地址是一致的。

    ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

    这个分散加载文件的基本意思如下图:

    ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

    这里面Flash以及SRAM的地址以及大小都是可以修改的,其他的也可以修改,但起始地址以及大小都要在芯片真实存在的有效物理地址上,这部分需要参看芯片的用户手册里面的Memory MAP一节的内容,如下图就是LPC54608的Memory MAP,参看分散加载里面的地址,Flash以及SRAM的起始地址以及大小都落在有效的空间上了。如果你定义的Flash起始地址在0x40000000,那么肯定会出错,因为与AHB总线地址冲突了。

    ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构       
2、分散加载的基本结构

    我们举个例子来说明分散加载文件的基本机构,如下:

LOAD_ROM_1 0x0000              【加载域描述】这段是要告诉链接器,你的程序是存在哪里?我从哪里去找需要执行的代码。
{                                                                        
    EXEC_ROM_1 0x0000           【运行域描述】这段是要告诉链接器,你的程序在哪里执行,在ARM Cortex-M系列的绝大多
    {                                                                    数MCU中加载域以及运行域是在同一个空间上的,即片内Flash。
        program1.o (+RO)            【输入节描述】就是告诉链接器,具体把哪一个以及怎么把这一个obj文件放到运行域里面
    }
    DRAM 0x18000 0x8000        【运行域描述】这里指的是RAM空间的运行域,下面会解释为什么这里会有两个运行域
    {
        program1.o (+RW,+ZI)     输入节描述】告诉链接器,去哪里找执行程序是需要使用的变量以及数据
    }
}

LOAD_ROM_2 0x4000                【另一个加载域描述】同一个工程可以有多个加载域,就好像同一台电脑可以装几个操作系统
{
    EXEC_ROM_2 0x4000             【另一个运行域描述】
    {
        program2.o (+RO)             【另一个输入节
    }
    SRAM 0x8000 0x8000            【另一个运行域描述】
    {
        program2.o (+RW,+ZI)      【另一个输入节
    }
}
    其实Keil的官方帮助文档里面有官方的对分散加载的全面介绍与使用指导,但是里面的东西语言太官方了,搞了一大堆定义,然后又例子又很少,一大堆的专业术语,小编我最开始学这部分内容就是啃的这部分,真是想死的心情停不了啊~。好吧,下面我尽量多举例子,尽量少扯那些乱七八糟的术语,下面我们开始:

    你可能会问,这个分散加载文件是用什么语言编写的?是一种脚本语言,不是C语言也不是汇编,所以分散加载不能调试,它是一种叫做BNF的东东,官方文档里面把这东西描述的极其神秘,反正我是没太看懂这个所谓的BNF到底是个啥东东(据说是一个符号推导语言之类的一般存在于各种专业论文里面用于装B的东西),但是经过多个工程的磨练,小编我也基本弄明白了这东西该怎么用了,至于理论层面我没有深究,这个东东貌似做分散加载的描述脚本有些大材小用了,它能做的事情远不止于此,当然其他的跟我们没关系啦。

    对于上面这个例子的几点说明:
    <1>分散加载的根本功能是指定程序在存储空间上面的存储分配以及运行空间的分配~,所有要有加载域和运行域来分别指定程序存储空间以及程序运行空间。一般来说程序的运行空间是在芯片的ROM类存储器里面,在Cortex-M里面基本就是芯片内部的Flash空间;
    <2>运行域就有意思了,由于MCU内部的Flash(几乎都是Nor-Flash)是可以运行代码的,但是不能用于变量也就是RW与ZI的加载,主要原因是变量需要经常修改,几个小时就可能连续改变几十万次,但是目前Flash工艺的写寿命介于10万次~100万次之间,如果把RW和ZI放在Flash上,那就是灾难,Flash会因为写次数的限制很快就会挂掉,而且Flash只能按块操作,开销太大,所以一般都是放到SRAM里面,所以你会看到在这个例子里面,运行域分成两个部分,RO数据段放在内部Flash里面,RW与ZI放到片内SRAM中去执行。这点与电脑是不同的,电脑的硬盘是完全不能执行程序的,所以如果你把电脑看成MCU的话,用Keil来编写程序的话,那么电脑的RO、RW、ZI段都是放到内存上执行的,也就是说电脑实际上只有一个执行域就在内存上,可以类似理解为MCU的片内SRAM上。其实很多市面上的A7、A8、A9、A53等内核的应用处理器运行Linux与Android也可以类比为电脑,所有的东西都要加载到RAM上运行。
    <3>所以分散加载可以简单理解为的最基本结构就是至少3个域(这个事实上不对,但是对于大多数Cortex-M系列MCU的分散加载可以这样简单理解):至少一个加载域、建议两个运行域(一个RO运行域、一个RW+ZI运行域),就是你要告诉链接器至少3个信息,即:从哪里加载程序(至少一个域)、在哪里运行程序(至少一个域)、在哪里读写程序运行中用到的变量(至少一个域,实际上也可以跟运行程序的域在一起,但强烈建议分开)。如果你自己编写分散加载文件,先把这把这3个结构写出来,我们再来看之前的麻雀:            

    ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

    LR_IROM1就是【加载域】,指定了用户程序存储在0x00000000起始大小为0x80000的地址上,用户程序从这个区域内加载;

    ER_IROM1就是【运行域】,是RO的运行域,因为LPC54608内部的Flash可以运行代码,所以用户代码可以从这个区域内运行;

    RW_IRAM1就是【运行域】,是RW+ZI的运行域,这里指向的是片内SRAM的地址。

    这样一解构,整个分散加载的基本结构就比较清晰了,下面我们再来看一个复杂一点的,同样的LPC54608的工程的分散加载                ARM Cortex-M底层技术(七)KEIL MDK 分散加载-1-分散加载的结构

    这个大家仔细看下这个结构,基本上上面所说的3个域结构的扩展。这里有几点不同的独特地方,我们解释下:

    1、USB单独搞了两个运行域出来是干嘛的?

        是这样的,USB由于要大量的数据吞吐,单独开辟一块SRAM作为数据BUFFER,所以你看到加载域2和加载域3的地址是在片内SRAM上,你可以对照一下上面的LPC5460的Memory MAP就会发现这两块地址是在片内SRAM上的。

    2、分散加载还可以指定堆栈????

        没错,确实可以,上面这个例子就在分散加载里面指定了堆栈其中:

        ARM_LIB_HEAP域ARM_LIB_STACK是两个编译器之前就定义好的标号(Symbol),用于处理堆栈分配。当然堆栈也可以在启动代码里面指定分配,这是两种不同的方式,上面这个例子是在分散加载中指定堆栈。

        关于分散加载,我们要讲的远不止于此,接下来,我们还会用几篇文档的篇幅来深入探讨这部分的技术。