DPDK gcc內聯匯編

在DPDK中,使用gcc的內聯匯編實現高效率的函數,比如自旋鎖,cas操作等。今天簡單介紹一下gcc內聯匯編語法和DPDK利用內聯匯編實現的函數。

gcc內聯匯編

這里簡單介紹一下內聯匯編的語法,更詳細的可以參考官方文檔

內聯匯編格式如下,小括號中的參數使用分號分隔。AssemblerTemplate 中的匯編語句會從 InputOperands 讀取變量值,執行結束后會將結果寫到 OutputOperands 指定的變量中。

asm asm-qualifiers ( AssemblerTemplate 
                 : OutputOperands 
                 [ : InputOperands
                 [ : Clobbers ] ])

asm
是 GCC 里的關鍵字,或者使用 "asm",表示內聯匯編。

asm-qualifiers
asm修飾符,有三個值: volatile(指示GCC不要做優化),inline和goto(如果使用goto,小括號中必須有參數 GotoLabels)。

AssemblerTemplate
字符串,包含一條或多條匯編語句,也可以為空。GCC不會解析具體的匯編指令,因為GCC也不知道匯編語句的作用,甚至不知道匯編語法是否正確。
在匯編語句中可以引用 output,input和goto label中的變量,可以通過 %[name] 引用,也可以通過數字 %0, %1 等引用。

OutputOperands
指定零個或多個操作數,匯編語句最終會修改這些操作數。格式如下:

    [ [asmSymbolicName] ] constraint (cvariablename)
    asmSymbolicName: 指定 cvariablename 的一個別名,可以在匯編語句中訪問 %[name]。
                     如果不指定name,則可以使用基于數字的位置訪問。
                     比如有三個output操作數,可以使用 %0 引用第一個,使用 %1 引用第二個,使用 %2 引用第三個。
    
    constraint: 字符串常量,指定了約束條件。輸出約束必須以=(只寫)或者+(可讀寫)開頭。
    cvariablename: c的變量名,最終會修改此變量。

InputOperands
指定零個或多個變量或者表達式,匯編語句會從此讀取變量值。格式和OutputOperands一樣。

    [ [asmSymbolicName] ] constraint (cexpression)
    asmSymbolicName: 指定 cvariablename 的一個別名,可以在匯編語句中引用 %[name]。
    constraint: 字符串常量,指定了約束條件。輸入約束不能以=(只寫)或者+(可讀寫)開頭。
    cvariablename: c的變量名或者表達式。

Clobbers
指定一個列表,告訴GCC列表中的寄存器是有其他用處的,不能被GCC使用。
除了指定寄存器還有兩個特殊的Clobber: cc和memory。
cc會告訴GCC,匯編語句會修改 flags 寄存器。
memory告訴GCC,要將寄存器中的值刷新到內存,保證內存中包含正確的值,另外GCC也不要假定在執行匯編語句之前從內存讀的值和執行匯編之后的值相同,有可能會被匯編語句修改,所以執行完匯編語句 后要重新讀取。

DPDK利用內聯匯編實現的函數

下面看幾個DPDK利用內聯匯編實現的函數。

1. 讀取處理器時間戳計數
讀取處理器時間戳計數用到了一個匯編指令 rdtsc。下面介紹一下這個指令。

Reads the current value of the processor’s time-stamp counter (a 64-bit MSR) into the EDX:EAX registers. The EDX
register is loaded with the high-order 32 bits of the MSR and the EAX register is loaded with the low-order 32 bits.
(On processors that support the Intel 64 architecture, the high-order 32 bits of each of RAX and RDX are cleared.)

翻譯過來就是rdtsc 指令用來讀取處理器的時間戳計數(64位),并保存到寄存器 EDX:EAX 中,EDX 保存高32位,EAX保存低32位。如果為64位處理器,則寄存器 RAX 和 RDX 的高32位都會被清空,低32分別保存計數的高32和低32位。

DPDK中的實現代碼如下

static inline uint64_t
rte_rdtsc(void)
{
    union {
        uint64_t tsc_64;
        RTE_STD_C11
        struct {
            uint32_t lo_32;
            uint32_t hi_32;
        };
    } tsc;

    asm volatile("rdtsc" :
             //output
             "=a" (tsc.lo_32),  //a表示寄存器,GCC根據tsc.lo_32的類型決定使用32位還是64位,很顯然這里是32位的,則使用寄存器 EAX。
             "=d" (tsc.hi_32)); //同上,d表示寄存器 EDX。
    return tsc.tsc_64;
}

最終會將EAX代表的低32位值保存到 tsc.lo_32,EDX代表的高32位值保存到 tsc.hi_32。

2. 原子操作
原子操作(以加1為例)用到了兩個匯編指令: lock 和 inc。下面分別介紹這兩個指令。
a. lock 指令

