MDK的編譯過程及文件類型全解——(四)

前言:

爲了方便查看博客,特意申請了一個公衆號,附上二維碼,有興趣的朋友可以關注,和我一起討論學習,一起享受技術,一起成長。

在這裏插入圖片描述


本文轉載自:第48章 MDK的編譯過程及文件類型全解—零死角玩轉STM32-F429系列


1. sct分散加載文件的格式與應用

1.1 sct分散加載文件簡介

當工程按默認配置構建時,MDK 會根據我們選擇的芯片型號,獲知芯片的內部 FLASH 及內部 SRAM 存儲器概況,生成一個以工程名命名的後綴爲 .sct 的分散加載文件 (Linker Control File,scatter loading),鏈接器根據該文件的配置分配各個節區地址,生成分散加載代碼,因此我們通過修改該文件可以定製具體節區的存儲位置。

例如:

(1)可以設置源文件中定義的所有變量自動按地址分配到外部 SDRAM,這樣就不需要再使用關鍵字 “__ attribut e__” 按具體地址來指定了;

(2)利用它還可以控制代碼的加載區與執行區的位置,例如可以把程序代碼存儲到單位容量價格便宜的 NAND-FLASH 中,但在 NAND-FLASH 中的代碼是不能像內部 FLASH 的代碼那樣直接提供給內核運行的,這時可通過修改分散加載文件,把代碼加載區設定爲 NAND-FLASH 的程序位置,而程序的執行區設定爲 SDRAM 中的位置,這樣鏈接器就會生成一個配套的分散加載代碼,該代碼會把 NAND-FLASH 中的代碼加載到 SDRAM 中,內核再從 SDRAM 中運行主體代碼,大部分運行 Linux 系統的代碼都是這樣加載的。

1.2 分散加載文件的格式


 ************************************************************

 ; *** Scatter-Loading Description File generated by uVision ***

 ; *************************************************************

 LR_IROM1 0x08000000 0x00100000 { ; 註釋:加載域,基地址空間大小

 ER_IROM1 0x08000000 0x00100000 { ; 註釋:加載地址 = 執行地址

 *.o (RESET, +First)

 *(InRoot$$Sections)

 .ANY (+RO)

 }

 RW_IRAM1 0x20000000 0x00030000 { ; 註釋:可讀寫數據

 .ANY (+RW +ZI)

 }

 }


在默認的 sct 文件配置中僅分配了 Code、RO-data、RW-data 及 ZI-data 這些大區域的地址,鏈接時各個節區(函數、變量等)直接根據屬性排列到具體的地址空間。

sct 文件中主要包含描述加載域及執行域的部分,一個文件中可包含有多個加載域,而一個加載域可由多個部分的執行域組成。同等級的域之間使用花括號 “{}” 分隔開,最外層的是加載域,第二層 “{}” 內的是執行域,其整體結構如下圖:

在這裏插入圖片描述

1.2.1 加載域

 //方括號中的爲選填內容

 加載域名 (基地址 | ("+"地址偏移)) [屬性列表] [最大容量]

 "{"

 執行區域描述+

 "}"

配合前面分散加載文件內容,各部分介紹如下:

(1) 加載域名: 名稱,在 map 文件中的描述會使用該名稱來標識空間。如本例中只有一個加載域,該域名爲 LR_IROM1。

(2) 基地址+地址偏移: 這部分說明了本加載域的基地址,可以使用+號連接一個地址偏移,算進基地址中,整個加載域以它們的結果爲基地址。如本例中的加載域基地址爲 0x08000000,剛好是 STM32 內部 FLASH 的基地址。

(3) 屬性列表: 屬性列表說明了加載域的是否爲絕對地址、N 字節對齊等屬性,該配置是可選的。本例中沒有描述加載域的屬性。

(4) 最大容量: 最大容量說明了這個加載域可使用的最大空間,該配置也是可選的,如果加上這個配置後,當鏈接器發現工程要分配到該區域的空間比容量還大,它會在工程構建過程給出提示。本例中的加載域最大容量爲 0x00100000,即 1MB,正是本型號 STM32 內部 FLASH 的空間大小。

1.2.2 執行域

 //方括號中的爲選填內容

 執行域名 (基地址 | "+"地址偏移) [屬性列表] [最大容量 ]

 "{"

 輸入節區描述

 "}"

