天天看點

Cortex-M3核心晶片進階之分散加載檔案

作者:LiNUS工程師筆記

1.1 什麼時候使用分散加載檔案

  • 存在複雜的位址映射:例如代碼和資料需要分開放在在多個區域。
  • 存在多種存儲器類型:例如包含 Flash、 ROM、 SDRAM 快速 SRAM。我們根據代碼與資料的特性把它們放在不同的存儲器中,比如中斷處理部分放在快速 SRAM 内部來提高響應速度,而把不常用到的代碼放到速度比較慢的 Flash 内。
  • 函數的位址固定定位:可以利用 Scatter file 實作把某個函數放在固定位址,而不管其應用程式是否已經改變或重新編譯。
  • 利用符号确定堆與堆棧:
  • 記憶體映射的 IO:采用 scatter file 可以實作把某個資料段放在精确的地指處。

是以對于嵌入式系統來說 scatter file 是必不可少的,因為嵌入式系統采用了 ROM, RAM,和記憶體映射的 IO。通過使用分散加載機制,可以使用文本檔案中的描述為連結器指定映像的記憶體映射。分散加載為您提供了對映像元件分組和位置的全面控制。分散加載可以用于簡單映像,但它通常僅用于具有複雜記憶體映射的映像,即多個區在加載和執行時分散在記憶體映射中。

1.2 适用範圍

有時候使用者希望将不同代碼放在不同存儲空間,也就是通過編譯器生成的映像檔案需要包含多個域,每個域在加載和運作時可以有不同的位址。要生成這樣的映像檔案,必須通過某種方式告知編譯器相關的位址映射關系。

Scattter loading 是 ARM 連接配接器(armlink)提供的一種機制,該機制讓你能夠把可執行映像檔案分成多個區域,然後分别為它們指定在存儲器上的存儲位置。 Scatter loading 機制用于定位裝載區域和運作區域在分離的存儲映像中的位置。

1.3 再談 ARM Image(鏡像檔案)

ARM 中的源檔案經過編譯器編譯生成的目标檔案.obj(Object file)和相應的 C/C++運作時庫(Runtime Library)經過連接配接器的處理後,生成 ELF 格式的映像檔案(image)——它可以被寫入目标裝置的 ROM 中直接運作或加載後運作。鏡像檔案的組成如圖所示。

Cortex-M3核心晶片進階之分散加載檔案

圖 1.1 鏡像檔案組成示意圖

可執行檔案由映像、區、輸出節和輸入節的層次結構構成:

  • 映像由一個或多個區組成。每個區由一個或多個輸出節組成。
  • 每個輸出節包含一個或多個輸入節。
  • 輸入節是對象檔案中的代碼和資料資訊。
  • 輸入節:輸入節包含代碼、初始化資料,或描述未初始化的或在映像執行前必須設

    為 0 的記憶體片斷。這些特性通過 RO、 RW 和 ZI 這樣的屬性來表示。

  • 輸出節:一個輸出節由若幹個具有相同 RO、 RW 或 ZI 屬性的相鄰輸入節組成。輸出節的屬性與組成它的輸入節的屬性相同。
  • 區:一個區由一個、兩個或三個相鄰的輸出節組成。區中的輸出節根據其屬性排序。首先是 RO 輸出節,然後是 RW 輸出節,最後是 ZI 輸出節。區通常映射到實體記憶體裝置,如 ROM、 RAM 或外圍裝置。

關于 RO、 RW 以及 ZI 資料段的詳細内容在前文中已有叙述,在此就不再詳述。

ARM 映像檔案各組成部分在存儲系統中的位址有兩種:

1. 裝載區域

程式在裝載之後、運作之前,所占有的存儲區域能夠被分成多個裝載區域,每個裝載區域就是一個連續的位元組塊。

2. 運作區域

程式在運作時所占有的存儲區域能夠被分成多個裝載區域,每個裝載區域也是一個連續的位元組塊。

注意:一個裝載區域可能包含多個運作區域;一個運作區域隻能屬于一個裝載區域。

1.4 節放置

