原文 https://blog.csdn.net/weixin_42616791/article/details/105389167
內存映射
在一些桌面程序中,整個內存映射是通過虛擬內存來進行管理的,使用一種稱為內存管理單元(MMU)的硬件結構來將程序的內存映射到物理RAM。在對於 RAM 緊缺的嵌入式系統中,是缺少 MMU 內存管理單元的。因此在一些嵌入式系統中,比如常用的 STM32 來講,內存映射被划分為閃存段(也被稱為Flash,用於存儲代碼和只讀數據)和RAM段,用於存儲讀寫數據。
STM32 的 Flash 和 RAM 地址范圍
筆者標題所說的內存是指 STM32 的 Flash 和 RAM,下圖是 ARM Cortex M3 的地址映射圖:
從圖中我們可以看到 RAM 地址是從 0x2000 0000 開始的,Flash地址是從 0x0800 0000 開始的,筆者將在下文中着重對這兩部分進行剖析。
Flash
代碼和數據是存放在 flash 中的,下面是將 flash 內部進行細分之后的一張圖,圖中標明了代碼段,數據段以及常量在 flash 中的位置。
如上圖所示,Flash 又可以細分為這么幾個部分,分別是文本段 (Text),其中文本段中又包含可執行代碼 (Executable Code)和常量 (Literal Value),在文本段之后就是只讀數據區域 (Read Only Data),當然並不是所有架構的單片機都滿足這樣一個排布規律,這里只針對ARM Cortex M3 系列,只讀數據段后面接着的就是數據復制段 (Copy of Data Section),第一次遇到這個概念的朋友看到數據復制可能會有所疑惑,其實這個段充當的作用是存放程序中初始化為非 0 值的全局變量的初始值,之所以要將初始值存放到這里,是因為全局變量是存放在 RAM 上的,RAM 上的值掉電便丟失,每次上電后這些變量是要進行重新賦值的,而重新賦的值就存放在這里。那為什么不存放初始化為 0 的全局變量初始值呢,原因也很簡單,既然是初始化為 0,那么在上電后統一對存放初始化為 0 的全局變量的那塊區域清0就好了。下面舉一個例子分析各個變量在上述中的存儲位置:
#include <stdio.h>
const int read_only_variable = 2000; int data = 500; void my_function(void) { int x = 200; char *str = "string"; }
在上述代碼中,read_only_variable 是一個用 const 修飾的全局變量,它是只讀的,存放在 flash 中的只讀數據區域,編譯器會給 read_only_variable 分配一個地址,並將 2000 這個數據存放到這個位置。data 這個變量將存放到 RAM 中的RW區域中 (后面將會進行詳細講解),但是 data 后面的初始值 500 將會被存放到數據復制區域中, 也就是上圖中從下往上的第三個區域。在 my_function 中的變量 x 將會被存放到 RAM 中的堆棧中,將 x 賦值為 200 ,200 將被存儲到 flash 里的 Text 中的常量區 (Literal Valu) 中。str 是一個 char 型的指針變量,它指向的是字符串第一個字符存放的位置,然而對於字符串 string 來講,它是存放在Text常量區的,所以指針變量指向這個區域的一個地址,但是因為它終歸中局部變量,它指向 Flash 的一個地址,但是其本身還是存放於 RAM 中的堆棧上的。
RAM
STM32單片機的片內RAM會被鏈接文件“分區”為如下幾個段:
如上圖所示,RAM 中包含了如下幾個部分:
棧 (Stack) : 存放局部變量和函數調用時的返回地址
堆 (heap) : 由 malloc 申請,由 free 釋放
bss : 存放未初始化或者是初始化為 0 的全局變量
data : 存放初始化為非 0 值的全局變量
下面舉一個簡單的例子來說明變量在各個段中的存儲位置:
#include <stdio.h> #include <stdlib.h>
int data_var = 500; int bss_var0; int bss_var1 = 0; static int static_var; void my_function(void) { static int static_var1 = 0; int stack = 0; char *buffer; const int value = 1; buffer = malloc(10); }
上述變量的命名已經很清楚地表明了變量處於 RAM 中的哪一個段,data_var 是已經初始化的全局變量,存放在 RAM 的 data 區,bss_var0 和 bss_var1是未初始化和初始化為0的全局變量,他們都存放於 RAM 中的 bss段,由 static 修飾的static_var 和 static_var1 都存放於 bss段,區別只在於兩個變量的作用域不同。stack 是在函數內部定義的局部變量,其存放於 RAM 的棧區域,用 const 修飾的局部變量 value ,雖然他是只讀的,但是它是存儲於 RAM 中的棧中的,這里也說明一點,並不是所有用 const 修飾的變量都是存放於只讀變量區的。buffer指針變量用 malloc 函數申請了 10 字節的內存空間,那這10字節的內存空間位於堆中。
堆棧溢出
如果在程序運行的過程中,堆的空間也一直在消耗,同時棧的空間也在增加,那么這時堆和棧如果碰到一起,那么就會造成堆棧溢出,從而導致我們的程序跑飛。
STM32中的map文件分析
在用 keil 編譯 STM32 工程之后,我們會得到一個 map 文件,map 文件的最底部有這么一個信息:
上圖中的各個段是和上文所述是能夠進行對應起來的,正如下面這張表所示:
Code | RO Data | RW Data | ZI Data |
Executable Code | Read Only Data | data | bss |
總結
對於 RAM 和 flash 空間都有限的 MCU 來講,了解各個變量在內存中的存儲位置是很有必要的,他能夠很好地幫助我們去解決很多問題。