新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > uboot中位置無(wú)關(guān)代碼的程序設(shè)計(jì)

uboot中位置無(wú)關(guān)代碼的程序設(shè)計(jì)

作者: 時(shí)間:2016-11-09 來(lái)源:網(wǎng)絡(luò) 收藏
ARM處理器支持位置無(wú)關(guān)的程序設(shè)計(jì),這種程序加載到存儲(chǔ)器的任意地址空間都可以正常運(yùn)行,其設(shè)計(jì)方法在嵌入式應(yīng)用系統(tǒng)開(kāi)發(fā)中具有重要的作用。本文首先 介紹位置無(wú)關(guān)代碼的基本概念和實(shí)現(xiàn)原理,然后闡述基于A(yíng)RM匯編位置無(wú)關(guān)的程序設(shè)計(jì)方法和實(shí)現(xiàn)過(guò)程,最后以嵌入式Bootloader程序設(shè)計(jì)為例,介紹 位置無(wú)關(guān)程序設(shè)計(jì)在Bootloader程序設(shè)計(jì)中的作用。

引言

基于位置無(wú)關(guān)代碼 PIC(Position Independent Code)的程序設(shè)計(jì)在嵌入式應(yīng)用系統(tǒng)開(kāi)發(fā)中具有重要的作用,尤其在裸機(jī)狀態(tài)下開(kāi)發(fā)Bootloader程序及進(jìn)行內(nèi)核初始化設(shè)計(jì);利用位置無(wú)關(guān)的程序設(shè) 計(jì)方法還可以在具體應(yīng)用中用于構(gòu)建高效率動(dòng)態(tài)鏈接庫(kù),因而深入理解和熟練掌握位置無(wú)關(guān)的程序設(shè)計(jì)方法,有助于開(kāi)發(fā)人員設(shè)計(jì)出結(jié)構(gòu)簡(jiǎn)單、清晰的應(yīng)用程序。本 文首先介紹位置無(wú)關(guān)代碼的基本概念和實(shí)現(xiàn)原理,然后闡述基于A(yíng)RM匯編位置無(wú)關(guān)的程序設(shè)計(jì)方法和實(shí)現(xiàn)過(guò)程,最后以Bootloader程序設(shè)計(jì)為例,介紹 了位置無(wú)關(guān)程序設(shè)計(jì)在Bootloader程序設(shè)計(jì)中的作用。

1 位置無(wú)關(guān)代碼及程序設(shè)計(jì)方法

1.1 基本概念與實(shí)現(xiàn)原理

應(yīng)用程序必須經(jīng)過(guò)編譯、匯編和鏈接后才變成可執(zhí)行文件,在鏈接時(shí),要對(duì)所有目標(biāo)文件進(jìn)行重定位(relocation),建立符號(hào)引用規(guī)則,同時(shí)為變 量、函數(shù)等分配運(yùn)行地址。當(dāng)程序執(zhí)行時(shí),系統(tǒng)必須把代碼加載到鏈接時(shí)所指定的地址空間,以保證程序在執(zhí)行過(guò)程中對(duì)變量、函數(shù)等符號(hào)的正確引用,使程序正常 運(yùn)行。在具有操作系統(tǒng)的系統(tǒng)中,重定位過(guò)程由操作系統(tǒng)自動(dòng)完成。

在設(shè)計(jì)Bootloader程序時(shí),必須在裸機(jī)環(huán)境中進(jìn)行,這時(shí) Bootloader映像文件的運(yùn)行地址必須由程序員設(shè)定。通常情況下,將 Bootloader程序下載到ROM的0x0地址進(jìn)行啟動(dòng),而在大多數(shù)應(yīng)用系統(tǒng)中,為了快速啟動(dòng),首先將Bootloader程序拷貝到SDRAM中再 運(yùn)行。一般情況下,這兩者的地址并不相同,程序在SDRAM中的地址重定位過(guò)程必須由程序員完成。實(shí)際上,由于Bootloader是系統(tǒng)上電后要執(zhí)行的 第一段程序,Bootloader程序的拷貝和在這之前的所有工作都必須由其自身來(lái)完成,而這些指令都是在ROM中執(zhí)行的。也就是說(shuō),這些代碼即使不在鏈 接時(shí)所指定的運(yùn)行時(shí)地址空間,也可以正確執(zhí)行。這就是位置無(wú)關(guān)代碼,它是一段加載到任意地址空間都能正常執(zhí)行的特殊代碼。

