1. 程式人生 > >ARM處理器學習之--GPIO操作篇(gnu link script)

ARM處理器學習之--GPIO操作篇(gnu link script)

1:主要內容

      本文主要介紹了VMA、LMA的相關概念,gnu link script的作用和使用方法。

2:引言

      我們程式設計師剛開始學習編寫程式時,都會接觸到一個 " *.C " 檔案要經過編譯、連結等過程才能變成可以執行的程式。至於這裡的連結到底怎麼回事,我們今天就來談談這方面的內容。現在,我們有這樣一套ARM7的硬體開發環境,0X80000000地址開始BANK0 我們用的是NorFlash,0X40000000地址是晶片內部的RAM。我編譯、連結的程式下載到0x80000000地址處。而真正執行時,一部分初始化程式碼在0X80000000執行,初始化完畢後,將主要工作的程式碼copy到內部RAM 0X40000000開始的地方執行。因為內部RAM執行程式比較快,所以我想NorFlash充當電腦的硬碟的作用,讓其主要程式在RAM裡執行。這是今天主要的內容,當然,程式的功能還是和上一節

3:主要思路

 我把程式分成兩個主要部分,第一部分負責對晶片基本的操作和copy程式碼(從Norflashcopy到Sdram中),然後跳轉到Sdram中去執行程式。第二部分為程式的主要部分。那這樣的話,我對這兩部分程式通過連結器連結時則需要設定不同的執行地址。即,第一部分程式碼連結的執行地址是0X80000000,而第二部分程式碼連結的執行地址是0X40000000。但這兩部分組成的映像檔案的下載地址都是0X80000000.我想這個應該能通過連結器進行設定,通過查詢資料得知,這個可以通過連結器的連結指令碼來實現,在gnu arm 的連結器上是通過書寫一個*。lds的連結指令碼來實現的。

4:相關知識點  

   經過編譯,連結後生成的可執行檔案,其實有一定的結構。主要分為code段,data段,zi段(在gnu linux 下為.text .data .bss段)。這個code段,就是我們使用匯編,c語言,c++寫的程式指令,而data是程式中使用的變數,zi是程式中定義的未初始化的變數(由於這些內容本來就沒有被初始化,所以這些zi段沒有必要儲存在生成的映像檔案中,只是在程式真正執行時在相應的地址處預留出相應的空間即可)。檔案的連結簡圖:

   VMA(Virtual Memory Address)和LMA:(Load Memory Address)。這個LMA地址是程式裝載到儲存器時的地址,WMA可以理解成程式真正執行時所在的地址。

      而連結器指定的連結地址要和程式真正執行時所在的地址一致。這個也好理解,連結器就是根據你指定的連結地址進行整個映像的連結操作,一些絕對跳轉指令就是根據連結指定的地址進行更改PC值的,這些在上一講有所解釋。當然一般情況下,LMA和VMA的地址是一樣的,不過,在有些嵌入式開發的過程中,程式的裝載地址和執行地址不一樣,那在訪問連結地址和裝載地址不一樣的code、data、zi段的時候應該在真正訪問前將其copy到連結指定的地址上去。

gnu 連結指令碼的格式。gnu 連結指令碼是一個描述文字,用來描述怎麼連結最終的映像檔案。關於這個連結指令碼檔案,我們在具體案例中瞭解其用法。

5:實驗原始碼

initsystem.s

@******************************************************************************
@ 檔名  :initsystem.s
@ 功    能:初始化系統並copy程式碼
@ 
@ 作者    :張連聘
@ 建立時間:2014-06-22
@******************************************************************************

.text
.global _start

			
			@宣告常量
			.equ   DATA_DST,0x40000000  @目的地址
			.equ   DATA_SRC,0x80000000  @源地址
	
@引入外部標號
			.extern  MainLoop
			.extern	 start_copy_addr
			
_start:

 
				
					LDR PC,  ResetAddr
		 
ResetAddr: .word  ResetInit

ResetInit:

					LDR R0,=DATA_DST  			@RO 指向目的地址
					LDR R1,=start_copy_addr 	@R1 指向源地址
					MOV R10,#128     			@複製的個數為128*8*4=4K
CopyLoop:   		LDMIA R1!,{R2-R9}			@從R1指定的記憶體地址處裝載資料到R2--R9中
					STMIA R0!,{R2-R9}			@把R2--R9的資料複製到R0指定的記憶體中
					SUBS  R10,R10,#1
					BNE   CopyLoop
					
					
					LDR PC,=MainLoop
.end

control_led.s
@******************************************************************************
@ 檔名  :control_led.s
@ 功    能:利用P2.28控制led燈閃爍
@ 
@ 作者    :張連聘
@ 建立時間:2014-06-08
@******************************************************************************

 .text
.global MainLoop


StartMain:			
			
			
			@定義程式中使用到的常量		
			.equ   IO2DIR  ,0xE0028028	@ 控制IO0的輸入、輸出屬性暫存器
			.equ   IO2SET  ,0xE0028024  @IO2輸出1控制暫存器
			.equ   IO2CLR  ,0xE002802C  @IO2輸出0控制暫存器
			.equ   LEDCON  ,(1<<28)     @0x10000000
			
			

