內嵌彙編代碼,每個input和output operand,由被方括號[]中的符號名,限制字符串,圓括號中的C表達式構成優化
// examplespa
static inline void __down_write(struct rw_semaphore *sem)
{
long oldcount;
#ifndef CONFIG_SMP
oldcount = sem->count;
sem->count += RWSEM_ACTIVE_WRITE_BIAS;
#else
long temp;
__asm__ __volatile__(
"1: ldq_l %0,%1\n"
" addq %0,%3,%2\n"
" stq_c %2,%1\n"
" beq %2,2f\n"
" mb\n"
".subsection 2\n"
"2: br 1b\n"
".previous"
:"=&r" (oldcount), "=m" (sem->count), "=&r" (temp)
:"Ir" (RWSEM_ACTIVE_WRITE_BIAS), "m" (sem->count) : "memory");
#endif
if (unlikely(oldcount))
rwsem_down_write_failed(sem);
}指針
轉載:接口
有時爲了高效,有時爲了直接控制硬件,有些模塊咱們不得不直接用匯編語言來編寫,而且對外提供調用的接口,隱藏細節,這其實就是內聯彙編。如何使用內聯彙編?咱們就以 GCC 爲例,一窺其中奧祕!內存
1、關鍵字
如何讓 GCC 知道代碼中內嵌的彙編呢? 藉助關鍵字!來看下面的例子:
__asm__ __volatile__("hlt");
__asm__ 表示後面的代碼爲內嵌彙編,asm 是 __asm__ 的別名。__volatile__ 表示編譯器不要優化代碼,後面的指令保留原樣,volatile 是它的別名。括號裏面是彙編指令。
2、示例分析
使用內嵌彙編,要先編寫彙編指令模板,而後將 C 語言表達式與指令的操做數相關聯,並告訴 GCC 對這些操做有哪些限制條件。示例以下:
__asm__ __violate__ ("movl %1,%0" : "=r" (result) : "m" (input));
movl %1,%0 是指令模板;%0 和 %1 表明指令的操做數,稱爲佔位符,內嵌彙編靠它們將C 語言表達式與指令操做數相對應。
指令模板後面用小括號括起來的是 C 語言表達式,本例中只有兩個:result 和 input ,他們按照出現的順序分別與指令操做數 %0 、%1 對應;注意對應順序:第一個 C 表達式對應 %0 ;第二個表達式對應 %1 ,依次類推,操做數至多有10 個,分別用 %0, %1 …. %9 表示。
在每一個操做數前面有一個用引號括起來的字符串,字符串的內容是對該操做數的限制或者說要求。result 前面的限制字符串是 =r ,其中 = 表示 result 是輸出操做數, r 表示須要將 result 與某個通用寄存器相關聯,先將操做數的值讀入寄存器,而後在指令中使用相應寄存器,而不是 result 自己,固然指令執行完後須要將寄存器中的值存入變量 result ,從表面上看好像是指令直接對 result 進行操做,實際上 GCC 作了隱式處理,這樣咱們能夠少寫一些指令。 input 前面的 r 表示該表達式須要先放入某個寄存器,而後在指令中使用該寄存器參加運算。
C 表達式或者變量與寄存器的關係由 GCC 自動處理,咱們只需使用限制字符串指導 GCC 如何處理便可。限制字符必須與指令對操做數的要求相匹配,不然產生的彙編代碼將會有錯,讀者能夠將上例中的兩個 r,都改成 m (m表示操做數放在內存,而不是寄存器中),編譯後獲得的結果是:
movl input, result
很明顯這是一條非法指令,所以限制字符串必須與指令對操做數的要求匹配。例如指令 movl 容許寄存器到寄存器,當即數到寄存器等,可是不容許內存到內存的操做,所以兩個操做數不能同時使用 m 做爲限定字符。
內嵌彙編語法以下:
__asm__(彙編語句模板: 輸出部分: 輸入部分: 破壞描述部分)
共四個部分:彙編語句模板,輸出部分,輸入部分,破壞描述部分,各部分使用「:」格開,彙編語句模板必不可少,其餘三部分可選,若是使用了後面的部分,而前面部分爲空,也須要用「:」格開,相應部份內容爲空。例如:
__asm__ __volatile__("cli": : :"memory")
具體這幾部分都有什麼限制呢?這得從細處着手!
3、語法細節
一、彙編語句模板
彙編語句模板由彙編語句序列組成,語句之間使用「;」、「\n」 或 「\n\t」 分開。指令中的操做數可使用佔位符引用 C 語言變量,操做數佔位符最多10 個,名稱以下:%0,%1,…,%9。指令中使用佔位符表示的操做數,總被視爲 long 型(4個字節),但對其施加的操做根據指令能夠是字或者字節,當把操做數看成字或者字節使用時,默認爲低字或者低字節。對字節操做能夠顯式的指明是低字節仍是次字節。方法是在 % 和序號之間插入一個字母,b 表明低字節,h 表明高字節,例如:%h1。
二、輸出部分
輸出部分描述輸出操做數,不一樣的操做數描述符之間用逗號格開,每一個操做數描述符由限定字符串和 C 語言變量組成。每一個輸出操做數的限定字符串必須包含「=」表示他是一個輸出操做數。 例如:
__asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x) )
描述符字符串表示對該變量的限制條件,這樣 GCC 就能夠根據這些條件決定如何分配寄存器,如何產生必要的代碼處理指令操做數與 C 表達式或 C 變量之間的聯繫。
三、輸入部分
輸入部分描述輸入操做數,不一樣的操做數描述符之間使用逗號格開,每一個操做數描述符由限定字符串和 C 語言表達式或者 C 語言變量組成。 示例以下:
例 1 :
__asm__ __volatile__ ("lidt %0" : : "m" (real_mode_idt));
例 2:
Static __inline__ void __set_bit(int nr, volatile void * addr)
{ ci
__asm__(
"btsl %1,%0"
:"=m" (ADDR)
:"Ir" (nr));
}
後例功能是將 (*addr) 的第 nr 位設爲 1。第一個佔位符 %0 與 C 語言變量 ADDR 對應,第二個佔位符 %1 與 C 語言變量 nr 對應。所以上面的彙編語句代碼與下面的僞代碼等價:btsl nr, ADDR,該指令的兩個操做數不能全是內存變量,所以將 nr 的限定字符串指定爲「Ir」,將 nr 與當即數或者寄存器相關聯,這樣兩個操做數中只有 ADDR 爲內存變量。
四、限制字符
限制字符有不少種,有些是與特定體系結構相關,此處僅列出經常使用的限定字符和i386中可能用到的一些經常使用的限定符。它們的做用是指示編譯器如何處理其後的 C 語言變量與指令操做數之間的關係。字符串
分類input |
限定符編譯器 |
描述it |
通用寄存器 |
「a」 |
將輸入變量放入eax |
「b」 |
將輸入變量放入ebx |
|
「c」 |
將輸入變量放入ecx |
|
「d」 |
將輸入變量放入edx |
|
「s」 |
將輸入變量放入esi |
|
「d」 |
將輸入變量放入edi |
|
「q」 |
將輸入變量放入eax,ebx,ecx,edx中的一個 |
|
「r」 |
將輸入變量放入通用寄存器,即eax,ebx,ecx,edx,esi,edi之一 |
|
「A」 |
把eax和edx合成一個64 位的寄存器(use long longs) |
|
內存 |
「m」 |
內存變量 |
「o」 |
操做數爲內存變量,但其尋址方式是偏移量類型, 也即基址尋址 |
|
「V」 |
操做數爲內存變量,但尋址方式不是偏移量類型 |
|
「 」 |
操做數爲內存變量,但尋址方式爲自動增量 |
|
「p」 |
操做數是一個合法的內存地址(指針) |
|
寄存器或內存 |
「g」 |
將輸入變量放入eax,ebx,ecx,edx之一,或做爲內存變量 |
「X」 |
操做數能夠是任何類型 |
|
當即數 |
「I」 |
0-31之間的當即數(用於32位移位指令) |
「J」 |
0-63之間的當即數(用於64位移位指令) |
|
「N」 |
0-255之間的當即數(用於out指令) |
|
「i」 |
當即數 |
|
「n」 |
當即數,有些系統不支持除字之外的當即數,則應使用「n」而非 「i」 |
|
匹配 |
「 0 」 |
表示用它限制的操做數與某個指定的操做數匹配 |
「1」 ... |
也即該操做數就是指定的那個操做數,例如「0」 |
|
「9」 |
去描述「%1」操做數,那麼「%1」引用的其實就是「%0」操做數,注意做爲限定符字母的0-9 與指令中的「%0」-「%9」的區別,前者描述操做數, 後者表明操做數。 |
|
& |
該輸出操做數不能使用過和輸入操做數相同的寄存器 |
|
操做數類型 |
「=」 |
操做數在指令中是隻寫的(輸出操做數) |
「+」 |
操做數在指令中是讀寫類型的(輸入輸出操做數) |
|
浮點數 |
「f」 |
浮點寄存器 |
「t」 |
第一個浮點寄存器 |
|
「u」 |
第二個浮點寄存器 |
|
「G」 |
標準的80387浮點常數 |
|
% |
該操做數能夠和下一個操做數交換位置,例如addl的兩個操做數能夠交換順序(固然兩個操做數都不能是當即數) |
|
# |
部分註釋,從該字符到其後的逗號之間全部字母被忽略 |
|
* |
表示若是選用寄存器,則其後的字母被忽略 |
五、破壞描述部分 破壞描述符用於通知編譯器咱們使用了哪些寄存器或內存,由逗號格開的字符串組成,每一個字符串描述一種狀況,通常是寄存器名;除寄存器外還有 「memory」。例如:「%eax」,「%ebx」,「memory」 等。