執行域的格式與加載域是類似的,區別只是輸入節區的描述有所不同,前面的例子中包含了 ER_IROM1 及 RW_IRAM 兩個執行域,它們分別對應描述了 STM32 的內部 FLASH 及內部 SRAM 的基地址及空間大小。而它們內部的"輸入節區描述"說明了哪些節區要存儲到這些空間,鏈接器會根據它來處理編排這些節區。

1.2.3 輸入節區描述

配合加載域及執行域的配置,在相應的域配置"輸入節區描述",即可控制該節區存儲到域中,其格式如下:

 //除模塊選擇樣式部分外,其餘部分都可選選填

 模塊選擇樣式"("輸入節區樣式",""+"輸入節區屬性")"

 模塊選擇樣式"("輸入節區樣式",""+"節區特性")"

 模塊選擇樣式"("輸入符號樣式",""+"節區特性")"

 模塊選擇樣式"("輸入符號樣式",""+"輸入節區屬性")"

配合前面舉例的分散加載文件內容,各部分介紹如下:

(1) 模塊選擇樣式: 模塊選擇樣式可用於選擇 .o 及 .lib 目標文件作爲輸入節區,它可以直接使用目標文件名或 * 通配符,也可以使用 “.ANY”。

例如:使用語句 “bsp_led.o” 可以選擇 bsp_led.o 文件,使用語句 “.o" 可以選擇所有 .o 文件,使用 ".lib” 可以選擇所有 .lib 文件,使用 * 或 “.ANY” 可以選擇所有的 .o 文件及 .lib 文件。其中 “.ANY” 選擇語句的優先級是最低的,所有其它選擇語句選擇完剩下的數據纔會被 “.ANY” 語句選中。

(2) 輸入節區樣式: 我們知道在目標文件中會包含多個節區或符號,通過輸入節區樣式可以選擇要控制的節區。

示例文件中 “(RESET,+First)” 語句的 RESET 就是輸入節區樣式,它選擇了名爲 RESET 的節區,並使用後面介紹的節區特性控制字 “+First” 表示它要存儲到本區域的第一個地址。示例文件中的 “*(InRoot$$Sections)” 是一個鏈接器支持的特殊選擇符號,它可以選擇所有標準庫裏要求存儲到 root 區域的節區,如 __main.o、__scatter.o 等內容。

(3) 輸入符號樣式: 同樣地,使用輸入符號樣式可以選擇要控制的符號,符號樣式需要使用 “:gdef:” 來修飾。例如:可以使用 “*(:gdef:Value_Test)” 來控制選擇符號 “Value_Test”。

(4) 輸入節區屬性: 通過在模塊選擇樣式後面加入輸入節區屬性,可以選擇樣式中不同的內容,每個節區屬性描述符前要寫一個 “+” 號,使用空格或 “,” 號分隔開,可以使用的節區屬性描述符見下表:

節區屬性描述符 說明
RO-CODE & CODE 只讀代碼段
RO-DATA & CONST 只讀數據段
RO & TEXT 包括 RO-CODE & RO-DATA
RW-DATA 可讀寫數據段
RW-CODE 可讀寫代碼段
RW & DATA 包括RW-DATA & RW-CODE
ZI & BSS 初始化爲 0 的可讀寫數據段
XO 只可執行的區域
ENTRY 節區入口點

eg:

示例文件中使用 “.ANY(+RO)” 選擇剩餘所有節區 RO 屬性的內容都分配到執行域 ER_IROM1 中,使用 “.ANY(+RW +ZI)” 選擇剩餘所有節區 RW 及 ZI 屬性的內容都分配到執行域 RW_IRAM1 中。

(5)節區特性: 節區特性可以使用 “+FIRST” 或 “+LAST” 選項配置它要存儲到的位置,FIRST 存儲到區域的頭部,LAST 存儲到尾部。通常重要的節區會放在頭部,而 CheckSum (校驗和)之類的數據會放在尾部。