MainLoop:		    
					
					LDR R0,=IO2DIR      @IO2DIR              
					LDR R1,=LEDCON  
					STR R1,[R0]			@設定P2.28為輸出

					LDR R0,=IO2CLR  
					LDR R1,=LEDCON
					STR R1,[R0]        @P2.28為輸出0,熄滅led
					BL DELAYS		   @呼叫延時程式
			
					LDR R0,=IO2SET
					LDR R1,=LEDCON			
					STR R1,[R0]        @P2.28為輸出1,點亮led
					BL DELAYS		   @呼叫延時程式
		
			
					B MainLoop
			
@******************************************************************************
@ 名    CopyData
@ 功    能:複製程式碼,從0x8000***---->0x40000000 size:4K
@ 入口引數:無
@ 出口引數:無
@ 佔用資源:
@******************************************************************************
/*
 
CopyData:   		LDR R0,=DATA_DST  			@RO 指向目的地址
					MOV R10,#128     			@複製的個數為128*8*4=4K
CopyLoop:   		LDMIA R1!,{R2-R9}			@從R1指定的記憶體地址處裝載資料到R2--R9中
					STMIA R0!,{R2-R9}			@把R2--R9的資料複製到R0指定的記憶體中
					SUBS  R10,R10,#1
					BNE   CopyLoop
					MOV   PC,LR
*/			
@******************************************************************************
@ 名    稱:DELAYS
@ 功    能:軟體延時
@ 入口引數:無
@ 出口引數:無
@ 佔用資源:R7
@******************************************************************************

DELAYS:	
			LDR		R7,=0x00080000		@ 延時引數
DELAYS_L1:	SUBS	R7,R7,#1		    @ R7 = R7-1
			BNE		DELAYS_L1          	@ 判斷R7-1結果是否為0,若不為0則跳轉
			MOV		PC,LR				@ 返回		    
			
.end

led_control.lds
/*
* led_control 的連結指令碼。
*
*
*/

 MEMORY
{
   rom (rx)  : ORIGIN = 0x80000000, LENGTH = 2M
   ram (!rx) : ORIGIN = 0x40000000, LENGTH = 2M
}
ENTRY(_start)
SECTIONS
{

	. = 0x80000000 ;
	.init : 
	{	
		initsystem.o(.text)
		start_copy_addr = . ;
	} >rom
	. = 0x40000000 ;
	.main :
	AT (ADDR(.init)+SIZEOF(.init))
	{	
		control_led.o(.text)
	} >ram


	
	
}
Makefile
control_led.bin:control_led.s initsystem.s
	arm-linux-gcc -g -c -o control_led.o control_led.s
	arm-linux-gcc -g -c -o initsystem.o initsystem.s
	arm-linux-ld -Tled_control.lds -nostdlib -g  control_led.o initsystem.o -o control_led_elf
	arm-linux-objcopy -O binary -S control_led_elf  control_led.bin
clean:
	rm -f control_led.bin control_led_elf *.o



6:原始碼重點解釋

關於上面兩個.s的彙編檔案,這裡就不再贅述,請讀者自行分析。主要說說這個連結指令碼的相關知識。gnu 連結指令碼的詳細資料參見,gnu_Linker.pdf這個官方資料。

連結檔案的細節問題這裡也不再提及,只說一下關鍵點。

1問:為什麼我將初始化,copy的程式碼放在一個單獨的檔案裡?

1答:我最開始把所有程式碼放在一個檔案裡,使用.section 偽指令定義新的段名,在連結腳本里使用不同的地址存放不同的段。但程式一直不能正常執行,後反編譯得知,我這樣做,連結出來的映像檔案和我在連結腳本里指定的不一樣。後查資料得知,gnu link 對每個原始檔都有預設的三個段名:.text .data .bss 。連結腳本里的輸入段只允許這些段名。因此我將啟動程式碼單獨放在一個檔案裡,且所有的程式碼均在 .text 這個段裡。

2問:我在連結腳本里能定義標號嗎?定義的標號,怎麼在彙編裡引用吶?

2答:可以在連結腳本里定義標號,這裡定義的標號的意義等同於在程式語言裡的地址。在上面給出的例子中,我們copy程式碼並不是從第一條指令開始copy的,而是從執行完初始化和copy程式碼這些功能後開始指令。那我們怎麼知道initsystem裡面的指令到底佔用多少空間,我們在連結腳本里定義了 

start_copy_addr = . ;

其中
start_copy_addr 為標號的名稱,它的值被賦成 . 其中這個dot代表當前連結的地址,此時的地址是從0x80000000開始加上initsystem.o 裡所有程式碼長度後的值。那我們從這個地址開始copy程式碼是最合適的了,那這個地址在ARM 彙編裡怎麼使用哪?

.externLDR R1,=start_copy_addr @R1 指向源地址@先宣告這個標號

LDR R1,=start_copy_addr @R1 指向源地址

在c語言裡應該這樣:

extern  start_copy_addr ;

然後 使用&start_copy_addr 的方法來使用這個標號的值。

7:相關資料

ARM開發指南中文版系列文章。

gnu-assembler.pdf

gnu_Linker.pdf

linker&&loader.pdf

8:後記

其實,程式連結這方面的知識,對理解程式結構是很有幫助的。尤其做嵌入式軟體開發。我們閱讀uboot linux 核心等開源程式時,若不理解連結過程是很難閱讀透徹的。 另外,我們寫程式時,其實有很多偽指令都是針對編譯、連結器的。瞭解了這方面的知識,我們綜合利用程式、連結才能充分實現我們的想法。感興趣的同志可以仔細 翻閱我上面列出的參考文件。願此文對您有幫助。