連結器根據輸入節的屬性在一個區内對它們進行排序。具有相同屬性的輸入節在區内形成相鄰塊。每個輸入節的基址由連結器定義的排序順序确定,并且在包含它的輸出節中正确對齊。通常,生成映像時連結器按以下順序對輸入節進行排序:

  • 按屬性。
  • 按輸入節名稱。
  • 按其在輸入清單中的位置,除非由 FIRST 或 LAST 覆寫。

注意:此排序順序不受分散加載描述檔案或對象檔案名中的排序的影響。如果執行區包含4MB 的 Thumb 代碼、 16M 的 Thumb-2 代碼或超過 32MB 的 ARM 代碼,則連結器可能會更改排序順序,進而将長跳轉中間代碼的數量減至最小。在預設情況下,連結器建立由 RO、 RW 和可選的 ZI 輸出節組成的映像。 RO 輸出節在具有記憶體管理硬體的系統上運作時可以受到保護。RO 節也可以放在目标的 ROM 中。

部分映像集合在一起,形成最小數量的相鄰區。 armlink 按如下屬性對輸入節排序:

1. 隻讀代碼

2. 隻讀資料

3. 讀寫代碼

4. 讀寫資料

5. 零初始化資料

具有相同屬性的輸入節按名稱排序。名稱是區分大小寫的,并且使用 ASCII 字元排列順序,對名稱按字母順序進行比較。屬性和名稱相同的輸入節根據它們在輸入檔案中的相對位置進行排序。這些規則意味着庫中所含屬性和名稱相同的輸入節的位置不可預判。如果需要更精确的定位,可以顯式指定各個子產品并将這些子產品包含在輸入清單中。

1.5 一個簡單的加載過程

在一個簡單的嵌入式計算機系統中,存儲器一般被分成 ROM 和 RAM。連接配接器生成的映像被分成“Read-Only”段和“Read-Write”段(包含已初始資料和未初始化資料,未初始化資料也叫 ZI 資料)。通常,在程式下載下傳(燒入)的時候,它們會被一塊下載下傳到 ROM 上;而在程式開始執行時, Read-Write 段會從 ROM 被 Copy 到 RAM。

如下圖所示:

Cortex-M3核心晶片進階之分散加載檔案

圖 1.2 加載過程描述示意圖

以上是一個簡單的例子,但在複雜的嵌入式系統中,其存儲器往往包括 ROM,SRAM,DRAM, FLASH 等等,加載過程比如上執行個體要複雜一些。

1.6 分散加載檔案文法

分散加載檔案主要由一個加載時域(區)和多個運作時域(區)組成,其大緻結構如下圖所示。

Cortex-M3核心晶片進階之分散加載檔案

圖 1.3 分散加載檔案的文法結構

1.6.1 加載時域(區)描述

加載時域文法格式如程式清單 1.1 所示,其每項含義解釋如下。

程式清單 1.1 加載時域描述文法描述

load_region_name(base_address|("+"offset))[attribute_list][max_size]

{

execution_region_description+

}

  • load_region_name:為本加載時域的名稱,名稱可以按照使用者意願自己定義,該名稱中隻有前 31 個字元有意義;
  • base_designator:用來表示本加載時域的起始位址,可以有下面兩種格式中的一種:
  • base_address:表示本加載時域中的對象在連接配接時的起始位址,位址必須是字對齊

    的;

  • +offset:表示本加載時域中的對象在連接配接時的起始位址是在前一個加載時域的結

    束位址後偏移量 offset 位元組處。本加載時域是第一個加載時域,則它的起始位址即為

    offset, offset 的值必須能被 4 整除。

  • attribute_list:指定本加載時域内容的屬性,包含以下幾種,預設加載時域的屬性是ABSOLUTE。
  • ABSOLUTE:絕對位址;
  • PI:與位置無關;
  • RELOC:可重定位;
  • OVERLAY:覆寫;
  • NOCOMPRESS:不能進行壓縮。
  • max_size:指定本加載時域的最大尺寸。如果本加載時域的實際尺寸超過了該值,連接配接器将報告錯誤,預設取值為 0xFFFFFFFF;
  • execution_region_description:表示運作時域,後面有個+号,表示其可以有一個或者多個運作時域,關于運作時域的介紹請看後面。