位置無(wú)關(guān)代碼常用于以下場(chǎng)合:

◆ 程序在運(yùn)行期間動(dòng)態(tài)加載到內(nèi)存;
◆ 程序在不同場(chǎng)合與不同程序組合后加載到內(nèi)存(如共享的動(dòng)態(tài)鏈接庫(kù));
◆ 在運(yùn)行期間不同地址相互之間的映射(如Bootloader程序)。

雖然在用GCC編譯時(shí),使用-fPIC選項(xiàng)可為C語(yǔ)言產(chǎn)生位置無(wú)關(guān)代碼,但這并不能修正程序設(shè)計(jì)中固有的位置相關(guān)性缺陷。特別是匯編語(yǔ)言代碼,必須由程序員遵循一定的程序設(shè)計(jì)準(zhǔn)則,才能保證程序的位置無(wú)關(guān)性。

1.2 ARM處理器的位置無(wú)關(guān)程序設(shè)計(jì)要點(diǎn)

ARM程序的位置無(wú)關(guān)可執(zhí)行文件PIE(PositionIndependent Executable)包括位置無(wú)關(guān)代碼PIC和位置無(wú)關(guān)數(shù)據(jù)PID(PositionIndependent Data)兩部分。

PID主要針對(duì)可讀寫(xiě)數(shù)據(jù)段(.data段),其中保存已賦初值的全局變量。為實(shí)現(xiàn)其位置無(wú)關(guān)性,通常使用寄存器R9作為靜態(tài)基址寄存器,使其指向該可 讀寫(xiě)段的首地址,并使用相對(duì)于基址寄存器的偏移量來(lái)對(duì)該段的變量進(jìn)行尋址。這種方法常用于為可重入程序的多個(gè)實(shí)例產(chǎn)生多個(gè)獨(dú)立的數(shù)據(jù)段。在程序設(shè)計(jì)中,一 般不必考慮可讀寫(xiě)段的位置無(wú)關(guān)性,這主要是因?yàn)榭勺x寫(xiě)數(shù)據(jù)主要分配在SDRAM中。

PIC包括程序中的代碼和只讀數(shù)據(jù)(.text段),為保證程序能在ROM和SDRAM空間都能正確運(yùn)行(如裸機(jī)狀態(tài)下的Bootloader程序),必須采用位置無(wú)關(guān)代碼程序設(shè)計(jì)。下面重點(diǎn)介紹PIC的程序設(shè)計(jì)要點(diǎn)。

PIC遵循只讀段位置無(wú)關(guān)ROPI(ReadOnly Position Independence)的ATPCS(ARMThumb Procedure Call Standard)的程序設(shè)計(jì)規(guī)范:

(1) 程序設(shè)計(jì)規(guī)范

引用同一ROPI段或相對(duì)位置固定的另一ROPI段中的符號(hào)時(shí),必須是基于PC的符號(hào)引用,即使用相對(duì)于當(dāng)前PC的偏移量來(lái)實(shí)現(xiàn)跳轉(zhuǎn)或進(jìn)行常量訪(fǎng)問(wèn)。

① 位置無(wú)關(guān)的程序跳轉(zhuǎn)。在A(yíng)RM匯編程序中,使用相對(duì)跳轉(zhuǎn)指令B/BL實(shí)現(xiàn)程序跳轉(zhuǎn)。指令中所跳轉(zhuǎn)的目標(biāo)地址用基于當(dāng)前PC的偏移量來(lái)表示,與鏈接時(shí)分配給地址標(biāo)號(hào)的絕對(duì)地址值無(wú)關(guān),因而代碼可以在任何位置進(jìn)行跳轉(zhuǎn),實(shí)現(xiàn)位置無(wú)關(guān)性。

另外,還可使用ADR或ADRL偽指令將地址標(biāo)號(hào)值讀取到PC中實(shí)現(xiàn)程序跳轉(zhuǎn)。這是因?yàn)锳DR或ADRL等偽指令會(huì)被編譯器替換為對(duì)基于PC的地址值進(jìn) 行操作,但這種方式所能讀取的地址范圍較小,并且會(huì)因地址值是否為字對(duì)齊而異?! 〉贏(yíng)RM程序中,使用LDR等指令直接將地址標(biāo)號(hào)值讀取到 PC中實(shí)現(xiàn)程序跳轉(zhuǎn)不是位置無(wú)關(guān)的。例如:

