以下部分摘自RT-thread开发手册:
keil编译信息
Keil 工程在编译完之后,会有相应的程序所占用的空间提示信息,如下所示:
linking...
Program Size: Code=48008 RO-data=5660 RW-data=604 ZI-data=2124
After Build- User command \#1: fromelf--bin.\\build\\rtthread-stm32.axf--output
rtthread.bin
".\\build\\rtthread-stm32.axf"- 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:07
上面提到的ProgramSize包含以下几个部分:
- 1)Code:代码段,存放程序的代码部分;
- 2)RO-data:只读数据段,存放程序中定义的常量;
- 3)RW-data:读写数据段,存放初始化为非0值的全局变量;
- 4)ZI-data:0 数据段,存放未初始化的全局变量及初始化为0的变量;
编译完工程会生成一个. map的文件,该文件说明了各个函数占用的尺寸和地址,在文件的最后几行 也说明了上面几个字段的关系:
Total RO Size (Code + RO Data) 53668 ( 52.41kB)
Total RW Size (RW Data + ZI Data) 2728 ( 2.66kB)
Total ROM Size (Code + RO Data + RW Data) 53780 ( 52.52kB)
- 1)ROSize 包含了Code及RO-data,表示程序占用Flash空间的大小;
- 2)RWSize包含了RW-data及ZI-data,表示运行时占用的RAM的大小;
- 3)ROMSize包含了Code、ROData以及RWData,表示烧写程序所占用的Flash空间的大小;
程序运行之前,需要有文件实体被烧录到STM32的Flash中,一般是bin或者hex文件,该被烧录
文件称为可执行映像文件。如图3-3中左图所示,是可执行映像文件烧录到STM32后的内存分布,它包含
RO段和RW段两个部分:其中RO段中保存了Code、RO-data的数据,RW段保存了RW-data的数据,
由于ZI-data 都是0,所以未包含在映像文件中。
STM32在上电启动之后默认从Flash启动,启动之后会将RW段中的RW-data(初始化的全局变量)
搬运到RAM中,但不会搬运RO段,即CPU的执行代码从Flash中读取,另外根据编译器给出的ZI地址
和大小分配出ZI段,并将这块RAM区域清零。
以下是读自源码
RT-Thread 隐式初始化源码解读
RT-T通过在编译阶段声明函数为初始化函数,以此来实现自动且有规律的初始化。具体宏定义应该在/rt-thread/blob/master/include/rtdef.h这个文件里,部分摘录如下 我一开始找错了,找到了MVSC的
#define INIT_EXPORT(fn, level) \
const char __rti_level_##fn[] = ".rti_fn." level; \
const char __rti_##fn##_name[] = #fn; \
__declspec(allocate("rti_fn$f")) \
rt_used const struct rt_init_desc __rt_init_msc_##fn = \
{__rti_level_##fn, fn, __rti_##fn##_name};
但是我们通常来说用的GCC的,也就是
#ifdef RT_DEBUGING_AUTO_INIT
struct rt_init_desc
{
const char* fn_name;
const init_fn_t fn;
};
#define INIT_EXPORT(fn, level) \
const char __rti_##fn##_name[] = #fn; \
rt_used const struct rt_init_desc __rt_init_desc_##fn rt_section(".rti_fn." level) = \
{ __rti_##fn##_name, fn};
#else
#define INIT_EXPORT(fn, level) \
rt_used const init_fn_t __rt_init_##fn rt_section(".rti_fn." level) = fn
首先先看结构体rt_init_desc,可以看到其是这个注册后的init宏的返回类型,由初始化等级与初始化函数构成。在最前面规定了typedef int (*init_fn_t)(void);,是所有初始化函数的统一规范(返回int,参数void)
struct rt_init_desc
{
const char* level;
const init_fn_t fn;
};
然后逐行展开。const char __rti_##fn##_name[] = #fn; ,#fn 是 C 宏里的“字符串化”,也就是说假设fn叫uart_init这个是开了一个叫做 __rti_uart_init_name[]的字符串,内容就是 uart_init这个函数名本身。
rt_used const struct rt_init_desc __rt_init_desc_##fn rt_section(".rti_fn." level)这行是比较关键的,但是大部分也一样,
rt_used看起来就像是修饰符,实际上也确实如此。搜索获知,该宏的作用是向编译器 说明这段代码有用,即使函数中没有调用也要保留编译。
例如RT-Thread自动初始化 功能使用了自定义的段,使用RT_USED会将自定义的代码段保留。
所以,返回类型为有很多修饰符的结构体rt_init_desc,名字一样是包含了fn本身,然后后面是关键的 rt_section(“.rti_fn.” level),
这个语法很奇怪,通常不符合C语言规范,但是这是在宏定义里面,所以会出现:
rt_section(“.rti_fn.” level) 不是变量名的一部分,也不是函数调用,
它是一个属性修饰,意思是:
把这个变量放到 .rti_fn.x 这个特殊段里
这样,这些就会在编译时候被归类到一个section里,日后链接就可以直接塞进初始化代码里。
也就是说展开来是这样的(假设fn是hw_usart_init)
const char __rti_rt_hw_usart_init_name[] = "rt_hw_usart_init";
rt_used const struct rt_init_desc __rt_init_desc_rt_hw_usart_init
__attribute__((section(".rti_fn.1"))) =
{
__rti_rt_hw_usart_init_name,
rt_hw_usart_init
};
值得注意的是,这里存的是函数指针,尽管宏定义完全可以复制整个函数进去,但是这里并非如此。
BSP制作的坑
最近做BSP的时候没少吃苦头,各种老旧版本包和CubeMX版本问题,搞得我头都大了。 需要注意的是,RT-Thread 的sconscript只做了老版本的Cubemx配置,也就是没有core目录
而这个生成结构是控制的,这个选项又是在ioc初始创建的时候固定的,所以就可能产生你怎么改都报错的问题 最终的解决办法就是修改ioc的文件内容,可以参考这个链接:https://community.st.com/t5/stm32cubeide-mcus/stm32cubeide-application-strucutre-is-stuck-to-advanced/td-p/782881