参考
- 运行时访问
__initial_sp
和__heap_base
无RTOS时的情况
在以上配置的情况下,生成工程。在工程的startup.s
文件中,由如下代码:
Stack_Size EQU 0x400AREA STACK, NOINIT, READWRITE, ALIGN=3
__Stack_top ; 自己添加
Stack_Mem SPACE Stack_Size
__initial_spIF :DEF:__MICROLIB EXPORT __initial_spEXPORT __heap_baseEXPORT __heap_limitEXPORT __Stack_top ; 自己添加
ELSEIMPORT __use_two_region_memoryEXPORT __user_initial_stackheap
__user_initial_stackheapLDR R0, = Heap_MemLDR R1, =(Stack_Mem + Stack_Size)LDR R2, = (Heap_Mem + Heap_Size)LDR R3, = Stack_MemBX LRALIGN
ENDIF
通过以上代码可以看出,需要使能MicroLib
才会默认导出__initial_sp
,__Stack_top
,__heap_base
,__heap_limit
这几个变量。(如果不使能MicroLib
,则需要在上面代码的ELSE
下面也添加EXPORT
语句将这几个变量导出)。
然后在main.c
中添加如下代码,查看以上这些变量的值:
extern uint32_t __heap_base, __heap_limit;extern uint32_t __Stack_top, __initial_sp;extern uint32_t __Vectors, __Vectors_End, __Vectors_Size;while (1){// 0x200013A8 ~ 0x200015A8 size:0x200 RAMprintf("heapS[0x%08x], heapE[0x%08x]\r\n", (uint32_t)&__heap_base, (uint32_t)&__heap_limit);// 0x200019A8 ~ 0x200015A8 size:0x400 RAMprintf("stackS[0x%08x], stackE[0x%08x]\r\n", (uint32_t)&__initial_sp, (uint32_t)&__Stack_top); // 0x08000000 ~ 0x080000EC ROMprintf("VectS[0x%08x], VectE[0x%08x], VectSize[0x%x]\r\n\r\n", (uint32_t)&__Vectors, (uint32_t)&__Vectors_End, (uint32_t)&__Vectors_Size);}
从上可以看到,__Stack_top
和__heap_limit
是一样的,说明这里分配的堆和栈是紧邻的。而且地址刚好也和我们在cubeMx
中定义的一致。堆和栈是在RAM
中,而中断向量表是在Flash
中。
添加FreeRTOS
从以上三张图中,可以发现,我们给FreeRTOS总共分配了3072(0xC00) Bytes HEAP
空间。而我们定义了一个defaultTask
并分配了256 * 4 = 1024 Bytes
,但是在最后的FreeRTOS Heap Usage
页面看到,defaultTask
实际使用了1144 Bytes
,剩余3072 - 1144 = 1928 Bytes
。(除了defaultTask
多使用的1144 - 1024 = 120 Bytes
(用于任务控制块TCB
)外,其余都是可以对上的。)
生成工程后,我们还是将之前的那些堆栈指针地址打印出来,看一下MCU是如何进行地址分配的。只是这里还需要添加FreeRTOS
中的堆栈信息了。首先通过追踪configTOTAL_HEAP_SIZE
可以发现,堆空间定义为了一个数组:
static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];uint8_t* osHeapPoint = ucHeap; // 由于以上变量是 static 类型,所有这里再定义一个指针指向这个 Heap 地址,以便在外部访问它;
然后我们就可以在defaultTask
任务里面添加输出以上变量地址的代码了:
extern uint32_t __heap_base, __heap_limit;extern uint32_t __Stack_top, __initial_sp;extern uint32_t __Vectors, __Vectors_End, __Vectors_Size;extern uint8_t* osHeapPoint;extern uint32_t local_addr1, local_addr2;void* os_mal_buf = malloc(50);uint32_t tast_local_val = 1;for(;;) {// 0x20002D10 ~ 0x20002F10 size:0x200 RAMprintf("HeapS[0x%08x], HeapE[0x%08x]\r\n", (uint32_t)&__heap_base, (uint32_t)&__heap_limit);// 0x20003310 ~ 0x20002F10 size:0x400 RAMprintf("StackS[0x%08x], StackE[0x%08x]\r\n", (uint32_t)&__initial_sp, (uint32_t)&__Stack_top); // // 0x08000000 ~ 0x080000EC ROMprintf("VectS[0x%08x], VectE[0x%08x], VectSize[0x%x]\r\n", (uint32_t)&__Vectors, (uint32_t)&__Vectors_End, (uint32_t)&__Vectors_Size); // 中断向量表// 0x20002110 size: 3072 Bytes(0xC00)printf("OsHeapPoint[0x%08x]\r\n", (uint32_t)osHeapPoint); // freeRTOS中定义的HEAP数组// 0x20002D18printf("Os_mal_buf[0x%08x]\r\n", (uint32_t)os_mal_buf);// 0x20002504printf("&Os_mal_buf[0x%08x]\r\n", (uint32_t)&os_mal_buf);// 0x20002508printf((char*)buf, "&tast_local_val[0x%08x]\r\n\r\n", (uint32_t)&tast_local_val); // 在freeRTOS任务中定义的局部变量// 0x20000008, 0x2000000Cprintf("&global_var1[0x%08x], &global_var2[0x%08x]\r\n", (uint32_t)&global_var1, (uint32_t)&global_var2); // 函数外部定义的全局变量// 0x20003304, 0x20003308printf("local_addr1[0x%08x], local_addr2[0x%08x]\r\n\r\n", local_addr1, local_addr2); // 这两个全局变量保存了在freeRTOS初始化前,在main()函数中定义的两个局部变量的地址}
根据以上输出总结如下:
- 从
local_addr
的地址(靠近__initial_sp
)可以看出,栈空间的地址值是向下增长的,即栈顶(__initial_sp = 0x20003310
)在高地址; - 从全局变量的地址可以看出,全局变量是默认分配在
RAM
的起始地址; - 在
freeRTOS
任务中定义的局部变量(&tast_local_val = 0x20002508
)存储在定义freeRTOS
时定义的HEAP
里(0x20002110~0x20002D10
); - 再看一下
&tast_local_val = 0x20002508
这个数据,发现0x20002508
大概等于0x20002110 + 256*4 = 0x20002510
。这就再次说明,freeRTOS
定义任务时,是从配置好的HEAP
地址初始空间的开始部分给新任务分配Task stack
的,而在任务中使用这个task stack
时,还是符合栈的使用规范(即从高地址开始分配); - 从
s_mal_buf = 0x20002D18
可以知道,即使在freeRTOS
任务中动态分配,也是在任务之外的堆中分配空间。
疑问
- 通过以上总结,发现在
freeRTOS
任务中定义局部变量和在freeRTOS
任务之外定义局部变量,他们分配的地址空间是不一样的。那么,如果有以下函数:
void addr_test(void)
{uint32_t loc_var = 1;printf("&loc_var = 0x%08x\r\n", (uint32_t)&loc_var);
}
我分别在freeRTOS
初始化之前调用,以及在freeRTOS
的任务中调用,其输出会是什么呢?
经过验证,在freeRTOS
初始化之前调用,输出的地址范围在0x20002F10 - 0x20003310
之间;而在freeRTOS
的任务中调用,输出的地址范围在0x20002110 - 0x20002D10
之间。