示例文件中使用 “(RESET,+First)” 選擇了 RESET 節區,並要求把它放置到本區域第一個位置,而 RESET 是工程啓動代碼中定義的向量表,該向量表中定義的堆棧頂和復位向量指針必須要存儲在內部 FLASH 的前兩個地址,這樣 STM32 才能正常啓動,所以必須使用 FIRST 控制它們存儲到首地址。

 ; Vector Table Mapped to Address 0 at Reset

 AREA RESET, DATA, READONLY

 EXPORT __Vectors

 EXPORT __Vectors_End

 EXPORT __Vectors_Size


 __Vectors DCD __initial_sp ; Top of Stack

 DCD Reset_Handler ; Reset Handler

 DCD NMI_Handler ; NMI Handler

總的來說,我們的 sct 示例文件配置如下:

程序的加載域爲內部 FLASH 的 0x08000000,最大空間爲 0x00100000;程序的執行基地址與加載基地址相同,其中 RESET 節區定義的向量表要存儲在內部 FLASH 的首地址,且所有 .o 文件及 .lib 文件的 RO 屬性內容都存儲在內部 FLASH 中;程序執行時 RW 及 ZI 區域都存儲在以 0x20000000 爲基地址,大小爲 0x00030000 的空間(192KB),這部分正好是 STM32 內部主 SRAM 的大小。

鏈接器根據 sct 文件鏈接,鏈接後各個節區、符號的具體地址信息可以在 map 文件中查看。

2. 通過MDK配置選項來修改sct文件

瞭解 sct 文件的格式後,可以手動編輯該文件控制整個工程的分散加載配置,但sct 文件格式比較複雜,所以 MDK 提供了相應的配置選項可以方便地修改該文件,這些選項配置能滿足基本的使用需求,以下將對這些選項進行說明。

2.1 選擇sct文件的產生方式

首先需要選擇 sct 文件產生的方式,選擇使用 MDK 生成還是使用用戶自定義的 sct 文件。在 MDK 的 “Options for Target->Linker->Use Memory Layout from Target Dialog” 選項即可配置該選擇,見下圖:

在這裏插入圖片描述

該選項的譯文爲 “是否使用 Target 對話框中的存儲器分佈配置”,勾選後,它會根據 “Options for Target” 對話框中的選項生成 sct 文件,這種情況下,即使我們手動打開它生成的 sct 文件編輯也是無效的,因爲每次構建工程的時候,MDK 都會生成新的 sct 文件覆蓋舊文件。該選項在 MDK 中是默認勾選的,若希望 MDK 使用我們手動編輯的 sct 文件構建工程,需要取消勾選,並通過 Scatter File 框中指定 sct 文件的路徑,見下圖:

在這裏插入圖片描述

2.2 通過Target對話框控制存儲器分配

我們在 Linker 中勾選了 “使用 Target 對話框的存儲器佈局” 選項,那麼 “Options for Target” 對話框中的存儲器配置就生效了。主要配置是在 Device 標籤頁中選擇芯片的類型,設定芯片基本的內部存儲器信息以及在 Target 標籤頁中細化具體的存儲器配置(包括外部存儲器),見以下圖片:

在這裏插入圖片描述

上圖中 Device 標籤頁中選定了芯片的型號爲 STM32F429IGTx,選中後,在 Target 標籤頁中的存儲器信息會根據芯片更新。

在這裏插入圖片描述

在 Target 標籤頁中存儲器信息分成只讀存儲器 (Read/Only Memory Areas) 和可讀寫存儲器 (Read/Write Memory Areas) 兩類,即 ROM 和 RAM,而且它們又細分成了片外存儲器 (off-chip) 和片內存儲器 (on-chip) 兩類。

例如:

由於我們已經選定了芯片的型號,MDK 會自動根據芯片型號填充片內的 ROM 及 RAM 信息,其中的 IROM1 起始地址爲 0x80000000,大小爲 0x100000,正是該 STM32 型號的內部 FLASH 地址及大小;而 IRAM1 起始地址爲 0x20000000,大小爲 0x30000,正是該 STM32 內部主 SRAM 的地址及大小。圖中的 IROM1 及 IRAM1 前面都打上了勾,表示這個配置信息會被採用,若取消勾選,則該存儲配置信息是不會被使用的。

在標籤頁中的 IRAM2 一欄默認也填寫了配置信息,它的地址爲 0x10000000,大小爲 0x10000,這是 STM32F4 系列特有的內部 64KB 高速 SRAM(被稱爲CCM)。當我們希望使用這部分存儲空間的時候需要勾選該配置,另外要注意這部分高速 SRAM 僅支持 CPU 總線的訪問,不能通過外設訪問。

