天天看點

【轉】Keil MDK下如何設定非零初始化變量

轉載自:

https://www.amobbs.com/thread-5666000-1-1.html?_dsign=759ed928

   一些工控産品,當系統複位後(非上電複位),可能要求保持住複位前RAM中的資料,用來快速恢複現場,或者不至于因瞬間複位而重新開機現場裝置。而keil mdk在預設情況下,任何形式的複位都會将RAM區的非初始化變量資料清零。如何設定非初始化資料變量不被零初始化,這是本篇文章所要探讨的。

       在給出方法之前,先來了解一下代碼和資料的存放規則、屬性,以及複位後為何預設非初始化變量所在RAM都被初始化為零了呢。

       什麼是初始化資料變量,什麼又是非初始化資料變量?(因為我的文字描述不一定準确,是以喜歡舉一些例子來輔助了解文字。)

       定義一個變量:int nTimerCount=20;變量nTimerCount就是初始化變量,也就是已經有初值;

       如果定義變量:int nTimerCount;變量nTimerCount就是一個非指派的變量,Keil MDK預設将它放到屬性為ZI的輸入節。

       那麼,什麼是“ZI”,什麼又是“輸入節”呢?這要了解一下ARM映像檔案(image)的組成了,這部分内容略顯無聊,但我認為這是非常有必要掌握的。

ARM映像檔案的組成:

一個映像檔案由一個或多個域(region,也有譯為“區”)組成

每個域包含一個或多個輸出段(section,也有譯為“節”)

每個輸出段包含一個或多個輸入段

各個輸入段包含了目标檔案中的代碼和資料

       輸入段中包含了四類内容:代碼、已經初始化的資料、未經過初始化的存儲區域、内容初始化為零的存儲區域。每個輸入段有相應的屬性:隻讀的(RO)、可讀寫的(RW)以及初始化成零的(ZI)。

       一個輸出段中包含了一些列具有相同的RO、RW和ZI屬性的輸入段。輸出段屬性與其中包含的輸入段屬性相同。

       一個域包含一到三個輸出段,各個輸出段的屬性各不相同:RO屬性、RW屬性和ZI屬性

       到這裡我們就可以知道,一般情況下,代碼會被放到RO屬性的輸入節,已經初始化的變量會被配置設定到RW屬性輸入區,而“ZI”屬性輸入節可以了解為是初始化成零變量的集合。

       已經初始化變量的初值,會被放到硬體的哪裡呢?(比如定義int nTimerCount=20;那麼初始值20被放到哪裡呢?),我覺得這是個有趣的問題,比如keil在編譯完成後,會給出編譯檔案大小的資訊,如下所示:

Total RO Size (Code + RO Data) 54520 ( 53.24kB)

Total RW Size (RW Data + ZI Data) 6088 ( 5.95kB)

Total ROM Size (Code + RO Data + RW Data) 54696 ( 53.41kB)

       很多人不知道這是怎麼計算的,也不知道究竟放入ROM/Flash中的代碼有多少。其實,那些已經初始化的變量,是被放入RW屬性的輸入節中,而這些變量的初值,是被放入ROM/Flash中的。有時候這些初值的量比較大,Keil還會将這些初值壓縮後再放入ROM/Flash以節省存儲空間。那這些初值是誰在何時将它們恢複到RAM中的?ZI屬性輸入節中的變量所在RAM又是誰在何時給用零初始化的呢?要了解這些東西,就要看預設設定下,從系統複位,到執行C代碼中你編寫的main函數,Keil幫你做了些什麼。

       硬體複位後,第一步是執行複位處理程式,這個程式的入口在啟動代碼裡(預設),摘錄一段cortex-m3的複位處理入口代碼:

   1: Reset_Handler   PROC        ;PROC等同于FUNCTION,表示一個函數的開始,與ENDP相對?  

   2:  

   3:                 EXPORT  Reset_Handler             [WEAK]  

   4:                 IMPORT  SystemInit  

   5:                 IMPORT  __main  

   6:                 LDR     R0, =SystemInit  

   7:                 BLX     R0  

   8:                 LDR     R0, =__main  

   9:                 BX      R0  

  10:                 ENDP  

       初始化堆棧指針、執行完使用者定義的底層初始化代碼(SystemInit函數)後,接下來的代碼調用了__main函數,這裡__main函數會調用一些列的C庫函數,完成代碼和資料的複制、解壓縮以及ZI資料的零初始化。資料的解壓縮和複制,其中就包括将儲存在ROM/Flash中的已初始化變量的初值複制到相應的RAM中去。對于一個變量,它可能有三種屬性,用const修飾符修飾的變量最可能放在RO屬性區,已經初始化的變量會放在RW屬性區,那麼剩下的變量就要放到ZI屬性區了。預設情況下,ZI資料的零初始化會将所有ZI資料區初始化為零,這是每次複位後程式執行C代碼的main函數之前,由編譯器“自作主張”完成的。是以我們要在C代碼中設定一些變量在複位後不被零初始化,那一定不能任由編譯器“胡作非為”,我們要用一些規則,限制一下編譯器。

       分散加載檔案對于連接配接器來說至關重要,在分散加載檔案中,使用UNINIT來修飾一個執行節,可以避免__main對該區節的ZI資料進行零初始化。這是要解決非零初始化變量的關鍵。是以我們可以定義一個UNINIT修飾的資料節,然後将希望非零初始化的變量放入這個區域中。于是,就有了第一種方法:

