天天看點

嵌入式 - 代碼看加載【1】IDE 生成的代碼大小引言生成資料區分參考資料

更新曆史

20220318

  1. 首次編輯,簡述 keil 工程生成的代碼大小,以及存放的空間位置;

文章目錄

  • 引言
  • 生成資料區分
    • 各資料含義
      • 為什麼 RW 資料有兩份?
    • 如何縮減空間
      • 閃存空間
      • RAM 空間
        • FreeRTOS 堆空間簡述
  • 參考資料

引言

這篇文章介紹了 keil 工程生成代碼的各中 data 的介紹,以及在位址空間中的存放差異。

依舊是【固件防複制系列】的衍射知識總結,在真正讨論程式加載以及堆棧的配置設定及使用問題前,先以 keil 為實踐 IDE 對象來簡單描述下代碼生成後,各種資料的放置空間,以及若存在空間不足情況下,代碼的簡單體積縮減。

生成資料區分

各資料含義

嵌入式 - 代碼看加載【1】IDE 生成的代碼大小引言生成資料區分參考資料

圖1 keil 編譯資訊截圖

這裡我們可以看到:

這裡的數值,機關均為位元組 Byte。

先分别來說下,各部分的含義:

  1. Code: 代碼大小

    這個很容易了解,也是我們程式最主要的部分;90512 位元組

  2. RO-data: Read Only data, 隻讀性質的資料

    如字元串,const 資料;4184 位元組

  3. RW-data: Read Write data, 可讀可寫性質的資料

    已初始化的全局變量;820 位元組

  4. ZI-data: Zero Initialized, 初始值為 0 的資料

    未初始化的全局變量;44140 位元組

關于其放置位置,也是根據 MCU 存儲媒體的性質以及本身的操作類型來決定的,我們知道 MCU 的閃存或 Flash 空間其屬于 ROM 存儲器,适合存放隻讀或初始化值非 0 的資料,而 RAM 則适合存放反複修改的資料類型。

(RW-data 會先配置設定到閃存,但是由于其 RW 的性質,會在 RAM 中進行拷貝。)

則會有下面的配置設定:

Flash_occupied = Code + RO-data + RW-data = 95516 Bytes = 93 MB
RAM_occupied = RW-data + ZI-data = 44960 Bytes = 43 MB
           

為什麼 RW 資料有兩份?

RW 我們知道是初始化且非 0 的全局變量,也就是說相當于我們看書,看到了某個進度,需要下次再看,這裡就需要記錄一下進度。

由于 RAM 是掉電即失,則不會也不适合将這個進度的記錄放到 RAM 中(程式的放置或者映射位置是編譯器工具鍊進行設定的,而資料本身的讀寫性質是工程師進行編碼确定的,映射放置時隻看本身的讀寫性質),而又由于程式運作中,會對其進行讀寫(産品電源周期内的運作态),是以需要一塊掉電不易失的存儲空間來存儲,這就會将其放置到了閃存及 Flash 空間。

相應的, ZI-data 放在了 RAM 區,因為其是未初始化或者初始化值為 0 的變量,則不用占用閃存空間(相當于,知道是新書,也不用記錄這次看到哪了,拿起來看就完了),運作中進行預設初始化為 0 .

注:關于加載的知識,詳細請見《程式員的自我修養》,具體版本請見參考資料章節。

如何縮減空間

在嵌入式開發領域中,嵌入式資源是寶貴的,不免會遇到代碼體積過大或需要進行優化的情況,單單從代碼體積來看,可以如何縮小占用空間。

想要有的放矢的縮減尺寸,需要了解哪些資料屬于 RO, RW 和 ZI。

閃存空間

  1. 優化代碼文法的使用;如替換 while(1) 為 for( ; ; ),這樣彙編語句會減少,且不占用寄存器,沒有判斷和跳轉,空間和時間上都具有優勢:
while (1);

// while(1) 編譯後
mov eax,1
test eax,eax
je foo+23h
jmp foo+18h


for(;;);
// 編譯後
jmp foo+23h
           
  1. 結構體成員之間的排列問題;
  2. 不需要的字元串或 const 常量的删減;(RO-data區)
  3. 。。。

RAM 空間

這裡的 RAM 一定程度上可以了解為運作時用的空間,涉及到動态建立、修改和通路的資料及資料結構都會放置于這裡,比如一些任務控制塊、堆棧等。

簡化這部分的使用大小:

  1. 避免棧空間的大量使用,會導緻記憶體溢出;
  2. 簡化函數調用;
  3. 避免一些不用但是依舊聲明的數組等;
  4. 。。。

如我在代碼中注釋掉了僵屍代碼,一個在源檔案中申明的全局數組(外部源檔案沒有引用):

我們可以看到,這個資料為 64 長的 32 位元組元素長度的數組,計算下來就需要 256 位元組長度的 RAM 空間,這裡注釋掉後,再次編譯:

嵌入式 - 代碼看加載【1】IDE 生成的代碼大小引言生成資料區分參考資料

圖2 keil 編譯資訊截圖(删除備援代碼後)

這裡我們對比圖1,發現 ZI-data 的大小減少了 44140-43884=256 Byte,也就是我們剛才注釋掉的數組大小。

注:這裡隻是示範了一個很簡單的政策,當生成代碼大小與預期不一緻,有些許出入也是很正常的。

FreeRTOS 堆空間簡述

這裡簡單提一下,如果包含了作業系統的話,比我們産品中使用的是 FreeRTOS,其是在 RAM 區申請了一個較大空間的數組,也就是連續存儲空間以作為其本身資源的消耗及管理,這部分是計算在 ZI-data 空間内的,具體大小檢視宏定義,比如 v10.0.1 版本的 FreeRTOS 系統堆空間為 15M:

嵌入式 - 代碼看加載【1】IDE 生成的代碼大小引言生成資料區分參考資料

圖3 FreeRTOS 堆空間大小宏定義

嵌入式 - 代碼看加載【1】IDE 生成的代碼大小引言生成資料區分參考資料

圖4 FreeRTOS 不同記憶體管理政策下的空間申請

結合圖 3、4 我們可知,不同的記憶體管理政策,都是在 15MB 堆空間大小的前提下,至于不同的記憶體管理政策具體有什麼不同,請參考 FreeRTOS 官網手冊: Memory Management; 根據自身開發需求來標明政策。

參考資料

  1. stackoverflow - ROM and RAM in ARM;
  2. Arm Cortex-M4 Processor Technical Reference Manual;
  3. FreeRTOS Developer Docs;
  4. 《程式員的自我修養 - 連結、轉載與庫》,俞甲子 石凡 潘愛民 著,中國工信出版社;