注:程式清單 4.1 中’|’、‘ []’符号的使用請參考 BNF 文法。

1.6.2 運作時域(區)描述

運作時域文法格式如程式清單 1.2 所示,其每項含義解釋如下。

程式清單1.2 運作時域文法描述

exec_region_name(base_address|"+"offset)[attribute_list][max_size|" "length]

{

input_section_description*

}

  • exec_region_name:為為本加載時域的名稱,名稱可以按照使用者意願自己定義,該名稱中隻有前 31 個字元有意義;
  • base_address:用來表示本加載時域的起始位址,可以有下面兩種格式中的一種:
  • base_address:表示本加載時域中的對象在連接配接時的起始位址,位址必須是字對齊的;
  • +offset:表示本加載時域中的對象在連接配接時的起始位址是在前一個加載時域的結

    束位址後偏移量 offset 位元組處, offset 的值必須能被 4 整除。

  • attribute_list:指定本加載時域内容的屬性:
  • ABSOLUTE:絕對位址;
  • PI:與位置無關;
  • RELOC:可重定位;
  • OVERLAY:覆寫;
  • FIXED:固定位址。區加載位址和執行位址都是由基址訓示符指定的,基址訓示符必須是絕對基址,或者偏移為 0。
  • ALIGNalignment:将執行區的對齊限制從 4 增加到 alignment。 alignment 必須為 2的正數幂。如果執行區具有 base_address,則它必須為 alignment 對齊。如果執行區具有 offset,則連結器将計算的區基址與 alignment 邊界對齊;
  • EMPTY:在執行區中保留一個給定長度的空白記憶體塊,通常供堆或堆棧使用。
  • ZEROPAD:零初始化的段作為零填充塊寫入 ELF 檔案,是以,運作時無需使用零進行填充;
  • PADVALUE:定義任何填充的值。如果指定 PADVALUE,則必須為其指派;
  • NOCOMPRESS:不能進行壓縮;
  • UNINIT:未初始化的資料。
  • max_size:指定本加載時域的最大尺寸。如果本加載時域的實際尺寸超過了該值,連接配接器将報告錯誤,預設取值為 0xFFFFFFFF;
  • length:如果指定的長度為負值,則将 base_address 作為區結束位址。它通常與 EMPTY一起使用,以表示在記憶體中變小的堆棧。
  • input_section_description:指定輸入段的内容。

1.6.3 輸入段描述

輸入段文法描述如程式清單 1.3 所示。

程式清單 1.3 輸入段文法描述

module_select_pattern [ "(" input_section_selector ( "," input_section_selector )* ")" ]

("+" input_section_attr | input_section_pattern | input_symbol_pattern)

● module_select_pattern:目标檔案濾波器,支援使用通配符“*”與“?”。其中符号“*”代表零個或多個字元,符号“?”代表單個字元。進行比對時所有字元不區分大小寫。當 module_select_pattern 與以下内容之一相比對時,輸入段将與子產品選擇器模式相比對:

1.包含段和目标檔案的名稱;

2.庫成員名稱(不帶前導路徑名);

3.庫的完整名稱(包括路徑名)。如果名稱包含空格,則可以使用通配符簡化搜尋。例如,使用*libname.lib 比對 C:\lib dir\libname.lib。

● nput_section_attr:屬性選擇器與輸入段屬性相比對。每個 input_section_attr 的前面有一個“+”号。如果指定一個模式以比對輸入段名稱,名稱前面必須有一個“+”号。可以省略緊靠“+”号前面的任何逗号。選擇器不區分大小寫。可以識别以下選擇器:

◆ RO-CODE;

◆ RO-DATA;

◆ RO,同時選擇 RO-CODE 和 RO-DATA;

◆ RW-DATA;

◆ RW-CODE;

◆ RW,同時選擇 RW-CODE 和 RW-DATA;

◆ ZI;

