Background 使用Keil RTX RTOS的項目開發過程中,在加入一些新的代碼之後,發現線上程們被創建並被啟動之後,程式就跑飛了。 藉助Keil的RTOS debug視窗,發現有其中2個線程有stack overflow的現象。 於是開始思考RTOS thread stack size的設 ...
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的棧大小這兩者的乘積決定。
- 當從線程陷入到RTOS內核時,就使用MSP;
- 當在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) ==============================================================================