LDRPC, =main

上面的LDR匯編偽指令編譯后的結(jié)果為:

LDRPC, [PC, OFFSET_TO_LPOOL]
LPOOLDCD main

可見(jiàn),雖然LDR是把基于PC的一個(gè)存儲(chǔ)單元LPOOL的內(nèi)容加載到PC中,但該存儲(chǔ)單元中保存的卻是鏈接時(shí)所決定的main函數(shù)入口的絕對(duì)地址,所以main函數(shù)實(shí)際所在的段不是位置無(wú)關(guān)。

② 位置無(wú)關(guān)的常量訪(fǎng)問(wèn)。在應(yīng)用程序中,經(jīng)常要讀寫(xiě)相關(guān)寄存器以完成必要的硬件初始化。為增強(qiáng)程序的可讀性,利用EQU偽指令對(duì)一些常量進(jìn)行賦值,但在訪(fǎng)問(wèn)過(guò)程中,必須實(shí)現(xiàn)位置無(wú)關(guān)性。下面以PXA270的GPIO初始化介紹位置無(wú)關(guān)的常量訪(fǎng)問(wèn)方法。

GPIO_BASEEQU0x40e00000;
GPIO基址寄存器地址GPDR0EQU0x00c;相對(duì)于GPIO基址寄存器的偏移量
init_GPDR0EQU0xfffbfe00;寄存器GPDR0初值
LDRR1, =GPIO_BASE
LDRR0, =init_GPDR0
STRR0, [R1, #GPDR0]

上述匯編代碼段經(jīng)編譯后的結(jié)果為:

LDRR1, [PC, OFFSET_TO_GPIO_BASE]
LDRR0, [PC, OFFSET_TO_init_GPDR0]
STRR0, [R1, #0xc]
GPIO_BASEDCD0x40e00000
GPDR0DCD0x00c
init_GPDR0DCD0xfffbfe00

可見(jiàn),LDR偽指令實(shí)際上使用基于PC的偏移量來(lái)對(duì)符號(hào)常量GPIO_BASE和init_GPDR0進(jìn)行引用,因而是位置無(wú)關(guān)的。由此可以得出如下結(jié) 論:使用LDR偽指令將一個(gè)常量讀取到非PC的其他通用寄存器中可實(shí)現(xiàn)位置無(wú)關(guān)的常量訪(fǎng)問(wèn);但將一個(gè)地址值讀取到PC中進(jìn)行程序跳轉(zhuǎn)時(shí),跳轉(zhuǎn)目標(biāo)則是位置 相關(guān)的。

(2) 程序設(shè)計(jì)規(guī)范2

其他被ROPI段中的代碼引用的必須是絕對(duì)地址,或者是基于可讀寫(xiě)位置無(wú)關(guān)(RWPI)段的靜態(tài)基址寄存器的可寫(xiě)數(shù)據(jù)。

使用絕對(duì)地址只能引用被重定位到特定位置的代碼段中的符號(hào),通過(guò)在位置無(wú)關(guān)代碼中引入絕對(duì)地址,可以讓程序跳轉(zhuǎn)到指定位置。例如,假設(shè) Bootloader的階段1將其自身代碼拷貝到鏈接時(shí)所指定的SDRAM地址空間后,當(dāng)要跳轉(zhuǎn)到階段2的C程序入口時(shí),可以使用指令“LDRPC, =main”跳轉(zhuǎn)到程序在SDRAM中的main函數(shù)入口地址開(kāi)始執(zhí)行。這是因?yàn)槌绦蛟诰幾g鏈接時(shí)給main函數(shù)分派絕對(duì)地址,系統(tǒng)通過(guò)將main函數(shù)的 絕對(duì)地址直接賦給PC實(shí)現(xiàn)程序跳轉(zhuǎn)。如果使用相對(duì)跳轉(zhuǎn)指令“Bmain”,那么只會(huì)跳轉(zhuǎn)到啟動(dòng)ROM內(nèi)部的main函數(shù)入口。

2 位置無(wú)關(guān)代碼在Bootloader設(shè)計(jì)中的應(yīng)用

在使用GNU工具開(kāi)發(fā)Bootloader時(shí),程序在鏈接時(shí)會(huì)通過(guò)一個(gè)鏈接腳本(linker script)來(lái)設(shè)定映像文件的內(nèi)存映射。一個(gè)簡(jiǎn)單的鏈接腳本結(jié)構(gòu)如下:

OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = BOOTADDR;/*Bootloader的起始地址*/
__boot_start = .;
.textALIGN(4): {/*代碼段.text*/
*(.text)
}
.dataALIGN(4): {/*數(shù)據(jù)段.data*/
*(.data)
}
.gotALIGN(4): {/*全局偏移量表.got段*/
*(.got)
}
__boot_end = .;/*Bootloader映像文件的結(jié)束地址*/
.bssALIGN(16): {/*堆棧段.bss*/
__bss_start = .;
*(.bss)
__bss_end =.;
}
}