◆ ENTRY:即包含 ENTRY 點的段。

● 可以識别以下同義詞:

◆ CODE 表示 RO-CODE;

◆ CONST 表示 RO-DATA;

◆ TEXT 表示 RO;

◆ DATA 表示 RW;

◆ BSS 表示 ZI。

● 可以識别以下僞屬性:

◆ FIRST;

◆ LAST。

通過使用特殊子產品選擇器模式.ANY 可以将輸入段配置設定給執行區,而無需考慮其父子產品。可以使用一個或多個.ANY 模式以任意配置設定方式填充運作時域。在大多數情況下,使用單個.ANY等效于使用*子產品選擇器。

1.7 實戰測試(一個簡單的例程)

程式清單 1.4 Keil 預設的分散加載描述檔案代碼

; *************************************************************
; *** Scatter-Loading Description File generated by uVision
; *************************************************************
LR_IROM1 0x00000000 0x00040000 { ; load region size_region
ER_IROM1 0x00000000 0x00040000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$Sections)
.ANY (+RO)
}
RW_IRAM1 0x10000000 0x00008000 { ; RW data
.ANY (+RW +ZI)
}
}           

程式清單 1.4 所示的即為 keil 預設的分散加載檔案,加載結果如下圖所示。

Cortex-M3核心晶片進階之分散加載檔案

圖 1.4 加載結果圖

這僅僅隻是一個片段,不過可以看出其與上文的分散加載代碼的描述是相吻合的。現在按照代碼清單 1.5 修改代碼,看看是不是 RO 段的加載區域會改變。

程式清單 1.5 測試代碼

LR_IROM1 0x00010000 0x00040000 { ; load region size_region
ER_IROM1 0x00010000 0x00040000 { ; load address = execution address
*.o (RESET, +First)
*(InRoot$Sections)
.ANY (+RO)
}
RW_IRAM1 0x10000000 0x00008000 { ; RW data
.ANY (+RW +ZI)
}
}           

按照上面對分散加載檔案的介紹可知,這段代碼運作後, RO 段的加載位址将由 0x00000000變為 0x00010000,下面讓我們實際操作一下,看看我們預想的是否正确。要想直接執行這段代碼是行不通的,正常的 Keil 編譯器預設是不會自動執行.sct(分散加載描述)檔案的,需要按下圖所示修改相應的連結器配置。

Cortex-M3核心晶片進階之分散加載檔案

圖 1.5 需要修改的 keil 配置示意圖

這樣我們編寫的分散加載檔案就可以運作了。重新連結工程,再次檢視 LPC1700.map 檔案,如下圖所示。

Cortex-M3核心晶片進階之分散加載檔案

圖 1.6 再次加載結果示意圖

與圖 1.4 對比,從圖中我們可以明顯看出程式加載的基址變為了 0x00010000,這與我們編寫的分散加載代碼的内容一緻,說明代碼成功運作,結果和我們預想的一樣。

1.8 實戰分散加載

假設,一個 Cortex-M3 核心的 LPC17xx 微控制器有 Flash、 RAM 的資源如下:

  • Flash 基址: 0x00000000,大小: 256 KByte;
  • RAM 基址: 0x10000000,大小: 32 Kbyte。

那這一個分散加載檔案應該怎樣描述呢?可參考如下所示的配置。

程式清單 1.6 一個普通的分散加載檔案配置

LR_IROM1 0x00000000 0x00040000 { ; 定義一個加載時域,域基址: 0x00000000,域大
                                                             ; 小為 0x00040000,對應實際 Flash 的大小
  ER_IROM1 0x00000000 0x00040000 { ; 定義一個運作時域,第一個運作時域必須和加載
  *.o (RESET, +First)                                  ; 時域起始位址相同,否則庫不能加載到該時域的
  .ANY (+RO)                                            ; 錯誤,其域大小一般也和加載時域大小相同
                                                               ; 将 RESET 段最先加載到本域的起始位址外,即
                                                               ; RESET 的起始位址為 0, RESET 存儲的是向量表
                                                               ; 加載所有比對目标檔案的隻讀屬性資料,包含:
                                                               ; Code、 RW-Code、 RO-Data
  }
  RW_IRAM1 0x10000000 0x00008000 {
  * (+RW +ZI)                                         ; 定義一個運作時域,域基址: 0x10000000,域大
                                                              ; 小為 0x00008000,對應實際 RAM 大小
                                                              ; 加載所有區配目标檔案的 RW-Data、 ZI-Data
                                                              ; 這裡也可以用.ANY 替代*号
  }
}           

