ARM中的RO、RW和ZI DATA说明
发布时间:2020-12-15 18:35:44 所属栏目:百科 来源:网络整理
导读:RO段、RW段和ZI段 ? ?? ?? ?? ?一直以来对于ARM体系中所描述的RO,RW和ZI数据存在似是而非的理解,这段时间对其仔细了解了一番,发现了一些规律,理解了一些以前书本上有的但是不理解的东西,我想应该有不少人也有和我同样的困惑,因此将我的一些关于RO,RW
RO段、RW段和ZI段
? ?? ?? ?? ?一直以来对于ARM体系中所描述的RO,RW和ZI数据存在似是而非的理解,这段时间对其仔细了解了一番,发现了一些规律,理解了一些以前书本上有的但是不理解的东西,我想应该有不少人也有和我同样的困惑,因此将我的一些关于RO,RW和ZI的理解写出来,希望能对大家有所帮助。 ? ?? ?? ?? ?要了解RO,RW和ZI需要首先了解以下知识: ? ?? ?? ?? ?ARM程序的组成 ? ?? ?? ?? ?此处所说的“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM中的bin映像(image)文件,这一点清注意区别。 ? ?? ?? ?? ?一个ARM程序包含3部分:RO,RW和ZI ? ?? ?? ?? ?RO是程序中的指令和常量 ? ?? ?? ?? ?RW是程序中的已初始化变量 ? ?? ?? ?? ?ZI是程序中的未初始化的变量 ? ?? ?? ?? ?由以上3点说明可以理解为: ? ?? ?? ?? ?RO就是readonly, ? ?? ?? ?? ?RW就是read/write, ? ?? ?? ?? ?ZI就是zero ? ?? ?? ?? ?ARM映像文件的组成 ? ?? ?? ?? ?所谓ARM映像文件就是指烧录到ROM中的bin文件,也成为image文件。以下用Image文件来称呼它。 ? ?? ?? ?? ?Image文件包含了RO和RW数据。 ? ?? ?? ?? ?之所以Image文件不包含ZI数据,是因为ZI数据都是0,没必要包含,只要程序运行之前将ZI数据所在的区域一律清零即可。包含进去反而浪费存储空间。 ? ?? ?? ?? ?Q:为什么Image中必须包含RO和RW? ? ?? ?? ?? ?A:因为RO中的指令和常量以及RW中初始化过的变量是不能像ZI那样“无中生有”的。 ? ?? ?? ?? ?ARM程序的执行过程 ? ?? ?? ?? ?从以上两点可以知道,烧录到ROM中的image文件与实际运行时的ARM程序之间并不是完全一样的。因此就有必要了解ARM程序是如何从ROM中的image到达实际运行状态的。 ? ?? ?? ?? ?实际上,RO中的指令至少应该有这样的功能: ? ?? ?? ?? ?1. 将RW从ROM中搬到RAM中,因为RW是变量,变量不能存在ROM中。 ? ?? ?? ?? ?2. 将ZI所在的RAM区域全部清零,因为ZI区域并不在Image中,所以需要程序根据编译器给出的ZI地址及大小来将相应得RAM区域清零。ZI中也是变量,同理:变量不能存在ROM中 ? ?? ?? ?? ?在程序运行的最初阶段,RO中的指令完成了这两项工作后C程序才能正常访问变量。否则只能运行不含变量的代码。 ? ?? ?? ?? ?说了上面的可能还是有些迷糊,RO,RW和ZI到底是什么,下面我将给出几个例子,最直观的来说明RO,RW,ZI在C中是什么意思。 ? ?? ?? ?? ?1; RO ? ?? ?? ?? ?看下面两段程序,他们之间差了一条语句,这条语句就是声明一个字符常量。因此按照我们之前说的,他们之间应该只会在RO数据中相差一个字节(字符常量为1字节)。 ? ?? ?? ?? ?Prog1: ? ?? ?? ?? ?#include <stdio.h> ? ?? ?? ?? ?void main(void) ? ?? ?? ?? ?{ ? ?? ?? ?? ?; ? ?? ?? ?? ?} ? ?? ?? ?? ?Prog2: ? ?? ?? ?? ?#include <stdio.h> ? ?? ?? ?? ?const char a = 5; ? ?? ?? ?? ?void main(void) ? ?? ?? ?? ?{ ? ?? ?? ?? ?; ? ?? ?? ?? ?} ? ?? ?? ?? ?Prog1编译出来后的信息如下: ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Code RO Data RW Data ZI Data Debug ? ?? ?? ?? ?948 60 0 96 0 Grand Totals ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Total RO Size(Code + RO Data) 1008 ( 0.98kB) ? ?? ?? ?? ?Total RW Size(RW Data + ZI Data) 96 ( 0.09kB) ? ?? ?? ?? ?Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB) ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Prog2编译出来后的信息如下: ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Code RO Data RW Data ZI Data Debug ? ?? ?? ?? ?948 61 0 96 0 Grand Totals ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Total RO Size(Code + RO Data) 1009 ( 0.99kB) ? ?? ?? ?? ?Total RW Size(RW Data + ZI Data) 96 ( 0.09kB) ? ?? ?? ?? ?Total ROM Size(Code + RO Data + RW Data) 1009 ( 0.99kB) ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?以上两个程序编译出来后的信息可以看出: ? ?? ?? ?? ?Prog1和Prog2的RO包含了Code和RO Data两类数据。他们的唯一区别就是Prog2的RO Data比Prog1多了1个字节。这正和之前的推测一致。 ? ?? ?? ?? ?如果增加的是一条指令而不是一个常量,则结果应该是Code数据大小有差别。 ? ?? ?? ?? ?2; RW ? ?? ?? ?? ?同样再看两个程序,他们之间只相差一个“已初始化的变量”,按照之前所讲的,已初始化的变量应该是算在RW中的,所以两个程序之间应该是RW大小有区别。 ? ?? ?? ?? ?Prog3: ? ?? ?? ?? ?#include <stdio.h> ? ?? ?? ?? ?void main(void) ? ?? ?? ?? ?{ ? ?? ?? ?? ?; ? ?? ?? ?? ?} ? ?? ?? ?? ?Prog4: ? ?? ?? ?? ?#include <stdio.h> ? ?? ?? ?? ?char a = 5; ? ?? ?? ?? ?void main(void) ? ?? ?? ?? ?{ ? ?? ?? ?? ?; ? ?? ?? ?? ?} ? ?? ?? ?? ?Prog3编译出来后的信息如下: ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Code RO Data RW Data ZI Data Debug ? ?? ?? ?? ?948 60 0 96 0 Grand Totals ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Total RO Size(Code + RO Data) 1008 ( 0.98kB) ? ?? ?? ?? ?Total RW Size(RW Data + ZI Data) 96 ( 0.09kB) ? ?? ?? ?? ?Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB) ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Prog4编译出来后的信息如下: ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Code RO Data RW Data ZI Data Debug ? ?? ?? ?? ?948 60 1 96 0 Grand Totals ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Total RO Size(Code + RO Data) 1008 ( 0.98kB) ? ?? ?? ?? ?Total RW Size(RW Data + ZI Data) 97 ( 0.09kB) ? ?? ?? ?? ?Total ROM Size(Code + RO Data + RW Data) 1009 ( 0.99kB) ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?可以看出Prog3和Prog4之间确实只有RW Data之间相差了1个字节,这个字节正是被初始化过的一个字符型变量“a”所引起的。 ? ?? ?? ?? ?3; ZI ? ?? ?? ?? ?再看两个程序,他们之间的差别是一个未初始化的变量“a”,从之前的了解中,应该可以推测,这两个程序之间应该只有ZI大小有差别。 ? ?? ?? ?? ?Prog3: ? ?? ?? ?? ?#include <stdio.h> ? ?? ?? ?? ?void main(void) ? ?? ?? ?? ?{ ? ?? ?? ?? ?; ? ?? ?? ?? ?} ? ?? ?? ?? ?Prog4: ? ?? ?? ?? ?#include <stdio.h> ? ?? ?? ?? ?char a; ? ?? ?? ?? ?void main(void) ? ?? ?? ?? ?{ ? ?? ?? ?? ?; ? ?? ?? ?? ?} ? ?? ?? ?? ?Prog3编译出来后的信息如下: ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Code RO Data RW Data ZI Data Debug ? ?? ?? ?? ?948 60 0 96 0 Grand Totals ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Total RO Size(Code + RO Data) 1008 ( 0.98kB) ? ?? ?? ?? ?Total RW Size(RW Data + ZI Data) 96 ( 0.09kB) ? ?? ?? ?? ?Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB) ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Prog4编译出来后的信息如下: ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Code RO Data RW Data ZI Data Debug ? ?? ?? ?? ?948 60 0 97 0 Grand Totals ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?Total RO Size(Code + RO Data) 1008 ( 0.98kB) ? ?? ?? ?? ?Total RW Size(RW Data + ZI Data) 97 ( 0.09kB) ? ?? ?? ?? ?Total ROM Size(Code + RO Data + RW Data) 1008 ( 0.98kB) ? ?? ?? ?? ?================================================================================ ? ?? ?? ?? ?编译的结果完全符合推测,只有ZI数据相差了1个字节。这个字节正是未初始化的一个字符型变量“a”所引起的。 ? ?? ?? ?? ?注意:如果一个变量被初始化为0,则该变量的处理方法与未初始化华变量一样放在ZI区域。 ? ?? ?? ?? ?即:ARM C程序中,所有的未初始化变量都会被自动初始化为0。 ? ?? ?? ?? ?总结: ? ?? ?? ?? ?1; C中的指令以及常量被编译后是RO类型数据。 ? ?? ?? ?? ?2; C中的未被初始化或初始化为0的变量编译后是ZI类型数据。 ? ?? ?? ?? ?3; C中的已被初始化成非0值的变量编译后市RW类型数据。 ? ?? ?? ?? ?附: ? ?? ?? ?? ?程序的编译命令(假定C程序名为tst.c): ? ?? ?? ?? ?armcc -c -o tst.o tst.c ? ?? ?? ?? ?armlink -noremove -elf -nodebug -info totals -info sizes -map -list aa.map -o tst.elf tst.o ? ?? ?? ?? ?编译后的信息就在aa.map文件中。 ? ?? ?? ?? ?ROM主要指:NAND Flash,Nor Flash ? ?? ?? ?? ?RAM主要指:PSRAM,SDRAM,SRAM,DDRAM
?
//===================================================
Image$$??$$Limit 的含义
对于刚学习ARM的人来说,如果分析它的启动代码,往往不明白下面几个变量的含义:|Image$$RO$$Limit|、|Image$$RW$$Base|、|Image$$ZI$$Base|。 首先申明我使用的调试软件为ADS1.2,当我们把程序编写好以后,就要进行编译和链接了,在ADS1.2中选择MAKE按钮,会出现一个Errors and Warnings 的对话框,在该栏中显示编译和链接的结果,如果没有错误,在文件的最后应该能看到Image component sizes,后面紧跟的依次是Code,RO Data ,RW Data ,ZI Data ,Debug 各个项目的字节数,最后会有他们的一个统计数据: Code 163632 ,RO Data 20939 ,RW Data 53 ,ZI Data 17028 Tatal RO size (Code+ RO Data)? ?? ?? ?? ? 184571 (180.25kB) Tatal RW size(RW Data+ ZI Data)? ?? ?? ???17081(16.68 kB) Tatal ROM size(Code+ RO Data+ RW Data)? ?184624(180.30 kB) 后面的字节数是根据用户不同的程序而来的,下面就以上面的数据为例来介绍那几个变量的计算。 在ADS的Debug Settings中有一栏是Linker/ARM Linker,在output选项中有一个RO base选项,下面应该有一个地址,我这里是0x0c100000,后面的RW base 地址是0x0c200000,然后在Options选项中有Image entry point ,是一个初始程序的入口地址,我这里是0x0c100000 。 有了上面这些信息我们就可以完全知道这几个变量是怎么来的了: |Image$$RO$$Base| = Image entry point = 0x0c100000 ;表示程序代码存放的起始地址 |Image$$RO$$Limit|=程序代码起始地址+代码长度+1=0x0c100000+Tatal RO size+1 = 0x0c100000 + 184571 + 1 = 0x0c100000 +0x2D0FB + 1 = 0x0c12d0fc |Image$$RW$$Base| = 0x0c200000 ;由RW base 地址指定 |Image$$RW$$Limit| =|Image$$RW$$Base|+ RW Data 53 = 0x0c200000+0x37(4的倍数,0到55,共56个单元) =0x0c200037 |Image$$ZI$$Base| = |Image$$RW$$Limit| + 1 =0x0c200038 |Image$$ZI$$Limit| = |Image$$ZI$$Base| + ZI Data 17028 ? ?? ?? ?? ?? ?? ?? ?? ?? ? =0x0c200038 + 0x4284 ? ?? ?? ?? ?? ?? ?? ?? ?? ? =0x0c2042bc 也可以由此计算: |Image$$ZI$$Limit| = |Image$$RW$$Base| +TatalRWsize(RWData+ZIData) 17081 ? ?? ?? ?? ?? ?? ?? ?? ?? ? =0x0c200000+0x42b9+3(要满足4的倍数) ? ?? ?? ?? ?? ?? ?? ?? ?? ? =0x0c2042bc
//====================================================================
Part1??简介
一 概述 Scatter file (分散加载描述文件)用于armlink的输入参数,他指定映像文件内部各区域的download与运行时位置。Armlink将会根据scatter file生成一些区域相关的符号,他们是全局的供用户建立运行时环境时使用。(注意:当使用了scatter file 时将不会生成以下符号 Image$$RW$$Base,Image$$RW$$Limit,Image$$RO$$Base,Image$$RO$$Limit,Image$$ZI$$Base,and Image$$ZI$$Limit) 二 什么时候使用scatter file ? ?? ? 当然首要的条件是你在利用ADS进行项目开发,下面我们看看更具体的一些情况。 1 存在复杂的地址映射:例如代码和数据需要分开放在在多个区域。 2 存在多种存储器类型:例如包含 Flash,ROM,SDRAM,快速SRAM。我们根据代码与数据的特性把他们放在不同的存储器中,比如中断处理部分放在快速SRAM内部来提高响应速度,而把不常用到的代码放到速度比较慢的Flash内。 3 函数的地址固定定位:可以利用Scatter file实现把某个函数放在固定地址,而不管其应用程序是否已经改变或重新编译。 4 利用符号确定堆与堆栈: 5 内存映射的IO:采用scatter file可以实现把某个数据段放在精确的地指处。 因此对于嵌入式系统来说scatter file是必不可少的,因为嵌入式系统采用了ROM,RAM,和内存映射的IO。 三 scatter file 实例 1 简单的内存映射 LOAD_ROM 0x0000 0x8000 { ? ?? ? EXEC_ROM 0x0000 0x8000 ? ?? ? { ? ?? ? *( RO) ? ?? ? } ? ?? ?RAM 0x10000 0x6000 ? ?? ?{ ? ?? ? *( RW,ZI) ? ?? ?} } LOAD_ROM(下载区域名称) 0x0000(下载区域起始地址) 0x8000(下载区域最大字节数) { ? ?? ???EXEC_ROM(第一执行区域名称) 0x0000(第一执行区域起始地址) 0x8000(第一执行区域最大字节数) ? ?? ? { ? ?? ? *( RO(代码与只读数据)) ? ?? ? } ? ?? ?RAM(第二执行区域名称) 0x10000(第二执行区域起始地址) 0x6000(第二执行区域最大字节数) ? ?? ?{ ? ?? ? *( RW(读写变量),ZI(未初始化变量)) ? ?? ?} } 2 复杂内存映射 LOAD_ROM_1 0x0000 { ? ?? ? EXEC_ROM_1 0x0000 ? ?? ? { ? ?? ???program1.o( RO) ? ?? ? } ? ?? ?DRAM 0x18000 0x8000 ? ?? ?{ ? ?? ? program1.o ( RW,ZI) ? ?? ?} } LOAD_ROM_2 0x4000 { ? ?? ? EXEC_ROM_2 0x4000 ? ?? ? { ? ?? ? program2.o( RO) ? ?? ? } ? ?? ? SRAM 0x8000 0x8000 ? ?? ?{ ? ?? ? program2.o ( RW,ZI) ? ?? ?} } LOAD_ROM_1 0x0000(下载区域一起始地址) { ? ?? ? EXEC_ROM_1 0x0000(第一执行区域开始地址) ? ?? ? { ? ?? ???program1.o( RO) (program1.o内的Code与RO data 放在第一执行区域) ? ?? ? } ? ?? ? DRAM 0x18000(第二执行区域开始地址) 0x8000(第二执行区域最大字节数) ? ?? ?{ ? ?? ? program1.o ( RW,ZI) (program1.o内的RW data与 ZI data 放在第二执行区域) ? ?? ?} } LOAD_ROM_2 0x4000(下载区域二起始地址) { ? ?? ? EXEC_ROM_2 0x4000 ? ?? ? { ? ?? ? program2.o( RO) (program2.o内的Code与RO data 放在第一执行区域) ? ?? ?} ? ?? ?SRAM 0x8000 0x8000 ? ?? ?{ ? ?? ? program2.o ( RW,ZI) (program2.o内的RW data与 ZI data 放在第二执行区域) ? ?? ?} } Part2 基本语法 2.1? ?BNF 符号与语法 " :由引号赖标示的符号保持其字面原意,如A” ”B标示A B。 A ::= B :定义A为B。 [A] :标示可选部分,如A[B]C用来标示ABC或AC。 A :用来标示A可以重复任意次,如A 可标示A,AA,AAA,… A* :同A 。 A | B :用来标示选择其一,不能全选。如A|B用来标示A或者B。 (A B) :标示一个整体,当和|符号或复杂符号的多次重复一起使用时尤其强大,如(AB) (C|D)标示ABC,ABD,ABABC,ABABD,… 2.2? ???分散加载文件各部分描述? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ?? ?? ?? ?? ?? ? (2.1) 如图2.1所示为一个完整的分散加载脚本描述结构图。下面我们对图示中各个部分进行讲述。 2.2.1 加载区描述 每个加载区有: ó名称:供连接器确定不同下载区域 ó基地址:相对或绝对地址 ó属性:可选 ó最大字节数:可选 ó执行区域列:确定执行时各执行区域的类型与位置 load_region_name (base_address | (" " offset)) [attribute_list] [ max_size ] "{" execution_region_description "}" load_region_name:下载区域名称,最大有效字符数31。(并不像执行区域段名用于Load$$region_name,而是仅仅用于标示下载区域)。 base_address:本区域内部目标被连接到的地址(按字对齐)。 offset:相对前一个下载区域的偏移量(4的整数倍,如果为第一个区域)。 2.2.2 执行区描述 每个执行区有: ó名称:供连接器确定不同下载区域 ó基地址:相对或绝对地址 ó属性:确定执行区域的属性 ó最大字节数:可选 ó输入段:确定放在该执行区域的模块 exec_region_name (base_address | " " offset) [attribute_list] [max_size] "{" input_section_description "}" exec_region_name:执行区域名称,最大有效字符数31。 base_address:本执行区域目标要被联接到的位置,按字对齐。 offset:相对于前一个执行区域结束地址的偏移量,4的整数倍;如果没有前继之能够行区域(本执行区域为该下载区域的第一个执行区域),则该偏移量是相对于该下载区域的基址偏移量。 attribute_list:PI,OVERLAY,ABSOLUTE,FIXED,UNINIT。 PI: 位置独立。 OVERLAY: 覆盖。 ABSOLUTE: 绝对地址。 FIXED: 固定地址,下载地址与执行地址具有该地址指示确定。 UNINIT: 未初始化数据。 RELOC:无法明确指定执行区域具有该属性,而只能通过继承前一个执行区或父区域获得。 对于PI,OVERLAY,ABSOLUTE,FIXED,我们只能选择一个,缺省属性为ABSOLUTE。一个执行区域要么直接继承其前面的执行区域的属性或者具有属性为ABSOLUTE。 具有PI,OVERLAY,RELOC属性的执行区域允许其地址空间重叠,对于BSOLUTE,FIXED 属性执行区域地址空间重叠Armlink会报错。 max_size:可选,他用于指使Armlink在实际分配空间大于指定值时报错。 input_section_description:指示输入段的内容。 2.2.3 输入段描述 输入段: ó模块名:目标文件名,库成员名,库文件名。名称可以使用通配符。 ó输入段名,或输入段属性(READ-ONLY,CODE)。 module_select_pattern ["(" (" " input_section_attr | input_section_pattern) ([","] " " input_section_attr | "," input_section_pattern))* ")"] 2.2.3.1 module_select_pattern:选择的模块名称(目标文件,库文件成员,库文件),模块名可以使用通配符(*匹配任意多个字符,?匹配任意一个字符),名称不区分字母大小写,它是供选择的样本。 例1:*libtx.a ( RO) libtx.a为threadX库文件。 例2:tx_ill.o (INIT) ? ?? ? tx_ill.o为threadX中断向量目标文件。 2.2.3.2 input_section_attr:输入段属性选择子,每个选择子以” ”开头,选择子不区分大小写字符。 选择子可选RO-CODE,RO-DATA,RO( selects both RO-CODE and RO-DATA),RW-DATA,RW-CODE,RW( selects both RW-CODE and RW-DATA),ZI,ENTRY( that is a section containing an ENTRY point)。 以下同义词可以选择:CODE (for RO-CODE),CONST( for RO-DATA),TEXT (for RO),DATA (for RW),BSS (for ZI)。 还有两个伪属性:FIRST,LAST。如果各段的先后顺序比较重要时,可以使用FIRST,LAST标示一个执行区域的第一个和最后一个段。 例1:os_main_init.o (INIT,FIRST) FIRST表示放于本执行区域的开始处。 例2:*libtx.a ( RO) ? ?? ? RO 表示*libtx.a的只读部分。 2.2.3.3 input_section_pattern:输入段名。 例1:os_main_init.o (INIT,FIRST) INIT 为os_main_init.o的一个段。 例2:os_stackheap.o (heap) ? ?? ? heap 为os_stackheap.o的一个段。 例3:os_stackheap.o (stack) ? ?? ? stack为os_stackheap.o的一个段。 //-------------------------------------------------------------------------------------------------------------------------- 分散加载文件事例 ADS下的分散加载文件应用实例 load_region_name??start_address | " "offset??[attributes] [max_size] { ? ? execution_region_name??start_address | " "offset??[attributes][max_size] ? ? { ? ?? ???module_select_pattern??["(" ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?(" " input_section_attr | input_section_pattern) ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?(["," input_section_pattern)) * ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ")"] ? ? } } load_region:? ?? ? 加载区,用来保存永久性数据(程序和只读变量)的区域; execution_region:??执行区,程序执行时,从加载区域将数据复制到相应执行区后才能被正确执行; load_region_name:??加载区域名,用于“Linker”区别不同的加载区域,最多31个字符; start_address:? ???起始地址,指示区域的首地址; offset:? ?? ?? ???前一个加载区域尾地址+offset 做为当前的起始地址,且“offset”应为“0”或“4”的倍数; attributes:? ?? ???区域属性,可设置如下属性: ? ?? ?? ?? ?? ?? ???PI? ?? ? 与地址无关方式存放; ? ?? ?? ?? ?? ?? ???RELOC? ? 重新部署,保留定位信息,以便重新定位该段到新的执行区; ? ?? ?? ?? ?? ?? ???OVERLAY??覆盖,允许多个可执行区域在同一个地址,ADS不支持; ? ?? ?? ?? ?? ?? ???ABSOLUTE 绝对地址(默认); max_size:? ?? ?? ? 该区域的大小; execution_region_name:执行区域名; start_address:? ???该执行区的首地址,必须字对齐; offset:? ?? ?? ???同上; attributes:? ?? ???同上; ? ?? ?? ?? ?? ?? ???PI? ?? ?? ? 与地址无关,该区域的代码可任意移动后执行; ? ?? ?? ?? ?? ?? ???OVERLAY? ???覆盖; ? ?? ?? ?? ?? ?? ???ABSOLUTE? ? 绝对地址(默认); ? ?? ?? ?? ?? ?? ???FIXED? ?? ? 固定地址; ? ?? ?? ?? ?? ?? ???UNINIT? ?? ?不用初始化该区域的ZI段; module_select_pattern: 目标文件滤波器,支持通配符“*”和“?”; ? ?? ?? ?? ?? ?? ?? ?? ?*.o匹配所有目标,* (或“.ANY”)匹配所有目标文件和库。 input_section_attr:? ? 每个input_section_attr必须跟随在“+”后;且大小写不敏感; ? ?? ?? ?? ?? ?? ?? ?? ?RO-CODE 或 CODE ? ?? ?? ?? ?? ?? ?? ?? ?RO-DATA 或 CONST ? ?? ?? ?? ?? ?? ?? ?? ?RO或TEXT,selects both RO-CODE and RO-DATA ? ?? ?? ?? ?? ?? ?? ?? ?RW-DATA ? ?? ?? ?? ?? ?? ?? ?? ?RW-CODE ? ?? ?? ?? ?? ?? ?? ?? ?RW 或 DATA,selects both RW-CODE and RW-DATA ? ?? ?? ?? ?? ?? ?? ?? ?ZI 或 BSS ? ?? ?? ?? ?? ?? ?? ?? ?ENTRY,that is a section containing an ENTRY point. ? ?? ?? ?? ?? ?? ?? ?? ?FIRST,用于指定存放在一个执行区域的第一个或最后一个区域; ? ?? ?? ?? ?? ?? ?? ?? ?LAST,同上; input_section_pattern: 段名; 汇编中指定段: ? ???AREA? ? vectors,CODE,READONLY C中指定段: #pragma arm section [sort_type[[=]"name"]] [,sort_type="name"]* sort_type:? ?? ?code、rwdata、rodata、zidata ? ?? ?? ?? ?? ? 如果“sort_type”指定了但没有指定“name”,那么之前的修改的段名将被恢复成默认值。 #pragma arm section? ???// 恢复所有段名为默认设置。 应用: ? ? #pragma arm section rwdata = "SRAM",zidata = "SRAM" ? ?? ???static OS_STK??SecondTaskStk[256];? ?? ?? ?? ???// “rwdata”“zidata”将定位在“sram”段中。 ? ? #pragma arm section? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?// 恢复默认设置 分散加载文件中定义如下: ? ? Exec_Sram??0x80000000??0x40000 ? ? { ? ?? ???* (sram) ? ? } “PI” 属性使用示例: LR_1 0x010000 PI? ?? ?? ?? ?? ? ; The first load region is at 0x010000. { ? ? ER_RO 0? ?? ?? ?? ?? ?? ???; The PI attribute is inherited from parent. ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???; The default execution address is 0x010000,but the code can be moved. ? ? { ? ?? ???*( RO)? ?? ?? ?? ?? ?? ?; All the RO sections go here. ? ? } ? ? ER_RW 0 ABSOLUTE? ?? ?? ???; PI attribute is overridden by ABSOLUTE. ? ? { ? ?? ???*( RW)? ?? ?? ?? ?? ?? ?; The RW sections are placed next. They cannot be moved. ? ? } ? ? ER_ZI 0? ?? ?? ?? ?? ?? ???; ER_ZI region placed after ER_RW region. ? ? { ? ?? ???*( ZI)? ?? ?? ?? ?? ?? ?; All the ZI sections are placed consecutively here. ? ? } } LR_1 0x010000? ?? ?? ?? ?? ?? ? ; The first load region is at 0x010000. { ? ? ER_RO 0? ?? ?? ?? ?? ?? ???; Default ABSOLUTE attribute is inherited from parent. The execution address ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???; is 0x010000. The code and ro data cannot be moved. ? ? { ? ?? ???*( RO)? ?? ?? ?? ?? ?? ?; All the RO sections go here. ? ? } ? ? ER_RW 0x018000 PI? ?? ?? ???; PI attribute overrides ABSOLUTE ? ? { ? ?? ???*( RW)? ?? ?? ?? ?? ?? ?; The RW sections are placed at 0x018000 and they can be moved. ? ? } ? ? ER_ZI 0? ?? ?? ?? ?? ?? ???; ER_ZI region placed after ER_RW region. ? ? { ? ?? ???*( ZI)? ?? ?? ?? ?? ?? ?; All the ZI sections are placed consecutively here. ? ? } } 程序中对某区域地址等的引用方法: Load$$region_name$$Base? ?? ?? ?? ? Load address of the region. Image$$region_name$$Base? ?? ?? ?? ?Execution address of the region. Image$$region_name$$Length? ?? ?? ? Execution region length in bytes (multiple of 4). Image$$region_name$$Limit? ?? ?? ???Address of the byte beyond the end of the execution region. Image$$region_name$$ZI$$Base? ?? ???Execution address of the ZI output section in this region. Image$$region_name$$ZI$$Length? ?? ?Length of the ZI output section in bytes (multiple of 4). Image$$region_name$$ZI$$Limit? ?? ? Address of the byte beyond the end of the ZI output sectionin the execution region. SectionName$$Base? ?? ?? ?? ?? ?? ? Input Address of the start of the consolidated section called SectionName. SectionName$$Limit? ?? ?? ?? ?? ?? ?Input Address of the byte beyond the end of the consolidated section called SectionName. Load:? ?? ?? ? 加载区,即存放地址; Image:? ?? ?? ?执行区,即运行地址; Base:? ?? ?? ? 区首地址; Limit:? ?? ?? ?区尾地址; Length:? ?? ???区长度; region_name:? ?RO、RW、ZI、load_region_name、execution_region_name; 例如: ? ? “RAM1”区域的首地址:? ?? ?Image$$RAM1$$Base ? ? 上例中“sram”段首地址:? ? sram$$Base 汇编引用示例: ??IMPORT |Load$$Exec_RAM1$$Base|? ?? ?? ?? ???// Exec_RAM1 为“RW”段 ??IMPORT |Image$$Exec_RAM1$$Base| ??IMPORT |Image$$Exec_RAM1$$Length| ??IMPORT |Image$$Exec_RAM1$$Limit| ??LDR??R0,=|Load$$Exec_RAM1$$Base| ??LDR??R1,=|Image$$Exec_RAM1$$Base| ??LDR??R2,=|Image$$Exec_RAM1$$Limit| 0 ??CMP??R1,? ?R2 ??LDRCC R3,? ?[R0],#4 ??STRCC R3,? ?[R1],#4 ??BCC??%b0 C 引用: extern unsigned char Load$$Exec_RAM1$$Base; extern unsigned char Image$$Exec_RAM1$$Base; extern unsigned char Image$$Exec_RAM1$$Length; void MoveRO(void) { unsigned char * psrc,*pdst; unsigned int??count; count = (unsigned int)? ?&Image$$Exec_RAM1$$Length; psrc??= (unsigned char *)&Load$$Exec_RAM1$$Base; pdst??= (unsigned char *)&Image$$Exec_RAM1$$Base; while (count--) { ??*pdst = *psrc ; } } 加载文件示例一: ? ?? ???起始地址? ?? ?大小 ROM:? ? 0x00000000? ? 256K? ?? ?;0x1fc 保留为加密字,程序在ROM中运行; RAM? ???0x40000000? ???16K? ?? ?;用于全局变量及任务堆栈; SRAM? ? 0x80000000? ? 512K? ?? ?;SRAM速度慢,主要用于存放大的数据表; LOAD_ROM1 0x00000000??0x1f8? ?? ?? ?? ?? ???; 指定该加载区域首地址、大小 { ? ? EXEC_ROM1??0??0x1f8? ?? ?? ?? ?? ?? ???; 没有前一加载区域,所以该执行区域首地址为加载去首地址 ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???; 并指定该区域长度 ? ? { ? ?? ???Startup.o (vectors,FIRST)? ?? ?? ?; 目标文件的“vectors”段放在该执行区域的第一段 ? ?? ???irq.o ( RO)? ?? ?? ?? ?? ?? ?? ?? ? ; 目标文件的所有“RO”段放在该执行区域 ? ? } } LOAD_ROM2 0x00000200? ?? ?? ?? ?? ?? ?? ?? ?; 第二个加载区域 { ? ? EXEC_ROM2??0??0x3e600 ? ? { ? ?? ???* ( RO)? ?? ?? ?? ?? ?? ?? ?? ?? ???; 所有目标文件和库文件中的“RO”段存放在该区域 ? ? } ? ? RAM1? ?0x40000000? ?0x4000 ? ? { ? ?? ???* ( RW,ZI)? ?? ?? ?? ?? ?? ?? ?? ?; 所有目标文件和库文件的“RW”和“ZI”段存放在该区域 ? ? } ? ? SRAM2??0x80000000??0x80000 ? ? { ? ?? ???* (sram)? ?? ?? ?? ?? ?? ?? ?? ?? ? ; 所有目标文件中的“sram”段存放在该区域 ? ? } } 示例二: ? ? “iap.o”定义在“Exec_RAM1”中运行,所以设置“PI”属性; ? ? 在调用“iap.c”中函数之前应该将其从“Load$$Exec_IAP$$Base”复制到指定的“Exec_RAM1”区域; Load_region1??0x00000000??0x1fc { ? ? EXEC_ROM1??0 ? ? { ? ?? ???Startup.o (vectors,FIRST) ? ?? ???irq.o ( RO) ? ? } } Load_region2??0x00000200??0x3e600 { ? ? EXEC_ROM2??0 ? ? { ? ?? ???* ( RO) ? ? } ? ? Exec_IAP? ?0??PI? ?? ?? ?? ?? ?// 可能引起链接器未使用该属性警告,忽略 ? ? { ? ?? ???iap.o ( RO) ? ? } ? ? Exec_RAM1??0x40000000??0x4000 ? ? { ? ?? ???* ( RW,ZI) ? ? } ? ? Exec_Sram??0x80000000??0x40000 ? ? { ? ?? ???* (SRAM) ? ? } } // 移动“IAP.o”中的所有函数到“ImageExecIAPBase”加载区,并调用其中的函数 extern unsigned char Load$$Exec_IAP$$Base; extern unsigned char Image$$Exec_IAP$$Length; #define??ImageExecIAPBase??(0x40000000 0x1000)? ?// 加载区首址 void MoveIAPRO(void) { unsigned char * psrc,*pdst; unsigned int??count; count = (unsigned int)? ?&Image$$Exec_IAP$$Length; psrc??= (unsigned char *)&Load$$Exec_IAP$$Base; pdst??= (unsigned char *)ImageExecIAPBase; while (count--) { ??*pdst = *psrc ; } } // 调用“IAP.O”中的某函数 { ??void (* pfnIAPWrite)(unsigned long,int); ??pfnIAPWrite = (void (*)(unsigned long,int)) ? ?(ImageExecIAPBase ? ?(unsigned int)IAPWrite -? ?? ?? ?? ?? ?? ?? ?? ?// 被调用函数名 ? ?(unsigned int)&Load$$Exec_IAP$$Base); ??pfnIAPWrite((int)((CUPDATA *)CODESTARTADDR)->data, ? ???((CUPDATA *)CODESTARTADDR)->length); ? ? } //———————————————————————————————————————————————————————————— ARM编译程序参考 介绍ARM编译程序的ARM特有方面,包括: Pragmas? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? 编译指示 Function keywords? ?? ?? ?? ?? ?? ???函数关键字 Variable declaration keywords 变量声明关键字 Pragmas ARM编译程序可识别一下格式的编译指示: #pragma [no_] feature-name 编译指示优于相关的命令行选项。 能识别的编译选项如下: Pragma name ? ? ? ? Default ? ? ? ? Reference arm section ? ? ? ? Off ? ? ? ? Pragmas controlling code generation check_printf_formats ? ? ? ? Off ? ? ? ? Pragmas controlling printf and scanf argument checking check_scanf_formats ? ? ? ? Off ? ? ? ? Pragmas controlling printf and scanf argument checking check_stack ? ? ? ? On ? ? ? ? Pragmas controlling code generation debug ? ? ? ? On ? ? ? ? Pragmas controlling debugging import ? ? ? ? ? ? ? ? code generation Ospace ? ? ? ? ? ? ? ? optimization Otime ? ? ? ? ? ? ? ? optimization Onum ? ? ? ? ? ? ? ? optimization softfp_linkage ? ? ? ? Off ? ? ? ? code generation ? ? * check_printf_formats 该编译指示标记类似于printf的函数,如果存在文字格式串,则对照进行类型检查。 #pragma check_printf_formats extern void myprintf(const char *format,…); #pragma no_check_printf_formats ? ? * check_scanf_formats 该编译指示对声明为类似于scanf的函数做标记,以便对照文字格式串检查自变量的格式。 #pragma check_scanf_formats extern void myformat(const char *format,…); #pragma no_check_scanf_formats ? ? * debug 该编译指示可打开或关闭调试表生成,如果指定#pragma no_debug,则不会为随后的声明和函数生成调试信息表条目,直到下一个#pragma debug出现。 ? ? * Pragmas controlling optimization Ospace Otime Onum ? ? * Pragmas controlling code generation ? ?? ?? ? o check_stack 如果已经使用了#pragma no_check_stack和-apcs/swst命令行选项禁止栈检查,则该编译指示可使的检查是否违反了栈限制的函数入口代码的重新生成。 ? ?? ?? ? o once? ?? ?? ?? ? 同#ifndef …#endif效果相类似,用于头文件。但一般推荐使用#ifndef…#define。 ? ?? ?? ? o softfp_linkage? ?该编译指示指定了至下一个#pragma no_softfp_linkage之间的所有函数声明描述了使用软件浮点链接的函数。__softfp关键字与该编译指示的效果相同 ? ?? ?? ? o import(symbol_name) 该编译指示生成对symbol_name的导入引用。同如下汇编语言相同:IMPORT symbol_name。符号名作为外部符号放在映像的符号表中。 ? ?? ?? ? o arm section section_sort_list This pragma specifies the code or data section name that used for subsequent function or objects.This include definitions of anonymous objects the compiler creates for initializations.该编译指示可指定代码或数据段的名称用于随后的函数或对象。包括编译程序为初始化而创建的匿名对象的定义。该选项对一下情况没有影响: 内联函数(及其局部静态变量) 模板实例(及其局部静态变量) 消除未使用的变量和函数 将定义写入目标文件中的顺序 ? ?? ?? ?? ?? ?? ?? ?? ?该编译指示完整语法为: ? ?? ?? ?? ?? ?? ?? ?? ?? ?? ???#pragma arm section [sort_type[[=]“name”]][,sort_type=?? “name”] ? ?? ?? ?? ?? ?? ?? ?? ?此处name用于段名称,sort_type可为如下之一code,rwdata,rodata ? ?? ?? ?? ?? ?? ?? ?? ?和zidata。若指定sort_type,没有指定name,则sort_type的段名被 ? ?? ?? ?? ?? ?? ?? ?? ?重新设置为默认值。单独输入#pragma arm section,则所以对象段的 ? ?? ?? ?? ?? ?? ?? ?? ? 恢复为其默认值 int x1 = 5;? ?? ?? ?? ?? ?? ?? ?// in .data (default) ? ? int y1[100];? ?? ?? ?? ?? ?? ???// in .bss (default) ? ? int const z1[3] = {1,2,3};? ?? ?// in .constdata (default) #pragma arm section rwdata = "foo",rodata = "bar" int x2 = 5;? ?? ?? ?? ?? ?? ?? ?// in foo (data part of region) int y2[100];? ?? ?? ?? ?? ?? ???// in .bss ? ???int const z2[3] ={1,3};? ?? ?// in bar ??char *s2 = "abc";? ?? ?? ?? ?? ?// s2 in foo,"abc" in bar ??#pragma arm section rodata ??int x3 = 5;? ?? ?? ?? ?? ?? ?? ?// in foo ??int y3[100];? ?? ?? ?? ?? ?? ???// in .bss ? ?? ?int const z3[3] ={1,3};? ?? ?// in .constdata ??char *s3 = "abc";? ?? ?? ?? ?? ?// s3 in foo,"abc" in .constdata ??#pragma arm section code = "foo" ? ?int add1(int x)? ?? ?? ?? ?? ?? ? // in foo (code part of region) ? ?? ?{ ? ?? ???return x 1; ? ?? ?} ? ? #pragma arm section code 使用分散加载描述文件和链接程序,以控制将命名段放置在存储器中 ? ?? ?? ?? ?? ?? ?? ?? ? 的特定地址。 ·? ?? ?? ? Function keywords 一些关键字指示编译程序对其某个函数进行特殊处理。包括函数内的声明,函数限定符及函数存储类限定符。即Declarations inside function,Function qualifiers and Function storage. ? ?? ?__asm{assembler-code} 指示编译程序该语句是用汇编语言编写的。 ? ?? ?__irq? ?? ?This enables a C or C function to be used as an interrupt routine called by the IRQ,or FIQ vectors. All corrupted registers except floating-point registers are preserved,not only those that are normally preserved under the ATPCS. The default ATPCS mode must be used. The function exits by setting the pc to lr-4 and the CPSR to the value in SPSR. It is not available in tcc or tcpp. No arguments or return values can be used with __irq functions. ? ?? ?__pure? ?? ?指明函数声明为纯的。纯函数没有了公共子表达式。默认情况下,函数假定是不纯的(产生副作用)。纯函数需要满足:其结果仅取决于其自变量的值;没有副作用,其不能调用非纯函数。不能使用全局变量或废弃指针,同一参数两次调用纯函数,返回应该相同。
?
//===========================================
一般而言,一个程序包括只读的代码段和可读写的数据段。在ARM的集成开发环境中,只读的代码段和常量被称作RO段(ReadOnly);可读写的全局变量和静态变量被称作RW段(ReadWrite);RW段中要被初始化为零的变量被称为ZI段(ZeroInit)。对于嵌入式系统而言,程序映象都是存储在Flash存储器等一些非易失性器件中的,而在运行时,程序中的RW段必须重新装载到可读写的RAM中。这就涉及到程序的加载时域和运行时域。简单来说,程序的加载时域就是指程序烧入Flash中的状态,运行时域是指程序执行时的状态。对于比较简单的情况,可以在ADS集成开发环境的ARM LINKER选项中指定RO BASE和RW BASE,告知连接器RO和RW的连接基地址。对于复杂情况,如RO段被分成几部分并映射到存储空间的多个地方时,需要创建一个称为“分布装载描述文件”的文本文件,通知连接器把程序的某一部分连接在存储器的某个地址空间。需要指出的是,分布装载描述文件中的定义要按照系统重定向后的存储器分布情况进行。在引导程序完成初始化的任务后,应该把主程序转移到RAM中去运行,以加快系统的运行速度。 ? ?? ?? ?? ?什么是arm的映像文件,arm映像文件其实就是可执行文件,包括bin或hex两种格式,可以直接烧到rom里执行。在axd调试过程中,我们调试的是axf文件,其实这也是一种映像文件,它只是在bin文件中加了一个文件头和一些调试信息。映像文件一般由域组成,域最多由三个输出段组成(RO,RW,ZI)组成,输出段又由输入段组成。所谓域,指的就是整个bin映像文件所处在的区域,它又分为加载域和运行域。加载域就是映像文件被静态存放的工作区域,一般来说flash里的 整个bin文件所在的地址空间就是加载域,当然在程序一般都不会放在 flash里执行,一般都会搬到sdram里运行工作,它们在被搬到sdram里工作所处的地址空间就是运行域。我们输入的代码,一般有代码部分和数据部分,这就是所谓的输入段,经过编译后就变成了bin文件中ro段和rw段,还有所谓的zi段,这就是输出段。对于加载域中的输出段,一般来说ro段后面紧跟着rw段,rw段后面紧跟着zi段。在运行域中这些输出段并不连续,但rw和zi一定是连着的。zi段和rw段中的数据其实可以是rw属性。 ? ?? ?? ?? ?? ? | Image$$RO$$Base| |Image$$RO$$Limit| |Image$$RW$$Base| |Image$$ZI$$Base| |Image$$ZI$$Limit|这几个变量是编译器通知的,我们在 makefile文件中可以看到它们的值。它们指示了在运行域中各个输出段所处的地址空间| Image$$RO$$Base| 就是ro段在运行域中的起始地址,|Image$$RO$$Limit| 是ro段在运行域中的截止地址。其它依次类推。我们可以在linker的output中指定,在 simple模式中,ro base对应的就是| Image$$RO$$Base|,rw base 对应的是|Image$$RW$$Base|,由于rw和zi相连,|Image$$ZI$$Base| 就等于|Image$$ZI$$limit| .其它的值都是编译器自动计算出来的。 ? ?? ?? ?? ?? ???下面是2410启动代码的搬运部分,我给出注释 ? ?? ?? ?? ?BaSEOfROM DCD |Image$$RO$$Base| ? ?? ?? ?? ?TopOfROM DCD |Image$$RO$$Limit| ? ?? ?? ?? ?BaSEOfBSS DCD |Image$$RW$$Base| ? ?? ?? ?? ?BaSEOfZero DCD |Image$$ZI$$Base| ? ?? ?? ?? ?EndOfBSS DCD |Image$$ZI$$Limit| ? ?? ?? ?? ?adr r0,ResetEntry;? ? ResetEntry是复位运行时域的起始地址,在boot ? ?? ?? ?? ?nand中一般是0 ? ?? ?? ?? ?ldr r2,BaSEOfROM; ? ?? ?? ?? ?cmp r0,r2 ? ?? ?? ?? ?ldreq r0,TopOfROM;TopOfROM=0x30001de0,代码段地址的结束 ? ?? ?? ?? ?beq InitRam ? ?? ?? ?? ?ldr r3,TopOfROM ? ?? ?? ?? ?;part 1,通过比较,将ro搬到sdram里,搬到的目的地址从 | Image$$RO$$Base| 开始,到|Image$$RO$$Limit|结束 ? ?? ?? ?? ? ? ?? ?? ?? ?0 ? ?? ?? ?? ?ldmia r0!,{r4-r7} ? ?? ?? ?? ?stmia r2!,{r4-r7} ? ?? ?? ?? ?cmp r2,r3 ? ?? ?? ?? ?bcc %B0; ? ?? ?? ?? ? ? ?? ?? ?? ?;part 2,搬rw段到sdram,目的地址从|Image$$RW$$Base| 开始,到|Image$$ZI$$Base|结束 ? ?? ?? ?? ?sub r2,r2,r3;r2=0 ? ?? ?? ?? ?sub r0,r0,r2? ? ? ?? ?? ?? ?InitRam ;carry rw to baSEOfBSS ? ?? ?? ?? ?ldr r2,BaSEOfBSS ;TopOfROM=0x30001de0,baSEOfrw ? ?? ?? ?? ?ldr r3,BaSEOfZero ;BaSEOfZero=0x30001de0 ? ?? ?? ?? ?0 ? ?? ?? ?? ?cmp r2,r3 ? ?? ?? ?? ?ldrcc r1,[r0],#4 ? ?? ?? ?? ?strcc r1,[r2],#4 ? ?? ?? ?? ?bcc %B0 ? ?? ?? ?? ?;part 3,将sdram zi初始化为0,地址从|Image$$ZI$$Base|到|Image$$ZI$$Limit| ? ?? ?? ?? ?mov r0,#0;init 0 ? ?? ?? ?? ?ldr r3,EndOfBSS;EndOfBSS=30001e40 ? ?? ?? ?? ?1 ? ?? ?? ?? ?cmp r2,r3 ? ?? ?? ?? ?strcc r0,#4 ? ?? ?? ?? ?bcc %B1
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
相关内容
推荐文章
站长推荐
- Laravel中的依赖注入之IoC容器应用详解
- ruby-on-rails – EOFError错误尝试使用Amazon S
- SQLite 入门教程(二)创建、修改、删除表
- ruby-on-rails – Rails在控制器定义中不需要索引
- 设计模式六大原则(3):依赖倒置原则
- c# – 如何根据某个属性选择一个xml元素,并将其写
- 【Cocos2d-x 3.x】 调度器Scheduler类源码分析
- React/React Native 的ES5 ES6写法对照表
- ruby-on-rails – 在helper中调用yield
- Practical NoSQL - Solving a Real Problem with
热点阅读