作者
Bharata B
Rao
將各個部分組合起來
如果您是 Linux 內核的開發人員
您會發現自己經常要對與體系結構高度相關的功能進行編碼或優化代碼路徑
您很可能是通過將匯編語言指令插入到 C 語句的中間(又稱為內聯匯編的一種方法)來執行這些任務的
讓我們看一下 Linux 中內聯匯編的特定用法
(我們將討論限制在 IA
匯編
)
GNU 匯編程序簡述
讓我們首先看一下 Linux 中使用的基本匯編程序語法
GCC(用於 Linux 的 GNU C 編譯器)使用 AT&T 匯編語法
下面列出了這種語法的一些基本規則
(該列表肯定不完整
只包括了與內聯匯編相關的那些規則
)
寄存器命名
寄存器名稱有 % 前綴
即
如果必須使用 eax
它應該用作 %eax
源操作數和目的操作數的順序
在所有指令中
先是源操作數
然後才是目的操作數
這與將源操作數放在目的操作數之後的 Intel 語法不同
mov %eax
%ebx
transfers the contents of eax to ebx
操作數大小
根據操作數是字節 (byte)
字 (word) 還是長型 (long)
指令的後綴可以是 b
w 或 l
這並不是強制性的
GCC 會嘗試通過讀取操作數來提供相應的後綴
但手工指定後綴可以改善代碼的可讀性
並可以消除編譯器猜測不正確的可能性
movb %al
%bl
Byte move
movw %ax
%bx
Word move
movl %eax
%ebx
Longword move
立即操作數
通過使用 $ 指定直接操作數
movl $
xffff
%eax
will move the value of
xffff into eax register
間接內存引用
任何對內存的間接引用都是通過使用 ( ) 來完成的
movb (%esi)
%al
will transfer the byte in the memory
pointed by esi into al
register
內聯匯編
GCC 為內聯匯編提供特殊結構
它具有以下格式
GCG 的
asm
結構
asm ( assembler template
: output operands (optional)
: input operands (optional)
: list of clobbered registers
(optional)
);
本例中
匯編程序模板由匯編指令組成
輸入操作數是充當指令輸入操作數使用的 C 表達式
輸出操作數是將對其執行匯編指令輸出的 C 表達式
內聯匯編的重要性體現在它能夠靈活操作
而且可以使其輸出通過 C 變量顯示出來
因為它具有這種能力
所以
asm
可以用作匯編指令和包含它的 C 程序之間的接口
一個非常基本但很重要的區別在於簡單內聯匯編只包括指令
而擴展內聯匯編包括操作數
要說明這一點
考慮以下示例
內聯匯編的基本要素
{
int a=
b;
asm (
movl %
%%eax;
movl %%eax
%
;
:
=r
(b) /* output */
:
r
(a) /* input */
:
%eax
); /* clobbered register */
}
在上例中
我們使用匯編指令使
b
的值等於
a
請注意以下幾點
b
是輸出操作數
由 %
引用
a
是輸入操作數
由 %
引用
r
是操作數的約束
它指定將變量
a
和
b
存儲在寄存器中
請注意
輸出操作數約束應該帶有一個約束修飾符
=
指定它是輸出操作數
要在
asm
內使用寄存器 %eax
%eax 的前面應該再加一個 %
換句話說就是 %%eax
因為
asm
使用 %
%
等來標識變量
任何帶有一個 % 的數都看作是輸入/輸出操作數
而不認為是寄存器
第三個冒號後的修飾寄存器 %eax 告訴將在
asm
中修改 GCC %eax 的值
這樣 GCC 就不使用該寄存器存儲任何其它的值
movl %
%%eax 將
a
的值移到 %eax 中
movl %%eax
%
將 %eax 的內容移到
b
中
因為
b
被指定成輸出操作數
因此當
asm
的執行完成後
它將反映出更新的值
換句話說
對
asm
內
b
所做的更改將在
asm
外反映出來
現在讓我們更詳細的了解每一項的含義
匯編程序模板
匯編程序模板是一組插入到 C 程序中的匯編指令(可以是單個指令
也可以是一組指令)
每條指令都應該由雙引號括起
或者整組指令應該由雙引號括起
每條指令還應該用一個定界符結尾
有效的定界符為新行 (\n) 和分號 (;)
\n
後可以跟一個 tab(\t) 作為格式化符號
增加 GCC 在匯編文件中生成的指令的可讀性
指令通過數 %
%
等來引用 C 表達式(指定為操作數)
如果希望確保編譯器不會在
asm
內部優化指令
可以在
asm
後使用關鍵字
volatile
如果程序必須與 ANSI C 兼容
則應該使用 __asm__ 和 __volatile__
而不是 asm 和 volatile
操作數
C 表達式用作
asm
內的匯編指令操作數
在匯編指令通過對 C 程序的 C 表達式進行操作來執行有意義的作業的情況下
操作數是內聯匯編的主要特性
每個操作數都由操作數約束字符串指定
後面跟用括弧括起的 C 表達式
例如
constraint
(C expression)
操作數約束的主要功能是確定操作數的尋址方式
可以在輸入和輸出部分中同時使用多個操作數
每個操作數由逗號分隔開
在匯編程序模板內部
操作數由數字引用
如果總共有 n 個操作數(包括輸入和輸出)
那麼第一個輸出操作數的編號為
逐項遞增
最後那個輸入操作數的編號為 n
總操作數的數目限制在
如果機器描述中任何指令模式中的最大操作數數目大於
則使用後者作為限制
修飾寄存器列表
如果
asm
中的指令指的是硬件寄存器
可以告訴 GCC 我們將自己使用和修改它們
這樣
GCC 就不會假設它裝入到這些寄存器中的值是有效值
通常不需要將輸入和輸出寄存器列為 clobbered
因為 GCC 知道
asm
使用它們(因為它們被明確指定為約束)
不過
如果指令使用任何其它的寄存器
無論是明確的還是隱含的(寄存器不在輸入約束列表中出現
也不在輸出約束列表中出現)
寄存器都必須被指定為修飾列表
修飾寄存器列在第三個冒號之後
其名稱被指定為字符串
至於關鍵字
如果指令以某些不可預知且不明確的方式修改了內存
則可能將
memory
關鍵字添加到修飾寄存器列表中
這樣就告訴 GCC 不要在不同指令之間將內存值高速緩存在寄存器中
操作數約束
前面提到過
asm
中的每個操作數都應該由操作數約束字符串描述
後面跟用括弧括起的 C 表達式
操作數約束主要是確定指令中操作數的尋址方式
約束也可以指定
是否允許操作數位於寄存器中
以及它可以包括在哪些種類的寄存器中
操作數是否可以是內存引用
以及在這種情況下使用哪些種類的地址
操作數是否可以是立即數
約束還要求兩個操作數匹配
常用約束
在可用的操作數約束中
只有一小部分是常用的
下面列出了這些約束以及簡要描述
有關操作數約束的完整列表
請參考 GCC 和 GAS 手冊
寄存器操作數約束 (r)
使用這種約束指定操作數時
它們存儲在通用寄存器中
請看下例
asm (
movl %%cr
%
\n
:
=r
(cr
val));
這裡
變量 cr
val 保存在寄存器中
%cr
的值復制到寄存器上
cr
val 的值從該寄存器更新到內存中
指定
r
約束時
GCC 可以將變量 cr
val 保存在任何可用的 GPR 中
要指定寄存器
必須通過使用特定的寄存器約束直接指定寄存器名
a %eax
b %ebx
c %ecx
d %edx
S %esi
D %edi
內存操作數約束 (m)
當操作數位於內存中時
任何對它們執行的操作都將在內存位置中直接發生
這與寄存器約束正好相反
後者先將值存儲在要修改的寄存器中
然後將它寫回內存位置中
但寄存器約束通常只在對於指令來說它們是絕對必需的
或者它們可以大大提高進程速度時使用
當需要在
asm
內部更新 C 變量
而您又確實不希望使用寄存器來保存其值時
使用內存約束最為有效
例如
idtr 的值存儲在內存位置 loc 中
(
sidt %
\n
: :
m
(loc));
匹配(數字)約束
在某些情況下
一個變量既要充當輸入操作數
也要充當輸出操作數
可以通過使用匹配約束在
asm
中指定這種情況
asm (
incl %
:
=a
(var):
(var));
在匹配約束的示例中
寄存器 %eax 既用作輸入變量
也用作輸出變量
將 var 輸入讀取到 %eax
增加後將更新的 %eax 再次存儲在 var 中
這裡的
指定第
個輸出變量相同的約束
即
它指定 var 的輸出實例只應該存儲在 %eax 中
該約束可以用於以下情況
輸入從變量中讀取
或者變量被修改後
修改寫回到同一變量中
不需要將輸入操作數和輸出操作數的實例分開
使用匹配約束最重要的意義在於它們可以導致有效地使用可用寄存器
一般內聯匯編用法示例
以下示例通過各種不同的操作數約束說明了用法
有如此多的約束以至於無法將它們一一列出
這裡只列出了最經常使用的那些約束類型
asm
和寄存器約束
r
讓我們先看一下使用寄存器約束 r 的
asm
我們的示例顯示了 GCC 如何分配寄存器
以及它如何更
From:http://tw.wingwit.com/Article/program/Oracle/201311/17556.html