1.8.1 多塊 RAM 的分散加載檔案配置

還是上述的 MCU,假設其增加了另外一塊 RAM,其資源如下:

  • Flash 基址: 0x00000000,大小: 256 KByte;
  • RAM1 基址: 0x10000000,大小: 32 Kbyte;
  • RAM2 基址: 0x2007C000,大小: 32 Kbyte。

如果我想将這兩塊不連續的 RAM 都使用起來(可使用 64KB RAM)?分散加載檔案應怎樣描述呢?可參考如下所示配置。

程式清單 1.7 雙 RAM 的加載檔案配置

LR_IROM1 0x00000000 0x00040000 {
  ER_IROM1 0x00000000 0x00040000 {
  *.o (RESET, +First)
  .ANY (+RO)
  }
  RW_IRAM1 0x10000000 0x00008000 { ; 定義 RAM1 的運作時域
; 使用.ANY 進行随意配置設定變量,這裡不能使用*号; 替代, *表示比對有所有的目标檔案,這樣變量就
; 無法配置設定到第二塊 RAM 空間了
  .ANY (+RW +ZI)
  }
  RW_IRAM2 0x2007C000 0x00008000 { ; 定義第 RAM2 的運作時域
; 同樣使用.ANY 随意配置設定變量的方式
  .ANY (+RW +ZI)
  }
  ; 如果還有另多的 RAM 塊,在這裡增加新的運作
  ; 時域即可,格式和 RAM2 的定義相同
}           

如上所示的分散加載檔案配置,确實可将兩塊 RAM 都使用起來,即有 64KB 的 RAM 可以使用,但其并不能完全等價于一個 64KB 的 RAM,實際應用可能會碰到如下的問題。如我在 main.c 檔案中聲明了 1 個 40KB 的數組,如程式清單1.8所示,程式中紅色部分已注釋,可暫不理會。

程式清單 1.8 定義大數組出錯

// main.c 檔案

unsigned char GucTest0[40 * 1024]; // 定義一個 40KB 的數組

//unsigned char GucTest1[20 * 1024]; // 定義第一個 20KB 數組

//unsigned char GucTest2[20 * 1024]; // 定義第二個 20KB 數組

// end of file

如程式清單 1.8 所示的程式在編譯時會出現錯誤,并提示沒有足夠的空間,為什麼呢?原因為數組是一個整體,其内部元素的位址是連續的,不能分割的,但是在兩個不連續的 32KB空間中,是沒辦法配置設定出一個連續的 40KB 位址空間,是以編譯會提示空間不足,配置設定 40KB數組失敗。

還是程式清單 1.8 所示的程式,去掉 40KB 的資料,換成 2 個 20KB 的數組(即紅色注釋代碼部分),編譯結果又會如何呢?編譯結果還是提示空間不足,這又是為什麼呢?這裡出錯的原因其實和上面的原因是相同的,首先,重溫一下,.ANY 的作用, .ANY 是一個通配符,當其與以下内容之一相比對時将進行選擇。

  • 包含段和目标檔案的名稱;
  • 庫成員名稱(不帶前導路徑名);
  • 庫的完整名稱(包括路徑名)。如果名稱包含空格,則可以使用通配符簡化搜尋。例如,使用*libname.lib 比對 C:\lib dir\libname.lib。

後面兩個和本次讨論的話題無關,再仔細的看第一個比對項為:包含段和目标檔案,段這裡先不用理會,因為這裡沒有用到段,是以隻剩下目标檔案。要注意是“目标檔案”,不是其它,即是說一個 C 檔案編譯後,其所有的變量、代碼都會作為一個整體。是以定義兩個 20KB 和定義了一個 40KB,在編譯器看來都是一樣,就是這個 C 檔案總共定義了 40KB 的空間,我要用40KB 的空間來配置設定它,是以會出現同樣的錯誤。