Causes the processor’s LOCK# signal to be asserted during execution of the accompanying instruction (turns the
instruction into an atomic instruction). In a multiprocessor environment, the LOCK# signal ensures that the
processor has exclusive use of any shared memory while the signal is asserted.

lock指令可以保證只有一個cpu訪問內存。

b. inc 指令
inc 用來給目的操作數加1。

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.

在inc指令前,必須先使用lock指令,保證原子操作。

下面看一下DPDK中如何實現原子操作。
定義了宏MPLOCKED,只在多cpu時才會使用lock指令,只有一個cpu,宏MPLOCKED為空。

#if RTE_MAX_LCORE == 1
#define MPLOCKED                        /**< No need to insert MP lock prefix. */
#else
#define MPLOCKED        "lock ; "       /**< Insert MP lock prefix. */
#endif

16位原子操作加1

typedef struct {
    volatile int16_t cnt; /**< An internal counter value. */
} rte_atomic16_t;

static inline void
rte_atomic16_inc(rte_atomic16_t *v)
{
    asm volatile(
            MPLOCKED            /* 首先使用lock指令鎖住總線 */
            "incw %[cnt]"       /* 使用incw指令給cnt加1,incw中的w應該是word,表示兩個字節*/
            : [cnt] "=m" (v->cnt)   /* output */ v->cnt即作為輸入參數,又作為輸出參數 
            : "m" (v->cnt)          /* input */
            );
}

32原子操作加1,和16位的區別是,換成了指令incl,參數v->cnt 變成了32位

typedef struct {
    volatile int32_t cnt; /**< An internal counter value. */
} rte_atomic32_t;

static inline void
rte_atomic32_inc(rte_atomic32_t *v)
{
    asm volatile(
            MPLOCKED
            "incl %[cnt]"
            : [cnt] "=m" (v->cnt)   /* output */
            : "m" (v->cnt)          /* input */
            );
}

64原子操作加1,和前面的區別是,換成了指令incq(q為quadrupl,表示8個字節),參數v->cnt 變成了64位。

typedef struct {
    volatile int64_t cnt;  /**< Internal counter value. */
} rte_atomic64_t;

static inline void
rte_atomic64_inc(rte_atomic64_t *v)
{
    asm volatile(
            MPLOCKED
            "incq %[cnt]"
            : [cnt] "=m" (v->cnt)   /* output */
            : "m" (v->cnt)          /* input */
            );
}

3. 比較并交換操作
比較并交換操作用到了三個匯編指令: lock, cmpxchg 和 sete。下面分別介紹這三個指令。
a. lock
參考前面原子操作時的介紹。主要用來鎖住總線,保證只有一個cpu訪問內存。
b. cmpxchg 指令

Compares the value in the AL, AX, EAX, or RAX register with the first operand (destination operand). If the two
values are equal, the second operand (source operand) is loaded into the destination operand. Otherwise, the
destination operand is loaded into the AL, AX, EAX or RAX register. RAX register is available only in 64-bit mode.

This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically. To simplify the
interface to the processor’s bus, the destination operand receives a write cycle without regard to the result of the
comparison. The destination operand is written back if the comparison fails; otherwise, the source operand is
written into the destination. (The processor never produces a locked read without also producing a locked write

cmpxchg 指令將第一個操作數(目的操作數)和 A 寄存器比較,如果相等,則將第二個操作數(源操作數)賦給第一個操作數(目的操作數),并設置 ZF 為 1,否則將第一個操作數(目的操作數)賦給 A 寄存器,并設置 ZF 為0。使用此指令前也要先使用lock指令保證原子操作。

cmpxchg 實現的偽碼如下:

(* Accumulator = AL, AX, EAX, or RAX depending on whether a byte, word, doubleword, or quadword comparison is being performed *)
TEMP := DEST
IF accumulator = TEMP
    THEN
        ZF := 1;
        DEST := SRC;
    ELSE
        ZF := 0;
        accumulator := TEMP;
    DEST := TEMP;
FI;

c. sete 指令
如果 ZF 為 1,則設置操作數為 1,否則設置為 0。

DPDK中的實現代碼如下
dst指向一塊內存,exp為此內存之前的值,現在去dst內存中最新值和exp作比較,如果相等,則將src的值賦給dst,并返回1,如果不相等,則返回0。

static inline int
rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src)
{
    uint8_t res;

    asm volatile(
            MPLOCKED
            "cmpxchgl %[src], %[dst];"
            "sete %[res];"
            /* output */
            : [res] "=a" (res),     /* 將結果0或者1寫到變量res中 */
              [dst] "=m" (*dst)     /* 將src賦給dst指向的內存 */
            /* input */
            : [src] "r" (src),      /* 將src放在寄存器中,gcc會任選一個通用寄存器 */
              "a" (exp),            /* 將exp的值放到寄存器 a */
              "m" (*dst)            /* 讀取dst內存值,可以在上面的匯編語句中通過%[dst]訪問 */
            : "memory");            /* no-clobber list */ memory通知gcc執行匯編語句前要刷新寄存器,從內存讀取數據
    return res;
}

