Keil MDK下如何設定非零初始設定變數

來源:互聯網
上載者:User

       一些工控產品,當系統複位後(非上電複位),可能要求保持住複位前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.將整個程式的非初始設定變數都非零初始化 看了上面的,這個已經沒有必要說了。

本文搬運自我的個人部落格,原網址>點擊開啟連結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.