關于大數組配置設定的解決方法,有兩種,分别如下:

将數組分開在不同的 C 檔案中定義,避免在同一個 C 檔案定義的資料大小總量超過其中最大的分區;

将一個 C 數組,使用段定義,使其從該 C 檔案中獨立出來,這樣編譯器就不會将它們作為一個整體來劃分空間了,其示例如程式清單 1.9 所示。

程式清單 4.9 使用段的方式配置設定多數組

#pragma arm section zidata = "SRAM" // 在 C 檔案中定義新的段
unsigned char GucTest1[20 * 1024]; // 定義第一個 20KB 數組
#pragma arm section // 恢複原有的段
unsigned char GucTest2[20 * 1024]; // 定義第二個 20KB 數組,這 20KB 數組不會和
// GucTest1 作為一個整體來劃分空間           

1.8.2 多塊 Flash 的分散加載檔案配置

再一下上述的 MCU,假其增加多了一塊 Flash,不是 RAM,其資源如下。

  • Flash1 基址: 0x00000000,大小: 256 KByte;
  • Flash2 基址: 0x20000000,大小: 2048 KByte;
  • RAM 基址: 0x10000000,大小: 32 Kbyte;

注意這裡多增加的一塊的不是 RAM,而是 Flash,其情況會如何呢?假設其相同,那寫法應該就是如程式清單 1.10 所示的樣子。

程式清單 1.10 雙 Flash 的錯誤配置示例 1

LR_IROM1 0x00000000 0x00040000 {
  ER_IROM1 0x00000000 0x00040000 { ; 定義 Flash1 運作時域
  *.o (RESET, +First) ; 先加載向量表
  .ANY (+RO) ; 随意配置設定隻讀資料
  }
  ER_IROM2 0x20000000 0x00200000 { ; 定義 Flash2 運作時域
  .ANY (+RO) ; 随意配置設定隻讀資料
  }
  RW_IRAM1 0x10000000 0x00008000 {
  .ANY (+RW +ZI)
  }
}           

如程式清單 1.10 的分散加載配置,進行編譯卻出錯了,錯誤提示如下所示。

..Error: L6202E: __main.o(!!!main) cannot be assigned to non-root region 'ER_IROM2'

..Error: L6202E: __scatter.o(!!!scatter) cannot be assigned to non-root region 'ER_IROM2'

..Error: L6202E: __scatter_copy.o(!!handler_copy) cannot be assigned to non-root region 'ER_IROM2'

..Error: L6202E: __scatter_zi.o(!!handler_zi) cannot be assigned to non-root region 'ER_IROM2'

..Error: L6202E: anon$obj.o(Region$Table) cannot be assigned to non-root region 'ER_IROM2'

..Error: L6203E: Entry point (0x20000001) lies within non-root region ER_IROM2.

該錯誤的意思是說, __main.o、 __scatter.o、 __scatter_copy.o 等不能被加載到第二塊 Flash的運作時域 ER_IROM2,也就是說這幾項目資料隻能加載到 ER_IROM1 的運作時域。為什麼這些資料不能放到第二個運作時域呢?後面再進行解釋。

無論如何,但總的來說是使用.ANY 引起的錯誤, .ANY 是讓編譯器随意配置設定資料,是以資料有可能被配置設定到 ER_IROM2。如果手動這出錯的幾項到 ER_IROM1,結果又會如何呢?請看

如程式清單 1.11 所示的配置。

程式清單 1.11 雙 Flash 的錯誤配置示例 2

