Nginx 原始碼完全剖析(11)ngx_spinlock

鍾超發表於2012-10-12

Nginx 原始碼完全剖析(11)ngx_spinlock

Nginx 是多程式模式的,一個 master 與多個 workers,一般工作在多核 CPU 上,所以自旋鎖就是必須用到的。Nginx 中的自旋鎖的定義,位於 ngx_spinlock.c 中,如下:

void
ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin)
{

#if (NGX_HAVE_ATOMIC_OPS)

    ngx_uint_t  i, n;

    for ( ;; ) {

        // lock 即為鎖,是一個整數
        // ngx_atomic_cmp_set 是平臺相關的,一般都涉及內聯彙編
        if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
            return;
        }

        // 多核
        if (ngx_ncpu > 1) {

            // 等待與重試策略,見下面的描述
            for (n = 1; n < spin; n <<= 1) {

                for (i = 0; i < n; i++) {
                    ngx_cpu_pause();
                }

                if (*lock == 0 && ngx_atomic_cmp_set(lock, 0, value)) {
                    return;
                }
            }
        }

        ngx_sched_yield();
    }

#else

#if (NGX_THREADS)

#error ngx_spinlock() or ngx_atomic_cmp_set() are not defined !

#endif

#endif

}

其中用 lock 這個整形變數表示鎖,在筆者的機器(Darwin 12.0)上,是如下定義的:

typedef volatile ngx_atomic_uint_t  ngx_atomic_t;

再回到上面 spinlock 的原始碼分析中,如果 ngx_ncpu(表示 CPU 的核心數)超過 1 個,即多核 CPU,則要等待/重試。舉個例子,如果 spin 為 80,則第一次等待 1 個 ngx_cpu_pause() 操作,然後再次檢視鎖是否可用。接下來每輪分別等待 2個、4 個、8 個、16 個、32 個、64 個 ngx_cpu_pause() 操作後再試。這中間過程中如果出現鎖被釋放從而可以使用的情況,則迴圈會被中止,spinlock 函式會返回值。如果重試仍沒有成功,則執行 ngx_sched_yield,然後再重複上面的操作。

另外其中的 ngx_atomic_cmp_set 函式也很有探討價值。在 Darwin 12.0 上面是如下的巨集定義:

#define ngx_atomic_cmp_set(lock, old, new)                                    \
OSAtomicCompareAndSwap64Barrier(old, new, (int64_t *) lock)

在我一位朋友的 Linux 環境(具體忘記了,但是 x86),如下。其中的內聯彙編可以參考本部落格內的 GCC 內聯彙編的兩篇博文。其中的 SMP 為匯流排鎖。

static ngx_inline ngx_atomic_uint_t
ngx_atomic_cmp_set(ngx_atomic_t *lock, ngx_atomic_uint_t old,
    ngx_atomic_uint_t set)
{
    u_char  res;

    __asm__ volatile (

         NGX_SMP_LOCK
    "    cmpxchgl  %3, %1;   "
    "    sete      %0;       "

    : "=a" (res) : "m" (*lock), "a" (old), "r" (set) : "cc", "memory");

    return res;
}

這裡輸出為 res,儲存在 eax 暫存器中。輸入為 *lock(記憶體中)、old(eax中)、set(r 表示通用暫存器)。這樣 %0 就是 res,%1 就是 *lock,%2 就是 old,%3 就是 set。

如果 *lock 和 old 相等,則異或(cmpxchgl)為 0,則 ZF 為 1,sete 將 res(%0)的值設定為 1 並返回它。如果 *lock 和 old 不相等,則異火值非零,所以 ZF 非零,則 sete 不會執行動作,即 res 值為 0,即呼叫 ngx_atomic_cmp_set 失敗。

cmpxchgl 會影響 ZF(Zero Flag)標誌位。

-

轉載請註明來自鍾超的CSDN部落格:Blog.CSDN.net/Poechant

-

相關文章