歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> ARM嵌入式開發中的GCC內聯匯編簡介

ARM嵌入式開發中的GCC內聯匯編簡介

日期:2017/3/1 10:06:49   编辑:Linux編程

在針對ARM體系結構的編程中,一般很難直接使用C語言產生操作協處理器的相關代碼,因此使用匯編語言來實現就成為了唯一的選擇。但如果完全通過匯編代碼實現,又會過於復雜、難以調試。因此,C語言內嵌匯編的方式倒是一個不錯的選擇。然而,使用內聯匯編的一個主要問題是,內聯匯編的語法格式與使用的編譯器直接相關,也就是說,使用不同的C編譯器內聯匯編代碼時,它們的寫法是各不相同的。下面介紹在ARM體系結構下GCC的內聯匯編。GCC內聯匯編的一般格式:

asm(


代碼列表
: 輸出運算符列表
: 輸入運算符列表
: 被更改資源列表
);

在C代碼中嵌入匯編需要使用asm關鍵字,在asm的修飾下,代碼列表、輸出運算符列表、輸入運算符列表和被更改的資源列表這4個部分被3個“:”分隔。下面,我們看一個例子:

void test(void)
{
……
asm(
"mov r1,#1\n"
:
:
:"r1"
);
……
}

注:換行符和制表符的使用可以使得指令列表看起來變得美觀。你第一次看起來可能有點怪異,但是當C編譯器編譯C語句的是候,它就是按照上面(換行和制表)生成匯編的。

函數test中內嵌了一條匯編指令實現將立即數1賦值給寄存器R1的操作。由於沒有任何形式的輸出和輸入,因此輸出和輸入列表的位置上什麼都沒有填寫。但是,在匯編代碼執行過程中R1寄存器會被修改,因此為了通知編譯器,在被更改資源列表中,需要寫上寄存器R1。

寄存器被修改這種現象發生的頻率還是比較高的。例如,在調用某段匯編程序之前,寄存器R1可能已經保存了某個重要數據,當匯編指令被調用之後,R1寄存器被賦予了新的值,原來的值就會被修改,所以,需要將會被修改的寄存器放入到被更改資源列表中,這樣編譯器會自動幫助我們解決這個問題。也可以說,出現在被更改資源列表中的資源會在調用匯編代碼一開始就首先保存起來,然後在匯編代碼結束時釋放出去。所以,上面的代碼與如下代碼從語義上來說是等價的。

void test(void)
{
……
asm(
"stmfd sp!,{r1}\n"
"mov r1,#1\n"
"ldmfd sp!,{r1}\n"
);
……
}

這段代碼中的內聯匯編既無輸出又無輸入,也沒有資源被更改,只留下了匯編代碼的部分。由於程序在修改R1之前已經將寄存器R1的值壓入了堆棧,在使用完之後,又將R1的值從堆棧中彈出,所以,通過被更改資源列表來臨時保存R1的值就沒什麼必要了。

在以上兩段代碼中,匯編指令都是獨立運行的。但更多的時候,C和內聯匯編之間會存在一種交互。C程序需要把某些值傳遞給內聯匯編運算,內聯匯編也會把運算結果輸出給C代碼。此時就可以通過將適當的值列在輸入運算符列表和輸出運算符列表中來實現這一要求。請看下面的例子:

void test(void)
{
int tmp=5;
asm(
"mov r4,%0\n"
:
:"r"(tmp)
:"r4"
);
}

上面的代碼中有一條mov指令,該指令將%0賦值給R4。這裡,符號%0代表出現在輸入運算符列表和輸出運算符列表中的第一個值。如果%1存在的話,那麼它就代表出現在列表中的第二個值,依此類推。所以,在該段代碼中,%0代表的就是“r”(tmp)這個表達式的值了。

那麼這個新的表達式又該怎樣解釋呢?原來,在“r”(tmp)這個表達式中,tmp代表的正是C語言向內聯匯編輸入的變量,操作符“r”則代表tmp的值會通過某一個寄存器來傳遞。在GCC4中與之相類似的操作符還包括“m”、“I”,等等,其含義見下表:

與輸入運算符列表的應用方法一致,當C語言需要利用內聯匯編輸出結果時,可以使用輸出運算符列表來實現,其格式應該是下面這樣的。

void test(void)
{
int tmp;
asm(
"mov %0,#1\n"
:"=r"(tmp)
:
);
}

在上面的代碼中,原本應出現在輸入運算符列表中的運算符,現在出現在了輸出運算符列表中,同時變量tmp將會存儲內聯匯編的輸出結果。這裡有一點可能已經引起大家的注意了,上面的代碼中操作符r的前面多了一個“=”。這個等號被稱為約束修飾符,其作用是對內聯匯編的操作符進行修飾。幾種修飾符的含義如下表所示:

當一個操作符沒有修飾符對其進行修飾時,代表這個操作符是只讀的。當我們需要將內聯匯編的結果輸出出來,那麼至少要保證該操作符是可寫的。因此,“=”或者“+”也就必不可少了。

Copyright © Linux教程網 All Rights Reserved