LR_IROM1 0x00000000 0x00040000 {
  ER_IROM1 0x00000000 0x00040000 {
  *.o (RESET, +First) ; 先加載向量表
  __main.o ; 手動加載到 ER_IROM1,避免自動配置設定引起錯誤
  __scatter.o ; 避免自動配置設定
  __scatter_copy.o ; 避免自動配置設定
  __scatter_zi.o ; 避免自動配置設定
  * (Region$Table) ; 避免自動配置設定
  .ANY (+RO)
  }
  ER_IROM2 0x20000000 0x00200000 {
  .ANY (+RO)
  }
  RW_IRAM1 0x10000000 0x00008000 {
  .ANY (+RW +ZI)
  }
}           

終于,編譯沒有錯誤了,但在軟體仿真下,現象明顯不正确, main()函數都跑不到,這?這個問題得從第一個時域與加載時域的關系來進行說明,其關系如下所示。

1. 第一個運作時域存放的代碼不會進行額外拷貝

因為分散加載檔案有一項很強大的功能,就是可以将 Flash 的代碼拷貝到 RAM 中運作,這一段拷貝代碼就存在于__main()函數中,但拷貝代碼不能拷貝自身,是以必須規定有一個運作時域中存放的代碼是不會被拷貝的,這個指的就是第一個運作時域。

拷貝代碼為什麼不能拷貝自身呢?舉個例子,假設 A 代碼是要被拷貝到 RAM 執行的代碼,那 A 代碼必先存儲于 Flash 中,然後被拷貝到 RAM 中。這樣 A 代碼不就存在兩段了麼,但是隻能有一段是有效的,就像定義了兩個相同名字的函數,最終隻能留下一個。是以最終被認定為拷貝後的代碼才是有效的。那就是說 A 代碼從 Flash 中拷貝到了 RAM, RAM 中代碼才是有效, Flash 中的代碼是無效的,其它程式調用 A 代碼也是調用 RAM 中的代碼而不是 Flash 的。那如果 A 是一段拷貝代碼,那就會發生如下的現象,一段程式調用 RAM 中的 A 代碼, RAM中的 A 代碼再将自己從 Flash 中拷貝到 RAM。

結論,一段代碼必須先完成拷貝,才能被執行。換句了解就是拷貝代碼前包括自身的所有代碼都不能拷貝,也就是說這些代碼全部都必須放在第一個運作時域中。

2. 規定其餘運作時域中存放的代碼均會被拷貝

一個加載時域,隻需要一個不拷貝的運作時域即可。是以規定其餘所有的運作時域中的代碼均會被拷貝。

3. 第一個運作時域的基址必須與加載域基址相同

為了保證第一個運作時域的代碼能夠被正确存儲和執行,是以要求第一個運作時域的基址必須和加載時域的基址相同。看完了第一個運作時域與加載時域的關系,那程式清單 1.10、程式清單 1.11 出現錯誤的原因就很明了了。

  • 程式清單 1.10 出錯原因,是因為__main()等拷貝代碼必須存放在第一個運作時域中;
  • 程式清單 1.11 出錯的原因如程式清單 1.12 所示。

程式清單 1.12 程式清單 1.7 出錯的原因

ER_IROM2 0x20000000 0x00200000 {
.ANY (+RO) ; 定義 Flash1 運作時域
; 随意配置設定隻讀資料,但代碼存放在第二個運作時
; 域中,是以該代碼是運作時才被拷貝到這裡,
; 那就是說要往 Flash1 直接寫資料,當然會導緻
; 程式出錯了
}           

程式清單 1.13 給出了雙 Flash 的分散加載檔案正确配置示例,如下所示。

程式清單 1.13 雙 Flash 的正确配置示例

LR_IROM1 0x00000000 0x00040000 { ; 定義 Flash1 的加載域
  ER_IROM1 0x00000000 0x00040000 {
  *.o (RESET, +First)
  .ANY (+RO) ; 随機配置設定隻讀資料
  }
  RW_IRAM1 0x10000000 0x00008000 {
  .ANY (+RW +ZI)
  }
  }
  LR_IROM2 0x20000000 0x00200000 { ; 定義 Flash2 的加載域
  ER_IROM2 0x20000000 0x00200000 {
  .ANY (+RO) ; 随機配置設定隻讀資料,代碼不會進行拷貝
  }
}
           

繼續閱讀