這里不再介紹鏈接腳本的語(yǔ)法。需要指出的是,鏈接腳本中所描述的輸出段地址為虛擬地址VMA(Virtual Memory Address)。這里的“虛擬地址”僅指映像文件執(zhí)行時(shí),各輸出段所重定位到相應(yīng)的存儲(chǔ)地址空間,與內(nèi)存管理無(wú)關(guān)。因此,上面的鏈接腳本實(shí)際上指定了 Bootloader映像在執(zhí)行時(shí),將被重定位到BOOTADDR開(kāi)始的存儲(chǔ)地址空間,以保證在相關(guān)位置對(duì)符號(hào)進(jìn)行正確引用,使程序正常運(yùn)行。

ARM處理器復(fù)位后總是從0x0地址取第1條指令,因此只需把BOOTADDR設(shè)置為0,再把編譯后生成的可執(zhí)行二進(jìn)制文件下載到ROM的 0x0地址開(kāi)始的存儲(chǔ)空間,程序便可正常引導(dǎo);但是,一旦在鏈接時(shí)指定映像文件從0x0地址開(kāi)始,那么Bootloader就只能在0x0地址開(kāi)始的 ROM空間內(nèi)運(yùn)行,而無(wú)法拷貝到SDRAM空間運(yùn)行實(shí)現(xiàn)快速引導(dǎo)。當(dāng)然,對(duì)PXA270等具有MMU功能的微處理器來(lái)說(shuō),雖然可以先將 Bootloader映像整個(gè)拷貝到SDRAM中,再使用MMU功能將SDRAM空間映射到0x0地址,進(jìn)而繼續(xù)在SDRAM中運(yùn)行;但這樣一方面會(huì)使得 Bootloader的設(shè)計(jì)與實(shí)現(xiàn)復(fù)雜化,另一方面在一些必須屏蔽MMU功能的應(yīng)用中(例如引導(dǎo)armlinux系統(tǒng)),無(wú)法使用MMU進(jìn)行地址重映射。

利用ARM的基于位置無(wú)關(guān)的程序設(shè)計(jì)可以解決上述問(wèn)題。只需在程序鏈接時(shí),將BOOTADDR設(shè)置為SDRAM空間的地址(一般情況 下利用 SDRAM中最高的1 MB存儲(chǔ)空間作為起始地址),這樣ARM處理器上電復(fù)位后Bootloader仍然可以從地址0開(kāi)始執(zhí)行,并將自身拷貝到指定的__boot_start 起始的SDRAM中運(yùn)行。實(shí)現(xiàn)上述功能的鏈接腳本所對(duì)應(yīng)的啟動(dòng)代碼架構(gòu)如下:

.section .text
.globl _start
_start:
Breset/*復(fù)位異常*/
/*其他異常處理代碼*/
reset:
/*復(fù)位處理程序*/
copy_boot:/*拷貝Bootloader到SDRAM*/
LDRR0, =0x0LDRR1, =__boot_start
LDRR2, =__boot_end
1:LDRMIA R0!, { R3-R10 }
STRMIA R1!, { R3-R10 }
CMPR1, R2
BLT1b
clear_bss:
/*清零.bss段*/
BL
init_Stack/*初始化堆棧*/
LDRPC, = main/*跳轉(zhuǎn)到階段2的C程序入口*/
.end

程序入口為_(kāi)start,即復(fù)位異常,所有其他異常向量都使用相對(duì)跳轉(zhuǎn)指令B來(lái)實(shí)現(xiàn),以保證位置無(wú)關(guān)特性。在完成基本的硬件初始化后,利用鏈接腳本傳遞 過(guò)來(lái)__boot_start和__boot_end的參數(shù),將Bootloader映像整個(gè)拷貝到指定的SDRAM空間,并清零.bss段,初始化堆棧 后,程序?qū)ain函數(shù)入口的絕對(duì)地址賦給PC,進(jìn)而跳轉(zhuǎn)到SDRAM中繼續(xù)運(yùn)行。程序在跳轉(zhuǎn)到main函數(shù)之前,所有的代碼都在ROM中運(yùn)行,因而必須 要保證代碼的位置無(wú)關(guān)性,所以在調(diào)用初始化GPIO、存儲(chǔ)系統(tǒng)和堆棧等子程序時(shí),都使用相對(duì)跳轉(zhuǎn)指令來(lái)完成。

使用位置無(wú)關(guān)設(shè)計(jì)Bootloader程序有如下優(yōu)點(diǎn):

① 簡(jiǎn)化設(shè)計(jì),方便實(shí)現(xiàn)系統(tǒng)的快速引導(dǎo)。位置無(wú)關(guān)代碼可以避免在引導(dǎo)時(shí)進(jìn)行地址映射,并方便地跳轉(zhuǎn)到SDRAM中實(shí)現(xiàn)快速引導(dǎo)。


② 實(shí)現(xiàn)復(fù)位處理智能化。由于位置無(wú)關(guān)代碼可以被加載到任意地址空間運(yùn)行,因此其運(yùn)行時(shí)的當(dāng)前地址與鏈接時(shí)所指派的地址并不一定相同。利用這一特性,可以在復(fù) 位處理程序中使處理器進(jìn)入SVC模式并關(guān)閉中斷后加入如下代碼,便可根據(jù)當(dāng)前運(yùn)行時(shí)的地址進(jìn)行不同的復(fù)位處理:

ADRR0, _start/*讀取當(dāng)前PC附近的_start標(biāo)號(hào)所在指令地址*/
LDRR1,=__boot_start/*讀取Bootloader在SDRAM的起始地址*/
CMPR0,R1
BEQclear_bss

上述代碼中的ADR指令讀取的_start標(biāo)號(hào)地址由指令的執(zhí)行地址決定。若是從SDRAM中的Bootloader啟動(dòng),則上述比較結(jié)果相等,程序直 接跳轉(zhuǎn)到clear_bss標(biāo)號(hào)地址處執(zhí)行,這樣可以避免存儲(chǔ)系統(tǒng)的重新初始化和Bootloader的拷貝過(guò)程;若是上電或硬件復(fù)位,程序從 ROM啟動(dòng),則上述比較結(jié)果不等,程序便進(jìn)行包括系統(tǒng)初始化和Bootloader拷貝等過(guò)程的全面復(fù)位處理操作。

③ 便于調(diào)試。Bootloader的調(diào)試通常也是一個(gè)繁瑣的過(guò)程,使用位置無(wú)關(guān)代碼,則可以將映像文件加載到SDRAM中進(jìn)行調(diào)試,這既能真實(shí)地反映程序從ROM中進(jìn)行系統(tǒng)引導(dǎo)的情況,又可以避免頻繁燒寫(xiě)程序存儲(chǔ)器。

3 結(jié)論

本文所介紹的基于位置無(wú)關(guān)的程序設(shè)計(jì)是通過(guò)基于PC或基址寄存器的符號(hào)引用規(guī)范來(lái)實(shí)現(xiàn)的。這種方法在實(shí)際系統(tǒng)開(kāi)發(fā)中應(yīng)用廣泛,既能用于引導(dǎo)程序的設(shè)計(jì), 也可用于一般的應(yīng)用程序或嵌入式共享庫(kù)的開(kāi)發(fā)。而在Bootloader的設(shè)計(jì)中引入位置無(wú)關(guān)代碼,可以使程序結(jié)構(gòu)更為簡(jiǎn)單清晰,并能避免地址重映射并從 SDRAM進(jìn)行快速系統(tǒng)引導(dǎo);引用位置無(wú)關(guān)的設(shè)計(jì)方法使Bootloader的復(fù)位處理功能更為靈活,還使得在SDRAM中和在ROM中進(jìn)行程序調(diào)試具有 相同的效果。

本文引用地址:http://m.butianyuan.cn/article/201611/317894.htm


評(píng)論


技術(shù)專(zhuān)區(qū)

關(guān)閉