下面我們嘗試修改 Target 標籤頁中的這些存儲信息,例如:把 IRAM1 的基地址改爲 0x20001000,然後編譯工程,查看到工程的 sct 文件,和同時使用 IRAM1 和 IRAM2,然後編譯工程,做一個比較。

在這裏插入圖片描述

//修改了IRAM1基地址後的sct文件內容

 LR_IROM1 0x08000000 0x00100000 { ; load region size_region

 ER_IROM1 0x08000000 0x00100000 { ; load address = execution address

 *.o (RESET, +First)

 *(InRoot$$Sections)

 .ANY (+RO)

 }

 RW_IRAM1 0x20001000 0x00030000 { ; RW data

 .ANY (+RW +ZI)

 }

 }

//使用IRAM2時的sct文件內容

 LR_IROM1 0x08000000 0x00100000 { ; load region size_region

 ER_IROM1 0x08000000 0x00100000 { ; load address = execution address

 *.o (RESET, +First)

 *(InRoot$$Sections)

 .ANY (+RO)

 }

 RW_IRAM1 0x20000000 0x00030000 { ; RW data

 .ANY (+RW +ZI)

 }

 RW_IRAM2 0x10000000 0x00010000 {

 .ANY (+RW +ZI)

 }

 }

可以發現,sct 文件都根據 Target 標籤頁做出了相應的改變,除了這種修改外,在 Target 標籤頁上還控制同時使用 IRAM1 和 IRAM2、加入外部 RAM (如外接的 SDRAM),外部 FLASH 等。

2.3 控制文件分配到指定的存儲空間

設定好存儲器的信息後,可以控制各個源文件定製到哪個部分存儲器,在 MDK 的工程文件欄中,選中要配置的文件,右鍵,並在彈出的菜單中選擇 “Options for File xxxx” 即可彈出一個文件配置對話框,在該對話框中進行存儲器定製,見下圖:

在這裏插入圖片描述

在彈出的對話框中有一個 “Memory Assignment” 區域(存儲器分配),在該區域中可以針對文件的各種屬性內容進行分配,如 Code/Const 內容 (RO)、Zero Initialized Data 內容 (ZI-data) 以及 Other Data 內容 (RW-data),點擊下拉菜單可以找到在前面 Target 頁面配置的 IROM1、IRAM1、IRAM2 等存儲器。

例如:圖中我們把這個 bsp_led.c 文件的 Other Data 屬性的內容分配到了 IRAM2 存儲器(在 Target 標籤頁中我們勾選了 IRAM1 及 IRAM2),當在 bsp_led.c 文件定義了一些 RW-data 內容時(如初值非 0 的全局變量),該變量將會被分配到 IRAM2 空間,配置完成後點擊 OK,然後編譯工程,查看到的 sct 文件內容如下:

//修改bsp_led.c配置後的sct文件

 LR_IROM1 0x08000000 0x00100000 { ; load region size_region

 ER_IROM1 0x08000000 0x00100000 { ; load address = execution address

 *.o (RESET, +First)

 *(InRoot$$Sections)

 .ANY (+RO)

 }

 RW_IRAM1 0x20000000 0x00030000 { ; RW data

 .ANY (+RW +ZI)

 }

 RW_IRAM2 0x10000000 0x00010000 {

 bsp_led.o (+RW)

 .ANY (+RW +ZI)

 }

 }

可以看到在 sct 文件中的 RW_IRAM2 執行域中增加了一個選擇 bsp_led.o 中 RW 內容的語句。

類似地,我們還可以設置某些文件的代碼段被存儲到特定的 ROM 中,或者設置某些文件使用的 ZI-data 或 RW-data 存儲到外部 SDRAM 中(控制 ZI-data 到 SDRAM 時注意還需要修改啓動文件設置堆棧對應的地址,原啓動文件中的地址是指向內部 SRAM 的)。

雖然 MDK 的這些存儲器配置選項很方便,但有很多高級的配置還是需要手動編寫 sct 文件實現的,例如:MDK 選項中的內部 ROM 選項最多隻可以填充兩個選項位置,若想把內部 ROM 分成多片地址管理就無法實現了;另外 MDK 配置可控的最小粒度爲文件,若想控制特定的節區也需要直接編輯 sct 文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章