4. 自旋鎖的實現
自旋鎖操作用到了多個匯編指令,下面分別介紹一下。

a. xchg

Exchanges the contents of the destination (first) and source (second) operands. The operands can be two generalpurpose
registers or a register and a memory location. If a memory operand is referenced, the processor’s locking
protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or
absence of the LOCK prefix or of the value of the IOPL. (See the LOCK prefix description in this chapter for more
information on the locking protocol.)

指令 xchg 用來交換兩個操作數的內容。操作數可以是兩個通用寄存器,或者是 a 寄存器,或者是內存。
如果操作數從內存取,處理器的locking協議會自動實現原子操作,不用使用lock指令來保證。

b. test

Computes the bit-wise logical AND of first operand (source 1 operand) and the second operand (source 2 operand)
and sets the SF, ZF, and PF status flags according to the result. The result is then discarded.

test指令將兩個操作數相與,如果結果為0,則設置 ZF 為1,否則設置 ZF 為0。

test指令的偽碼如下

TEMP := SRC1 AND SRC2;
SF := MSB(TEMP);
IF TEMP = 0
    THEN ZF := 1;
    ELSE ZF := 0;
FI:

c. jz和jnz
jz: 如果 ZF 為 1,則跳轉
jnz: 如果 ZF 為 0,則跳轉

d. cmp

Compares the first source operand with the second source operand and sets the status flags in the EFLAGS register
according to the results. The comparison is performed by subtracting the second operand from the first operand
and then setting the status flags in the same manner as the SUB instruction. When an immediate value is used as
an operand, it is sign-extended to the length of the first operand.

cmp 指令用來比較兩個操作數的大小,如果相等,則設置 ZF 為1。

cmp指令的偽碼如下

temp := SRC1 ? SignExtend(SRC2);
ModifyStatusFlags; (* Modify status flags in the same manner as the SUB instruction*)

DPDK中的實現代碼如下
使用一個 volatile 修飾的變量 locked,如果加鎖了,locked值為1,沒加鎖值為0。

typedef struct {
    volatile int locked; /**< lock status 0 = unlocked, 1 = locked */
} rte_spinlock_t;

變量locked初始值為0

static inline void
rte_spinlock_init(rte_spinlock_t *sl)
{
    sl->locked = 0;
}

加鎖操作。此段匯編中有三個label: 1,2和3。
在label1處,讀取變量 locked,使用指令xchg和局部變量 lv 的值交換,然后使用指令test判斷 lv 是否為0,即判斷變量 locked 是否為0,如果為0,表示加鎖成功,變量 locked 值也變成1了,則跳轉到label3,如果不為0,說明變量 locked 已經被其他線程加1,即被其他線程加鎖,則執行label2。

在label2處,先pause一下,然后再讀取變量 locked,使用指令cmp判斷是否為0,如果為0,說明其他線程已經解鎖,跳轉到label1處,如果不為0,則繼續在label2出循環判斷。

在label3處,能到label3,說明加鎖成功,退出即可。

static inline void
rte_spinlock_lock(rte_spinlock_t *sl)
{
    int lock_val = 1;
    asm volatile (
            "1:\n"
            "xchg %[locked], %[lv]\n"   //locked和lv交換值
            "test %[lv], %[lv]\n"       //lv和lv相與,判斷結果
            "jz 3f\n"                   //如果為0,則加鎖成功,跳轉到label3
            "2:\n"                      //如果不為0,說明被其他線程加鎖了,則執行label2
            "pause\n"                   //暫停一下
            "cmpl $0, %[locked]\n"      //locked和0比較
            "jnz 2b\n"                  //locked不為0,說明其他線程還沒有釋放鎖
            "jmp 1b\n"                  //locked為0,說明其他線程已經解鎖,跳轉到label1,和lv交換值,將locked變成1,即加鎖成功
            "3:\n"
            : [locked] "=m" (sl->locked), [lv] "=q" (lock_val)
            : "[lv]" (lock_val)
            : "memory");
}

解鎖操作,將sl->locked值變成0

static inline void
rte_spinlock_unlock (rte_spinlock_t *sl)
{
    int unlock_val = 0;
    asm volatile (
            "xchg %[locked], %[ulv]\n"   //locked和lv交換值,locked變成0,解鎖
            : [locked] "=m" (sl->locked), [ulv] "=q" (unlock_val)
            : "[ulv]" (unlock_val)
            : "memory");
}

參考

https://cloud.tencent.com/developer/article/1520799
https://cloud.tencent.com/developer/article/1520798?from=article.detail.1520799
https://www.cnblogs.com/taek/archive/2012/02/05/2338838.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容