Keil MDK下如何設置非零初始化變量(復位后變量值不丟失)


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

 

轉載原文鏈接:https://blog.csdn.net/zhzht19861011/article/details/8780837


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM