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)

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

 





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM