一、每个段的具体示意
MEMORY命令:用于定义内存块,包括Flash、SRAM等,并指定它们的起始地址和大小。
SECTIONS命令:用于定义程序的各个段如何映射到内存中。它指定了哪些源文件中的哪些段应该被放置在内存的哪个位置。
ENTRY命令(可选):指定程序的入口点。
二、MEMORY段
boot_header : org = BOOT_RESET_VECTOR_BASE, len = BOOT_RESET_VECTOR_SIZE
cpu0_reset_vec : org = CPU_Z7_0_RESET_VECTOR, len = BOOT_RESET_VECTOR_SIZE
cpu1_reset_vec : org = CPU_Z7_1_RESET_VECTOR, len = BOOT_RESET_VECTOR_SIZE
cpu2_reset_vec : org = CPU_Z4_2_RESET_VECTOR, len = BOOT_RESET_VECTOR_SIZE
cpuc_reset_vec : org = CPU_Z7_3_RESET_VECTOR, len = BOOT_RESET_VECTOR_SIZE
cpu0_reset_vec_s : org = CPU_Z7_0_FLASH_BASE_ADDR, len = 0
cpu1_reset_vec_s : org = CPU_Z7_1_FLASH_BASE_ADDR, len = 0
hsm_parsave_addr : org = HSM_SECBOOT_PARSAVE_ADDR, len = 4
hsm_parstr_addr : org = HSM_SECBOOT_PARSTR_ADDR, len = 48
cal_data_flash : org = CAL_FLASH_BASE_ADDR, len = CAL_FLASH_SIZE
m_text : org = FLASH_BASE_ADDR, len = FLASH_SIZE
m_data : org = SRAM_BASE_ADDR, len = SRAM_SIZE
m_data_share : org = SHARE_SRAM_BASE_ADDR, len = SHARE_SRAM_SIZE
cal_data_ram : org = CAL_SRAM_BASE_ADDR, len = CAL_SRAM_SIZE
local_imem : org = LOCALIMEM_BASE_ADDR, len = LOCALIMEM_SIZE
local_dmem : org = LOCALDMEM_BASE_ADDR, len = LOCALDMEM_SIZE
boot_header :
BOOT_RESET_VECTOR_BASE:0xFC0000,用于存放起始地址,长度是0x04。该段存放的内容是0x005A0000|0x00000001,分别是启动的header和z4_2的标志。attribute部分放在最前和放在uint32_t之后效果一样。
const volatile uint32_t __attribute__ ((section(".boot_header"))) boot_header1 = (BOOT_HEADER_MAGIC_WORD | BOOT_ENABLE_CPU_Z4_2);
cpu2_reset_vec :
CPU_Z4_2_RESET_VECTOR = (BOOT_RESET_VECTOR_BASE + 0x04);,长度是0x04。该段放复位向量。
cpu0_reset_vec_s和cpu1_reset_vec_s全局搜不到,应该可以删掉。
hsm_parsave_addr:
应该是HSM参数的save地址,待确认,长度是4。代码中把__HSM_PARAM_BASE_ADDR放在了这个段中。
hsm_parstr_addr:
HSM的store地址,长度是48,与前面的save地址有什么区别也待确认。hsmSecbootParam存放在其中。
cal_data_flash:
MCORE_CAL_FLASH_BASE_ADDR = 0x00FE0000;,长度64k,字面意思是存放计算量的flash标定、校准段。代码中并没有调用存放在内的数组flashCalPara,不知道具体用途。
m_text :
flash中存放代码的段,放在其中的代码不需要额外用attribute声明。
m_data :
ram中用于存放数据的段
m_data_share :
仍然是ram中用于存放数据的段,可跟m_data分开存放
cal_data_ram :
用于存放算法数据的段,后面没有分配具体的section,应该是没用到。MCORE_CAL_SRAM_BASE_ADDR = 0x40090000;,64k。
local_imem :
指令local段,用于存放快速运行的代码CPU_Z4_2_LOCALIMEM_BASE_ADDR = 0x52000000;,16k。未使用
local_dmem :
数据local段,用于存放快速访问的数据,CPU_Z4_2_LOCALDMEM_BASE_ADDR = 0x52800000;,64k。后文把对应的栈放在其中了。
MEMORY整体介绍完了,书写规则见最上面的代码。
三、SECTIONS段
bootheader
.boot_header :
{
KEEP(*(.boot_header))
} > boot_header
说明:
第一行是段的名称加分隔符;
{……}是具体的定义;
KEEP(*(.boot_header)):
中keep表示禁止优化,比如看起来像未使用,编译器也应保留;*(.boot_header)是通配符,让编译器把所有标记为boot_header的段合并到当前的区域中;
> boot_header 指的是内存中的具体位置。
复位向量:
.cpu0_reset_vector :
{
KEEP(*(.cpu0_reset_vector))
} > cpu0_reset_vec
.cpu1_reset_vector :
{
KEEP(*(.cpu1_reset_vector))
} > cpu1_reset_vec
.cpu2_reset_vector :
{
KEEP(*(.cpu2_reset_vector))
} > cpu2_reset_vec
.cpuc_reset_vector :
{
KEEP(*(.cpuc_reset_vector))
} > cpuc_reset_vec
.cpu0_reset_vector_s :
{
__CPU_Z7_0_FLASH_BASE_ADDR = .;
} > cpu0_reset_vec_s
.cpu1_reset_vector_s :
{
__CPU_Z7_1_FLASH_BASE_ADDR = .;
} > cpu1_reset_vec_s
.hsm_param_save_addr :
{
KEEP(*(.hsm_param_save_addr))
} > hsm_parsave_addr
.hsm_param_store_addr :
{
__HSM_PARAM_BASE_ADDR = .;
KEEP(*(.hsm_param_store_addr))
} > hsm_parstr_addr
其编码规格与boot_header相同,此处不在赘述。
start_up
.startup : ALIGN(0x400)
{
__CPU_Z4_2_FLASH_BASE_ADDR = .;
__start = . ;
*(.startup)
} > m_text
.start_up 段名称;
ALIGN(0x400) 是对齐指令,该段的起始地址是0x400的倍数。
__CPU_Z4_2_FLASH_BASE_ADDR = .; 指的是当前段的起始地址,其中“.”是特殊的连接器变量,表示当前的地址位置,因此 __CPU_Z4_2_FLASH_BASE_ADDR 应是.start_up段的起始地址的位置。在boot_header中__CPU_Z4_2_FLASH_BASE_ADDR被存放到了复位向量处,所以复位向量会跳转到start_up。
__start = . ;类似地,这行代码定义了一个名为 __start
的符号,其值也被设置为当前段的起始地址。然而,由于它紧跟在 __CPU_Z4_2_FLASH_BASE_ADDR
的定义之后,且两者都在 .startup
段的开始处,所以 __start
和 __CPU_Z4_2_FLASH_BASE_ADDR
实际上会有相同的值。这个符号通常用于指示程序的入口点或启动代码的开始位置。
*(.startup) 这是一个通配符模式,用于告诉链接器将所有被标记为应该放入 .startup
段的输入文件中的内容都包含进来。这通常是通过在源代码中使用特定的编译器指令(如GNU C的__attribute__((section(".startup")))
)来完成的。
> m_text
:这部分指定了 .startup
段应该被放置在输出文件的哪个部分或内存中的哪个区域。m_text
通常是一个在链接脚本中预定义的内存区域或输出部分,用于存放可执行代码。这意味着 .startup
段中的代码将被放置在 m_text
指定的内存区域中。
start_up段对应start.s中的启动段。
core_exceptions_table:异常向量
.core_exceptions_table : ALIGN(4096)
{
__IVPR_VALUE = . ;
KEEP(*(.core_exceptions_table))
} > m_text
.core_exceptions_table
:存放内核异常表;
ALIGN(4096)
:这个指令指定了段的对齐方式。起始地址是4096的倍数;
__IVPR_VALUE = .; 定义一个变量,"."表示地址,即变量_IVPR_VALUE代表这个段的起始地址;
KEEP(*(.core_exceptions_table))
:不用删除、优化这个段中的内容,并把相同的attribute合并;
> m_text
:存放的位置。
intc_vector_table:
.intc_vector_table : ALIGN(4096)
{
KEEP(*(.intc_vector_table))
} > m_data AT>m_text
> m_data AT>m_text: m_data是运行地址,m_text是存放地址,通常中断向量表需要存放到固定、硬件可知的地址,便于CPU跳转,通常会把两个地址设置成相同的值。
cal_section:
.cal_section :
{
KEEP(*(.cal_section))
KEEP(*(.cal_section.*))
} > cal_data_flash
KEEP(*(.cal_section.*))
:这个表达式进一步扩展了匹配的范围,以包括所有以 .cal_section.
开头的段。这里的 *
是一个通配符,匹配任意数量的任意字符。因此,这个表达式会匹配如 .cal_section.temp
、.cal_section.humidity
等段,并将它们也合并到 .cal_section
输出段中。这提供了额外的灵活性,允许开发者根据需要将不同的校准数据分组到不同的子段中,同时仍然能够将它们作为一个整体进行访问。
.text :
.text :
{
*(.text.startup)
*(.text)
*(.text.*)
KEEP (*(.init))
KEEP (*(.fini))
. = ALIGN(16);
} > m_text
*(.text.startup)
:这个通配符表达式匹配所有输入文件中名为 .text.startup
的段,并将它们合并到当前的 .text
输出段中。.text.startup
段通常包含程序的启动代码,如复位向量、初始化代码等,这些代码需要在程序开始执行时首先运行。
*(.text)
:这个通配符表达式匹配所有输入文件中名为 .text
的段,并将它们也合并到 .text
输出段中。这是最常见的代码段名称,用于存放程序的主要函数和指令。
*(.text.*)
:这个表达式进一步扩展了匹配的范围,以包括所有以 .text.
开头的段。这里的 *
是一个通配符,匹配任意数量的任意字符。因此,这个表达式会匹配如 .text.function1
、.text.module_a
等段,并将它们也合并到 .text
输出段中。
KEEP (*(.init))
和 KEEP (*(.fini))
:禁止优化。
. = ALIGN(16);
它要求将 .text
段的下一个段的内容对齐到16字节的边界。
ctors:、dtors:、preinit_array、init_array和fini_array 构造函数相关的内容,跟编译器相关,暂且不管。
data、data_share、sdata2、sbss2、sdata都大同小异,不再赘述。
bss:
.bss (NOLOAD) :
{
__BSS_START = .;
*(.sbss)
*(.sbss.*)
*(.bss)
*(.bss.*)
*(COMMON)
__BSS_END = .;
} > m_data
(NOLOAD)
是一个属性,表示这段内存区域在程序加载时不需要从文件(如可执行文件或库文件)中加载数据。因为 .bss
段中的数据在程序启动前被清零,所以没有必要从文件中加载任何数据到这个区域。
*(COMMON)
:这行将 COMMON
块(或称为公共块)合并到 .bss
段中。COMMON
块是在汇编语言中定义的一种特殊类型的内存区域,用于存放多个源文件之间共享的、未初始化的全局变量。在C或C++中,这通常通过外部链接的未初始化变量实现。经测试验证,确实是多.c文件使用且未初始化的全局变量会放到COMMON段。
__BSS_END = .;
这行代码设置了另一个标签 __BSS_END
,其值为 .bss
段当前的位置(即 .bss
段的结束地址)。这可以用来在程序中获取 .bss
段的长度或结束地址。
stack (NOLOAD):
.stack (NOLOAD) : ALIGN(16)
{
__HEAP = . ;
PROVIDE (_end = . );
PROVIDE (end = . );
. += __HEAP_SIZE ;
__HEAP_END = . ;
_stack_end = . ;
. += __STACK_SIZE ;
_stack_addr = . ;
__SP_INIT = . ;
. += 4;
} > local_dmem
(NOLOAD)
:与之前相同;
:ALIGN(16)
指定了段的起始地址应该对齐到16字节的边界。对齐可以提高内存访问的效率,特别是在某些处理器架构上;
__HEAP = . : 把堆当道了local_dmem中,此变量为堆的起始地址;
PROVIDE (_end = . );
和 PROVIDE (end = . );
:这两行代码通过PROVIDE
指令提供了两个符号_end
和end,
它们的值都设置为当前位置。不理解为何会有这个字符的设定。
. += __HEAP_SIZE ; 这行代码将当前位置向前移动__HEAP_SIZE
个字节,为堆预留空间,但是代码中没有设置堆空间,此处为0;
__HEAP_END = . ; 表示堆结束地址;
_stack_end = . ; 设置一个地址为栈结束地址;
. += __STACK_SIZE ;将当前位置向前移动__STACK_SIZE
个字节,以定义栈的大小,即栈顶;程序中是4k;
_stack_addr = . ;
:设置了一个标签_stack_addr
,其值为当前位置(即栈的“顶部”位置);
__SP_INIT = . ;
:设置了一个标签__SP_INIT
,其值也为当前位置。这通常用于在程序启动时初始化栈指针;
. += 4;
:将当前位置再向前移动4个字节。至于为何要+4,需要看第一压栈是怎么实现的,是否有-4的操作;
.data
段用于存储程序中已初始化的全局变量和静态变量。这些变量在程序启动时需要在内存中占据特定的位置,并且它们的初始值需要从可执行文件或库文件中加载到内存中.
.sdata
和 .sdata2
是特定于某些处理器架构(如PowerPC)的段,用于存储较小的、已初始化的全局变量和静态变量。这些段的存在是为了优化内存访问速度,特别是对于那些经常访问的小数据项。通过将它们放置在特定的内存区域,处理器可以更高效地访问这些数据.
.sdata
和 .sdata2
的具体使用取决于编译器和链接器的实现,以及目标处理器架构的特定要求。在某些情况下,编译器可能会根据变量的大小和访问频率自动决定将哪些变量放置在 .sdata
或 .sdata2
段中.
.bss
段用于存储程序中未初始化的全局变量和静态变量。与 .data
段不同,.bss
段中的变量在程序启动时不需要从可执行文件或库文件中加载初始值,因为它们的初始值默认为零(或null)。.bss
段仅记录这些变量所需的内存空间大小,以便在程序运行时为它们分配内存.
类似于 .sdata
和 .sdata2
,.sbss
和 .sbss2
也是特定于某些处理器架构的段,用于存储较小的、未初始化的全局变量和静态变量。这些段的存在同样是为了优化内存访问速度.
它们的具体使用也取决于编译器和链接器的实现,以及目标处理器架构的特定要求。在某些情况下,编译器可能会将未初始化的小变量放置在 .sbss
或 .sbss2
段中,以优化内存布局和访问效率.