RTOS Thread stack and MSP/PSP registers in ARM Cortex-M3

Background

使用Keil RTX RTOS的项目开发过程中,在加入一些新的代码之后,发现在线程们被创建并被启动之后,程序就跑飞了。

借助Keil的RTOS debug窗口,发现有其中2个线程有stack overflow的现象。

于是开始思考RTOS thread stack size的设置问题。

以前一直就对有了RTOS之后,线程栈和内核栈是个什么情况。Cortex-M3的MSP和PSP该如何使用,这些都不是很清楚。

正好借此机会,好好研究一番。

 

本文基于以下开发环境:

Cortex-M3,Keil MDK 5。

C memory Modle

先上一张图,看看一般意义上的无OS情况下,ARM C语言执行环境下,RAM的布局。明显这张图是基于“向下增长”的栈结构来设计的。

从上图可以看到:

RW空间,位于RAM的最低地址区域。用来存放,已经被初始化了的,可读写的,全局变量的值;(提个问题,到底什么叫全局变量,什么叫全局变量的内容?)

ZI空间,位于RW的上方。用来存放,未被初始化的,可读写的全局变量的值;

Heap空间,位于ZI的上方。c函数malloc和calloc等内存分配函数,会从这块区域里面取一块内存区域,给函数的调用者;

Stack空间,位于RAM的最上方。C函数的自动变量(也叫局部变量),以及函数返回地址,会被保存在这里。

Stack空间从高位向低位增长,Heap从低位向高位增长。所以如果控制不好,会出现Heap和Stack overlap的情况。

可以参考:http://stackoverflow.com/questions/39113658/when-does-malloc-return-null-in-a-bare-metal-environment

那么这个c memory model是怎么形成的呢?

当我们写好了一个c程序,开始build这个程序。

这个程序会被预处理、编译、链接,形成一份image。编译器会分辨出:代码指令,常量,已初始化的可读写全局变量,未初始化的可读写全局变量,自动变量,内存分配函数。

链接脚本(link script)会告诉链接器,在链接的时候这些东西在flash里面放哪里,在RAM里面放哪里。

那么我们关心的栈,是在哪里设置栈顶是在RAM的最高位,而heap在ZI的上面呢?

CPU里面有个SP寄存器,又叫栈指针寄存器,指向程序运行时栈的实时位置。

往栈里面PUSH东西啦,SP寄存器的数值就减小;从栈里面POP东西啦,SP寄存器的数值就增加。

在CPU刚开始复位,开始从复位中断向量执行第一条指令的时候,都是汇编,但我们也得先把CPU初始化、栈指针初始化啊,这样后面的c程序才能开始跑。

一般在这里,我们都是通过汇编指令设置SP寄存器为RAM的最高地址,也就是指定了栈顶在哪里。

而Heap底部,则是链接器自动根据RW/ZI空间大小计算出来的,等于ZI空间的顶部。

 

Keil MDK下Cortex-M3的C Memory Model, without RTOS

那么在Keil MDK下,Cortex-M3的C memory model又是个什么样子呢。

为了说明,先上图。

在不跑RTOS的情况下,这个时候,整个C程序都只会用到main stack,为什么?请去翻翻cortex-m3 技术手册。

当然高级一点,可以自己去设置process stack,然后让用户程序在process stack里面跑,让中断函数再main stack里面跑。

先看没有RTOS,并且代码里面调用了malloc或者calloc这类内存分配函数的情况:

      +--------+   Last Address of RAM  
      |  not   |
| used | +--------+ MSP RAM | main |
| stck | | | +--------+ Heal_limit | ^ | | | | | Heap | +--------+ | ZI | +--------+ | RW | +========+ First Address of RAM

从图上可以看到RAM的顶部有一段空间,是没有被c程序用到的,相当于浪费掉了。

在这段空间的下面才是Main stack,MSP指向了这段空间的最上面。

Heap 在main stack的下面。

startup.s里面设置了heap和main stack的size,Keil toolchain会计算heap的起始地址,main stack的起始地址,防止出现heap和stack overlap的情况。

设置见下图的“Stack Configuration”和“Heap Configuration”,默认都是0x400 = 1k bytes。

 

 

以后c程序,就从heap里面去取memory,往main stack里面存自动变量和函数返回地址。

注意,如果你的c代码里面没有调用malloc这类函数,那么是不会最后生成的image,去看map file,是不会有heap这段空间的。

我们来看看没有RTOS的时候,并且没有调用malloc这类函数,最后image的map file是什么样子的。没错,没有heap,只有stack。

    Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000418, Max: 0x00002000, ABSOLUTE)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x20000000   0x00000004   Data   RW            4    .data               main.o
    0x20000004   0x00000014   Data   RW          236    .data               system_gd32f1x0.o
    0x20000018   0x00000400   Zero   RW         3328    STACK               startup_gd32f1x0.o

那我们再来看看没有RTOS的时候,有调用malloc这类函数,最后image的map file是什么样子的。看到没有,有heap,也有stack。并且size都是在之前的startup.s中设置的。

    Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x00000820, Max: 0x00002000, ABSOLUTE)

    Base Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x20000000   0x00000004   Data   RW            4    .data               main.o
    0x20000004   0x00000014   Data   RW          239    .data               system_gd32f1x0.o
    0x20000018   0x00000004   Data   RW         3387    .data               mc_w.l(mvars.o)
    0x2000001c   0x00000004   Data   RW         3388    .data               mc_w.l(mvars.o)
    0x20000020   0x00000400   Zero   RW         3332    HEAP                startup_gd32f1x0.o
    0x20000420   0x00000400   Zero   RW         3331    STACK               startup_gd32f1x0.o

