本文介绍MCU在生成Image时,Flash与RAM存储内容及占用空间计算。
1.段
我们知道在操作系统平台,如Linux上可执行二进制程序(在linux下为一个进程单元)通常包含如下几个段:
1)数据段(BSS段,DATA段)
BSS段为未初始化的数据,此部分并不占用代码段空间,DATA段为初始化的数据,此部分是需要占用代码段空间的(需要提供具体的初始化值)。
2)代码段(CODE,.RODATA)
代码段为实际可执行的代码指令,当然,代码段同样包括一些只读数据(.RODATA),如一些定义为const的变量,代码段也包含如上所提的数据段需要初始化的数据。
3)堆
堆为动态内存分配区域,需要用户在编译前制定,对于大部分情况下,MCU的堆大小被置为0。
4)栈
栈为程序执行过程中,局部变量,中断现场保护所需要的存储区域,一般情况下,函数内部定义的自动变量越多,函数嵌套层次越高,所需的栈空间也越大。需要说明的是,编译器一般只会评估静态的栈空间的大小,无法评估实际程序在运行过程中栈空间的大小,因此,这部分的设置需尤为注意。
2.Flash空间大小
殊途同归,MCU在生成Image的过程中也需要考虑这些段,如我们使用Keil uVision生成Image(bin,hex)时,查看Map文件:
图中,我们可以看到Total ROM Size是包含Code,RO Data,RW Data的。
采用gcc交叉编译器时,Print Size:
实际生成的bin文件大小:
可见,生成的bin文件大小:22976 bytes = 22944(text) + 32(data) bytes,并不包含bss段(5248 bytes)。
3.RAM空间
从Keil uVision,我们可以得出,Total RW Size包含RW Data,ZI Data的。而同样道理,采用gcc交叉编译器时,Print Size时RAM空间应包含:data,bss。
但这个地方还忽略了2个非常重要的段,堆和栈。实际,我们在评估所消耗的RAM空间时,应将这2部分也考虑进去。
如Keil uVision默认设置的堆和栈大小:
采用gcc交叉编译器,通常在配置文件中也对堆,栈也有描述:
因此,实际占用RAM空间大小为:data,bss,stack,heap的总和。
4.初始化
我们知道,对于C生成的可执行文件入口函数为main函数,但在main函数之前还有一些初始化操作,这部分通常包括startup.s和__main函数(对于keil uVision来讲),为更加清晰的描述这段过程,我们以gcc交叉编译器的entry.s为例。
_start:
j handle_reset
/* Some of the Mi-V soft IP cores support compressed 'C' extension. If the Mi-V
core in your design doesn't support 'C' extension and you enable 'C' extension
in firmware project compiler options, then it would result in a trap. For this
case, we are avoiding compressed instruction here so you can put a breakpoint
at the jump so that yo can at least look at mcause, mepc and get some hints
about the crash. */
trap_entry:
.option push
.option norvc
j generic_trap_handler
.option pop
.word 0
.word 0
/* 省略 */
generic_reset_handling:
/* Copy sdata section first so that the gp is set and linker relaxation can be
used */
la a4, __sdata_load
la a5, __sdata_start
la a6, __sdata_end
beq a4, a5, 1f /* Exit if source and dest are same */
beq a5, a6, 1f /* Exit if section start and end addresses are same */
call block_copy
1:
/* initialize global pointer */
la gp, __global_pointer$
.option pop
/* Floating point support configuration */
#ifdef __riscv_flen
csrr t0, mstatus
lui t1, 0xffffa
addi t1, t1, -1
and t0, t0, t1
lui t1, 0x4
or t1, t0, t1
csrw mstatus, t1
lui t0, 0x0
fscsr t0
#endif
call initializations
/* Initialize stack pointer */
la sp, __stack_top
/* Jump into C code */
j _init
/* Error: trap_entry is not at the expected address of reset_vector+mtvec offset
as configured in the MIV_RV32 core vectored mode */
vector_address_not_matching:
ebreak
initializations:
/* Initialize the .bss section */
mv t0, ra /* Store ra for future use */
la a5, __bss_start
la a6, __bss_end
beq a5, a6, 1f /* Section start and end address are the same */
call zeroize_block
1:
/* Initialize the .sbss section */
la a5, __sbss_start
la a6, __sbss_end
beq a5, a6, 1f /* Section start and end address are the same */
call zeroize_block
/* Clear heap */
la a5, __heap_start
la a6, __heap_end
beq a5, a6, 1f /* Section start and end address are the same */
call zeroize_block
1:
/* Copy data section */
la a4, __data_load
la a5, __data_start
la a6, __data_end
beq a4, a5, 1f /* Exit early if source and dest are same */
beq a5, a6, 1f /* Section start and end addresses are the same */
call block_copy
1:
mv ra, t0 /* Retrieve ra */
ret
zeroize_block:
bltu a6, a5, block_copy_error /* Error. End address is less than start */
or a7, a6, a5 /* Check if start or end is unalined */
andi a7, a7, 0x03u
bgtz a7, block_copy_error /* Unaligned addresses error*/
zeroize_loop:
sw x0, 0(a5)
add a5, a5, __SIZEOF_POINTER__
blt a5, a6, zeroize_loop
ret
block_copy:
bltu a6, a5, block_copy_error /* Error. End address is less than start */
or a7, a6, a5 /* Check if start or end is unalined */
andi a7, a7, 0x03u
bgtz a7, block_copy_error /* Unaligned addresses error*/
block_copy_loop:
lw a7, 0(a4)
sw a7, 0(a5)
addi a5, a5, 0x04
addi a4, a4, 0x04
blt a5, a6, block_copy_loop
j block_copy_exit
block_copy_error:
j block_copy_error
block_copy_exit:
ret
初始化流程:
generic_reset_handling->initializations->init data/bss/heap->_init->main
可以清晰的看到初始化过程中对bss,data,heap,stack的初始化。