STM32 記憶體分配解析及變數的儲存位置

wenzid發表於2020-04-29

記憶體對映

在一些桌面程式中,整個記憶體對映是通過虛擬記憶體來進行管理的,使用一種稱為記憶體管理單元(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_var0bss_var1是未初始化和初始化為0的全域性變數,他們都存放於 RAM 中的 bss段,由 static 修飾的static_varstatic_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 來講,瞭解各個變數在記憶體中的儲存位置是很有必要的,他能夠很好地幫助我們去解決很多問題。

最後如果您覺得我的文章對您有所幫助,歡迎關注我的個人公眾號:wenzi嵌入式軟體
在這裡插入圖片描述

相關文章