本文提供了在 linux 平臺上使用和構造 x86 內聯匯編的概括性介紹。他介紹了內聯匯編及其各種用法的基礎知識,提供了一些基本的內聯匯編編碼指導,并解釋了在 Linux 內核中內聯匯編代碼的一些實例。 假如您是 linux 內核的開發人員,您會發現自己經常要對與體系結構高度相關的功能進行編碼或優化代碼路徑。您很可能是通過將匯編語言指令插入到 C 語句的中間(又稱為內聯匯編的一種方法)來執行這些任務的。讓我們看一下 Linux 中內聯匯編的特定用法。(我們將討論限制在 IA32 匯編。) [目錄]
GNU 匯編程序簡述 讓我們首先看一下 linux 中使用的基本匯編程序語法。GCC(用于 Linux 的 GNU C 編譯器)使用 AT&T 匯編語法。下面列出了這種語法的一些基本規則。(該列表肯定不完整;只包括了與內聯匯編相關的那些規則。) 寄存器命名 寄存器名稱有 % 前綴。即,假如必須使用 eax,它應該用作 %eax。
源操作數和目的操作數的順序 在所有指令中,先是源操作數,然后才是目的操作數。這與將源操作數放在目的操作數之后的 Intel 語法不同。 mov %eax, %ebx, transfers the contents of eax to ebx.
C 表達式用作 "asm" 內的匯編指令操作數。在匯編指令通過對 C 程序的 C 表達式進行操作來執行有意義的作業的情況下,操作數是內聯匯編的主要特性。 每個操作數都由操作數約束字符串指定,后面跟用括弧括起的 C 表達式,例如:"constraint" (C eXPRession)。操作數約束的主要功能是確定操作數的尋址方式。
可以在輸入和輸出部分中同時使用多個操作數。每個操作數由逗號分隔開。
在匯編程序模板內部,操作數由數字引用。假如總共有 n 個操作數(包括輸入和輸出),那么第一個輸出操作數的編號為 0,逐項遞增,最后那個輸入操作數的編號為 n-1。總操作數的數目限制在 10,假如機器描述中任何指令模式中的最大操作數數目大于 10,則使用后者作為限制。
"asm" 和寄存器約束 "r" 讓我們先看一下使用寄存器約束 r 的 "asm"。我們的示例顯示了 GCC 如何分配寄存器,以及它如何更新輸出變量的值。 int main(void) { int x = 10, y; asm ("movl %1, %%eax; "movl %%eax, %0;" :"=r"(y) /* y is output operand */ :"r"(x) /* x is input operand */ :"%eax"); /* %eax is clobbered register */ }
在該例中,x 的值復制為 "asm" 中的 y。x 和 y 都通過存儲在寄存器中傳遞給 "asm"。為該例生成的匯編代碼如下:
main: pushl %ebp movl %esp,%ebp subl $8,%esp movl $10,-4(%ebp) movl -4(%ebp),%edx /* x=10 is stored in %edx */ #APP /* asm starts here */ movl %edx, %eax /* x is moved to %eax */ movl %eax, %edx /* y is allocated in edx and updated */ #NO_APP /* asm ends here */ movl %edx,-8(%ebp) /* value of y in stack is updated with the value in %edx */
當使用 "r" 約束時,GCC 在這里可以自由分配任何寄存器。在我們的示例中,它選擇 %edx 來存儲 x。在讀取了 %edx 中 x 的值后,它為 y 也分配了相同的寄存器。
因為 y 是在輸出操作數部分中指定的,所以 %edx 中更新的值存儲在 -8(%ebp),堆棧上 y 的位置中。假如 y 是在輸入部分中指定的,那么即使它在 y 的臨時寄存器存儲值 (%edx) 中被更新,堆棧上 y 的值也不會更新。
因為 %eax 是在修飾列表中指定的,GCC 不在任何其它地方使用它來存儲數據。
輸入 x 和輸出 y 都分配在同一個 %edx 寄存器中,假設輸入在輸出產生之前被消耗。請注重,假如您有許多指令,就不是這種情況了。要確保輸入和輸出分配到不同的寄存器中,可以指定 & 約束修飾符。下面是添加了約束修飾符的示例。
int main(void) { int x = 10, y; asm ("movl %1, %%eax; "movl %%eax, %0;" :"=&r"(y) /* y is output operand, note the & constraint modifier. */ :"r"(x) /* x is input operand */ :"%eax"); /* %eax is clobbered register */ }
以下是為該示例生成的匯編代碼,從中可以明顯地看出 x 和 y 存儲在 "asm" 中不同的寄存器中。
main: pushl %ebp movl %esp,%ebp subl $8,%esp movl $10,-4(%ebp) movl -4(%ebp),%ecx /* x, the input is in %ecx */ #APP movl %ecx, %eax movl %eax, %edx /* y, the output is in %edx */ #NO_APP movl %edx,-8(%ebp)
#define rdtscll(val) __asm__ __volatile__ ("rdtsc" : "=A" (val)) The generated assembly looks like this (if val has a 64 bit memory space). #APP rdtsc #NO_APP movl %eax,-8(%ebp) /* As a result of A constraint movl %edx,-4(%ebp) %eax and %edx serve as outputs */ Note here that the values in %edx:%eax serve as 64 bit output.