1. 修改分散加載檔案,增加一個名為MYRAM的執行節,該執行節起始位址為0x1000A000,長度為0x2000位元組(8KB),由UNINIT修飾:

   1: LR_IROM1 0x00000000 0x00080000  {    ; load region size_region

   2:   ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address

   3:    *.o (RESET, +First)

   4:    *(InRoot$$Sections)

   5:    .ANY (+RO)

   6:   }

   7:   RW_IRAM1 0x10000000 0x0000A000  {  ; RW data

   8:    .ANY (+RW +ZI)

   9:   }

  10:   MYRAM 0x1000A000 UNINIT 0x00002000  {

  11:    .ANY (NO_INIT)

  12:   }

  13: }

那麼,如果在程式中有一個數組,你不想讓它複位後零初始化,就可以這樣來定義變量:

    unsigned char  plc_eu_backup[PLC_EU_BACKUP_BUF/8] __attribute__((at(0x1000A000)));

       變量屬性修飾符__attribute__((at(adder)))用來将變量強制定位到adder所在位址處。由于位址0x1000A000開始的8KB區域ZI變量不會被零初始化,是以處在這一區域的數組plc_eu_backup也就不會被零初始化了。

       這種方法的缺點是顯而易見的:要自己配置設定變量的位址,如果非零初始化資料比較多,這将是件難以想象的大工程(以後的維護、增加、修改代碼等等)。是以要找到一種辦法,讓編譯器去自動配置設定這一區域的變量。

2. 分散加載文家同方法1,如果還是定義一個數組,可以用下面方法:

    unsigned char  plc_eu_backup[PLC_EU_BACKUP_BUF/8] __attribute__((section("NO_INIT"),zero_init));  

       變量屬性修飾符__attribute__((section(“name”),zero_init))用于将變量強制定義到name屬性資料節中,zero_init表示将未初始化的變量放到ZI資料節中。因為“NO_INIT”這顯性命名的自定義節,具有UNINIT屬性。

3. 如何将一個子產品内的非初始化變量都非零初始化?

假如該子產品名字為test.c,修改分散加載檔案如下所示:

   1: LR_IROM1 0x00000000 0x00080000  {    ; load region size_region

   2:   ER_IROM1 0x00000000 0x00080000  {  ; load address = execution address

   3:    *.o (RESET, +First)

   4:    *(InRoot$$Sections)

   5:    .ANY (+RO)

   6:   }

   7:   RW_IRAM1 0x10000000 0x0000A000  {  ; RW data

   8:    .ANY (+RW +ZI)

   9:   }

  10:   RW_IRAM2 0x1000A000 UNINIT 0x00002000  {

  11:    test.o (+ZI)

  12:   }

  13: }

定義時使用如下方法:

       int uTimerCount __attribute__((zero_init));

       這裡,變量屬性修飾符__attribute__((zero_init))用于将未初始化的變量放到ZI資料節中變量,其實keil預設情況下,未初始化的變量就是放在ZI資料區的。

4.将整個程式的非初始化變量都非零初始化 看了上面的,這個已經沒有必要說了。

原網址:http://blog.csdn.net/zhzht19861011/article/details/8780837

繼續閱讀