再来看这段调用malloc代码经过Keil编译之后,统计的code大小,data大小。

看到没有RW size = 2080 bytes。而上面显示的所有RAM中被用到的空间大小为0x00000820 = 2080 bytes。

是的,ZI Data包含了stack和heap空间的。所以keil编译出来的结果,就是最后ram占用的空间大小。而这个和c memory model里面讲的是不一样的。

==============================================================================

    Total RO  Size (Code + RO Data)                 1400 (   1.37kB)
    Total RW  Size (RW Data + ZI Data)              2080 (   2.03kB)
    Total ROM Size (Code + RO Data + RW Data)       1432 (   1.40kB)

==============================================================================

Keil MDK下Cortex-M3的C Memory Model, with Keil RTX RTOS

在有了RTOS之后,就多了RTOS内核和线程。RTOS内核代码,中断函数,以及启动代码,使用main stack。线程使用process stack。

整个C程序就有2个栈指针寄存器,MSP/PSP。MSP指向main stack的top address,PSP指向线程stack的top address。

于是就有了下面的图。

可以看到heap在main stack的下面,process stack在heap的下面。

process stack其实是RTOS代码里面的一个全局数组。

每创建一个线程,就从数组里面拿一块空间作为这个线程的栈空间。

这个数组的大小,由RTX_Conf_CM.C里面的stack number,以及每个stack的栈大小这两者的乘积决定。

  1. 当从线程陷入到RTOS内核时,就使用MSP;
  2. 当在RTOS内核中,需要从线程1调度掉线程2时,则修改PSP,将其值从线程1的栈顶,修改到线程2的栈顶。
      +--------+   Last Address of RAM  
      |  not   |
| used | +--------+ MSP RAM | main |
| stck | | | +--------+ Heal_limit | ^ | | | | | Heap | +--------+ PSP
| Process|
| Stack |
| |
+--------+ | ZI | +--------+ | RW | +========+ First Address of RAM
我们再来看看有RTOS的时候,map file是什么样子的吧。因为内容比较多,只截取了其中一部分:
Execution Region RW_IRAM1 (Base: 0x20000000, Size: 0x000018a8, Max: 0x00002000, ABSOLUTE, COMPRESSED[0x00000084])

    Base Addr Size Type Attr Idx E Section Name Object
    0x2000016c   0x00000001   Data   RW         7640    .data               RTX_CM3.lib(hal_cm.o)
    0x2000016d   0x00000003   PAD
    0x20000170   0x00000008   Data   RW         7684    .data               RTX_CM3.lib(rt_robin.o)
    0x20000178   0x00000004   Data   RW         8029    .data               mc_w.l(stdout.o)
    0x2000017c   0x0000003c   Zero   RW          250    .bss                pse_hostcomm.o
    0x200001b8   0x000001b0   Zero   RW          484    .bss                pse_pwrmgmt.o
    0x20000368   0x00000010   Zero   RW         6324    .bss                thread.o
    0x20000378   0x00000010   Zero   RW         6325    .bss                thread.o
    0x20000388   0x00000010   Zero   RW         6326    .bss                thread.o
    0x20000398   0x00000020   Zero   RW         6401    .bss                rtx_conf_cm.o
    0x200003b8   0x00000110   Zero   RW         6402    .bss                rtx_conf_cm.o
    0x200004c8   0x00000c90   Zero   RW         6403    .bss                rtx_conf_cm.o
    0x20001158   0x00000250   Zero   RW         6404    .bss                rtx_conf_cm.o
    0x200013a8   0x00000084   Zero   RW         6405    .bss                rtx_conf_cm.o
    0x2000142c   0x00000014   Zero   RW         6406    .bss                rtx_conf_cm.o
    0x20001440   0x00000034   Zero   RW         7141    .bss                RTX_CM3.lib(rt_task.o)
    0x20001474   0x00000030   Zero   RW         7361    .bss                RTX_CM3.lib(rt_list.o)
    0x200014a4   0x00000004   PAD
    0x200014a8   0x00000400   Zero   RW         6457    STACK               startup_gd32f1x0.o


==============================================================================

这个里面标STACK的,其实是main stack,采用默认值为0x400. 而下面这段则是所有线程栈空间的总和:

 0x200004c8   0x00000c90   Zero   RW         6403    .bss                rtx_conf_cm.o

 

Keil的统计数据如下,其中RW size刚好等于上面所占的RAM空间大小:Size = 0x000018a8 = 6312 bytes。也就是所Keil给出的结果,RW data + ZI data就是最后代码运行时占用的RAM大小,包含了所有的stack以及RW/ZI data空间。
==============================================================================

    Total RO  Size (Code + RO Data)                38196 (  37.30kB)
    Total RW  Size (RW Data + ZI Data)              6312 (   6.16kB)
    Total ROM Size (Code + RO Data + RW Data)      38328 (  37.43kB)

==============================================================================

 




posted @ 2016-08-24 14:45  ironX  阅读(3153)  评论(2编辑  收藏  举报