引言 在當今的嵌入式系統設計中,ARM處理器以?xún)r(jià)格便宜、功耗低、集成度高、外設資源豐富和易于使用的特點(diǎn)而得到廣泛的應用;在速度和性能方面已達到或超過(guò)部分Pcl04嵌入式計算機的性能,而成本卻比相應的PCI04計算機低很多,廣泛應用于手機、GPS接收機、地圖導航、路由器、以太網(wǎng)交換機及其他民用和工業(yè)電子設備。 在一個(gè)采用ARM處理器的實(shí)時(shí)嵌入式系統中,目標硬件常常由Flash、SRAM、SDRAM和 NVRAM(非易失性RAM)等存儲器組成,并定位于不同的物理地址范圍,那么,怎樣通過(guò)軟件更好地訪(fǎng)問(wèn)和利用這些不同的存儲器并讓系統高效地運行?分散加載(scatter loading)就提供了這樣一種機制。它可以將內存變量定位于不同的物理地址上的存儲器或端口,通過(guò)訪(fǎng)問(wèn)內存變量即可達到訪(fǎng)問(wèn)外部存儲器或外設的目的;同時(shí)通過(guò)分散加載,讓大多數程序代碼在高速的內部RAM中運行,從而使得系統的實(shí)時(shí)性大大增強。 1 ARM ELF目標文件的主要構成 ARM EIF(ExectItable and Linking Format)目標文件主要由.Text段、.Data段、.BSS段構成,其他段如.debug段、.comment段等與本文關(guān)系不大,不作介紹。 .Text 段由可執行代碼組成,段類(lèi)型為Code,屬性為RO: .Data段由已初始化數據組成,段類(lèi)型為Data,屬性為RO: .BSS 段由未初始化數據組成,段類(lèi)型為Zero,屬性為Rw,在應用程序啟動(dòng)時(shí)對該段的數據初始化為零。如果在分散加載文件中指定了UNINIT屬性,則在應用程序啟動(dòng)時(shí)不初始化該段。 2 分散加載的基本原理 假設一個(gè)采用ARM處理器的實(shí)時(shí)嵌入式系統目標硬件的存儲器由ROM存儲器和RAM存儲器組成。當一個(gè)嵌入式系統在仿真環(huán)境下調試完畢,需要脫機運行的時(shí)候,就需要將源程序編譯連接成可執行目標代碼并燒寫(xiě)到ROM存儲器中。由于ROM存儲器存取數據的速率比RAM存儲器慢,因此,讓程序在ROM存儲器中運行。CPU每次取指令和取數據操作都要訪(fǎng)問(wèn)ROM存儲器,這樣需要在 CPU的總線(xiàn)周期中插入等待周期,通過(guò)降低總線(xiàn)的速率來(lái)滿(mǎn)足訪(fǎng)問(wèn)慢速的ROM存儲器,這樣勢必會(huì )降低CPU的運行速率和效率,因此,分散加載就顯得非常必要。 ARM的連接器提供了一種分散加載機制,在連接時(shí)可以根據分散加載文件(.scf文件)中指定的存儲器分配方案,將可執行鏡像文件分成指定的分區并定位于指定的存儲器物理地址。這樣,當嵌入式系統在復位或重新上電時(shí),在對CPU相應寄存器進(jìn)行初始化后,首先執行ROM存儲器的 Bootloader(自舉)代碼,根據連接時(shí)的存儲器分配方案,將相應代碼和數據由加載地址拷貝到運行地址,這樣,定位在RAM存儲器的代碼和數據就在 RAM存儲器中運行,而不再從ROM存儲器中取數據或取指令,從而大大提高了CPU的運行速率和效率。 分散加載的基本原理如圖1所示。 ![]() 3 分散加載文件語(yǔ)法 在一個(gè)實(shí)時(shí)嵌入式系統中,分散加載文件是對目標硬件中的多個(gè)存儲器塊的分塊描述,它直接對應目標硬件存儲器的起始地址和范圍。同時(shí),它在應用程序連接時(shí)用于告訴連接器用戶(hù)程序代碼和數據的加載地址和運行地址,在連接時(shí)由連接器產(chǎn)生相應的加載地址和運行地址符號,包括代碼和數據的加載起始地址、運行地址和長(cháng)度等。這些符號用于上電后執行啟動(dòng)代碼的數據拷貝工作,啟動(dòng)代碼根據這些符號,將指定代碼和數據由ROM中的加載地址拷貝到RAM中的運行地址中,從而實(shí)現代碼在高速RAM存儲器中的脫機運行。其語(yǔ)法格式如下: ![]() 注意: ①每一個(gè)分散加載文件必須至少包含一個(gè)根區,每個(gè)根區的加載地址等于執行地址。 ②每一個(gè)引導區必須至少包含一個(gè)執行區,每一個(gè)執行區必須至少包含一個(gè)代碼段或數據段;一個(gè)引導區可以包含幾個(gè)執行區,每一個(gè)執行區只能屬于一個(gè)引導區。 4 分散加載時(shí)連接器生成的預定義符號 在編譯連接時(shí)如果指定了分散加載文件 (.scf文件),在連接后會(huì )自動(dòng)生成如下變量: ![]() 5 重新實(shí)現_user_initial_stackheap()函數 分散加載機制提供了一種指定代碼和靜態(tài)數據布局的方法。使用分散加載時(shí),必須重新放置堆棧和堆。 應用程序的堆棧(stack)和堆 (heap)是在C庫函數初始化過(guò)程中建立起來(lái)的,在A(yíng)DSl.2或更新版本中,在缺省狀態(tài)下C庫函數初始化代碼會(huì )將連接器生成的符號 Image$$ZI$$Limit地址作為堆的基地址。在分散加載時(shí),連接器會(huì )將用戶(hù)的__user_initial_stackheap()函數代替C 庫函數默認的堆棧和堆初始化函數,并將其連接到用戶(hù)的鏡像文件中,用戶(hù)可通過(guò)重新實(shí)現__user_initial_stackheap()函數來(lái)改變堆棧和堆的位置,而適合自己的目標硬件。 __user_initial_staekheap()可以用C或匯編語(yǔ)言來(lái)實(shí)現。它必須返回如下參數: R0--堆基地址; r1——堆;刂; r2——堆長(cháng)度限制值(需要的話(huà)); r3 ——堆棧長(cháng)度限制值(需要的話(huà))。 當用戶(hù)使用分散加載功能的時(shí)候,必須重新實(shí)現__user_initial_stackheap(),否則連接器會(huì )報錯: Error:L6218E:Undefined symbol Image$$ZI$$一Limit(referred from sys—stackheap.o)。 注:Image$$ZI$$Iimit 變量為零初始化段(gI段)的末地址。未使用分散加載時(shí),堆默認就定位在zI段的末地址,如圖2所示。 --user_initial_stackheap() 函數的實(shí)現有兩種方法。 ![]() (1)共用一個(gè)存儲區 匯編語(yǔ)言如下: ![]() 這種方式定義的堆棧和堆共用一個(gè)存儲區,采用相向的增長(cháng)方向,如圖3所示。 ![]() (2)使用兩個(gè)存儲區 匯編語(yǔ)言如下: ![]() 這種方式定義的堆棧和堆分別采用兩個(gè)不同存儲區。堆棧采用向下增長(cháng),從地址 Ox40000到地址0x20000;堆采用向上增長(cháng),從地址0x28000000到地址0x28080000,如圖4所示。 6 特殊應用 6.1 定位目標外設 使用分散加載,可以將用戶(hù)定義的結構體或代碼定位到指定物理地址七的外設,這種外設可以是定時(shí)器、實(shí)時(shí)時(shí)鐘、靜態(tài)SRAM或者是兩個(gè)處理器間用于數據和指令通信的雙端口存儲器等。在程序中不必直接訪(fǎng)問(wèn)相應外設,只需訪(fǎng)問(wèn)相應的內存變量即可實(shí)現對指定外設的操作,因為相應的內存變量定位在指定的外設上。這樣,對外設的訪(fǎng)問(wèn)看不到相應的指針操作,對結構體成員的訪(fǎng)問(wèn)即可實(shí)現對外設相應存儲單元的訪(fǎng)問(wèn),讓程序員感覺(jué)到仿佛沒(méi)有外設,只有內存。 例如,一個(gè)帶有兩個(gè)32位寄存器的定時(shí)器外設,在系統中的物理地址為0x04000000,其C語(yǔ)言結構描述如下: ![]() 屬性UNINIT是避免在應用程序啟動(dòng)時(shí)對該執行段的ZI數據段初始化為零。 在程序連接后,通過(guò)Image map文件可查看該ZI數據段的存儲器分配情況: Execution Region TIMER(Base:0 x04000000,Size:0x00000008,Max:Oxffffffff,ABSOLUTE,UNINIT)Base.Addr Size Type Attr Idx E Section Name Object 0x04000000 0x00000008 Zero RW 32.bss timer_regs.o 從Image map文件可以看出,該TIMER執行區定位在物理地址Ox04000000,即結構體timer_regs定位在0x04000000,因此,在程序中對結構體的操作即是對定時(shí)器的操作。 6.2定義超大型結構體數組 分散加載機制在提供將指定代碼和數據定位在指定物理地址的能力的同時(shí),也提供了一種代碼分割機制——可以將指定的零初始化段(ZI段)從可執行代碼中分離出來(lái)。這樣最終生成的燒人ROM或Flash中的鏡像文件就不包括那部分分割了的零初始化段,即使該零初始化段再大,也不影響最終生成的鏡像文件的大小。但不采用分散加載機制,零初始化段在編譯連接后是直接生成到鏡像文件中的。它的大小直接影響最終要燒寫(xiě)的文件的大小,且零初始化段的大小還取決于內存的大小,它不能大到超過(guò)內存的大;而采用分散加載機制,可以將某個(gè)零初始化段定位到非內存地址的一個(gè)存儲器外設上,如NVRAM(非易失性隨機存儲器)。 筆者曾在一個(gè)實(shí)際工程中采用這種分散加載機制,將一個(gè)2MB的結構體數組定位到外部NVRAM中,用于記錄設備在工作過(guò)程中采集到的數據;而在本系統中,ARM處理器的內存只有256 KB,Flash存儲器也只有2 MB。如果不采用分散加載,程序根本無(wú)法運行,也不能燒寫(xiě)到Flash中。 采用分散加載,把對復雜外設的訪(fǎng)問(wèn)變成對結構體數組的訪(fǎng)問(wèn),使程序代碼精簡(jiǎn)易懂。對程序員來(lái)說(shuō),對結構體數組的操作還是和內存變量的操作一樣的。 結語(yǔ) 分散加載是嵌人式系統應用中不可或缺的一種加載方式,ARM、DSP、 PowerPC和MIPS等嵌入式處理器,都離不開(kāi)分散加載。這種分散加載的思想是通用的,只是不同處理器的實(shí)現方式不同。 本文詳細闡述了基于A(yíng)RM處理器的分散加載方法及其特殊應用,并以實(shí)際工程為例來(lái)說(shuō)明怎樣實(shí)現分散加載及使用分散加載的好處。它是筆者在實(shí)際工程應用中的心得體會(huì ),同時(shí)也是筆者工作經(jīng)驗的總結,希望本文對從事嵌入式系統設計和應用的工程技術(shù)人員能有所幫助。 參考文獻 1. ARM Limited ARM ELF File Format 1998 2. ARM Limited Application Note 48,Scatter Loading 1998 3. ARM Limited Application Note 107,Embedded Software Development with ADS v1.2 2002 作者:桂林長(cháng)海發(fā)展有限責任公司 夏爽 來(lái)源:單片機與嵌入式系統應用 2009 (4) |