1. 程式人生 > 其它 >搶佔式核心與非搶佔式核心中的自旋鎖(spinlock)的差別

搶佔式核心與非搶佔式核心中的自旋鎖(spinlock)的差別

spin_lock()

在Linux2.6中,spin_lock()巨集有兩種實現方式,一種是具有核心搶佔的spin_lock(),一種是非搶佔式核心中的spin_lock(),下面先看下自旋鎖的資料結構,在Linux中,每個自旋鎖都用spinlock_t結構表示,如下:

   typedef struct {
        /**
         * 該欄位表示自旋鎖的狀態,值為1表示未加鎖,任何負數和0都表示加鎖
         */
        volatile unsigned int slock;

    #ifdef CONFIG_PREEMPT
        /**
         * 表示程序正在忙等待自旋鎖。
         * 只有核心支援SMP和核心搶佔時才使用本標誌。
         
*/ unsigned int break_lock; #endif } spinlock_t;

spin_lock()定義如下:

   #define spin_lock(lock)     _spin_lock(lock)

具有核心搶佔的_spin_lock巨集

 /**
     * 通過BUILD_LOCK_OPS(spin, spinlock);定義了_spin_lock,進而實現了spin_lock
     * 這是在具有核心搶佔時,spin_lock的實現。
     */
    #define BUILD_LOCK_OPS(op, locktype)                    \
    void
__lockfunc _##op##_lock(locktype##_t *lock) \ { \ /** * preempt_disable禁用核心搶佔。 * 必須在測試spinlock的值前,先禁止搶佔,原因很簡單: * 下面的迴圈中有可能會成功獲得自旋鎖,如果獲得鎖之後被搶佔了,將造成死鎖 */ preempt_disable(); \
for (;;) { \ /** * 呼叫_raw_spin_trylock,它對自旋鎖的slock欄位進行原子性的測試和設定。 * 本質上它執行以下程式碼: * movb $0,%al * xchgb %al, slp->slock * xchgb原子性的交換al和slp->slock記憶體單元的內容。如果原值>0,就返回1,否則返回0 * 換句話說,如果原來的鎖是開著的,就關掉它,它返回成功標誌。如果原來就是鎖著的,再次設定鎖標誌,並返回0。 */ if (likely(_raw_##op##_trylock(lock))) \ /** * 如果舊值是正的,表示鎖是開啟的,巨集結束,已經獲得自旋鎖了。 * 注意:返回後,本函式的一個負作用就是禁用搶佔了。配對使用unlock時再開啟搶佔。 * 請想一下禁用搶佔的必要性。 */ break; \ /** * 否則,無法獲得自旋鎖,就迴圈一直到其他CPU釋放自旋鎖。 * 在迴圈前,暫時開啟preempt_enable。也就是說,在等待自旋鎖的中間,程序是可能被搶佔的。 */ preempt_enable(); \ /** * break_lock表示有其他程序在等待鎖。 * 擁有鎖的程序可以判斷這個標誌,如果程序把持鎖的時間太長,可以提前釋放鎖。 */ if (!(lock)->break_lock) \ (lock)->break_lock = 1; \ /** * 執行等待迴圈,cpu_relax簡化成一條pause指令,對應rep;nop,即空操作 * 為什麼要加入cpu_relax,是有原因的,表面上看,可以用一段死迴圈的彙編來代替這個迴圈 * 但是實際上是不能那樣的的,那樣會鎖住匯流排,unlock想設定值都不能了。 * cpu_relax就是要讓CPU休息一下,把匯流排暫時讓出來。 */ while (!op##_can_lock(lock) && (lock)->break_lock) \ cpu_relax(); \ /** * 上面的死迴圈lock的值已經變化了。那麼關搶佔後,再次呼叫_raw_spin_trylock * 真正的獲得鎖還是在_raw_spin_trylock中。 */ preempt_disable(); \ } \ }

 

_raw_spin_trylock如下:

    static inline int _raw_spin_trylock(spinlock_t *lock)
    {
        char oldval;
        __asm__ __volatile__(
            "xchgb %b0,%1"
            :"=q" (oldval), "=m" (lock->slock)
            :"0" (0) : "memory");
        return oldval > 0;
    }

非搶佔式核心中的_spin_lock()

 void __lockfunc _spin_lock(spinlock_t *lock)
    {
        preempt_disable(); //一直關閉可搶佔 ,
        _raw_spin_lock(lock);
    }

_raw_spin_lock(lock)如下:

  /**
     * 對自旋鎖的slock欄位執行原子性的測試和設定操作。
     */
    static inline void _raw_spin_lock(spinlock_t *lock)
    {
    #ifdef CONFIG_DEBUG_SPINLOCK
        if (unlikely(lock->magic != SPINLOCK_MAGIC)) {
            printk("eip: %p\n", __builtin_return_address(0));
            BUG();
        }
    #endif
        __asm__ __volatile__(
            spin_lock_string
            :"=m" (lock->slock) : : "memory");
    }

    /* spin_lock_string如下: */

    #define spin_lock_string \
        "\n1:\t" \
        /**
         * %0對應上面的lock->slock
         * decb遞減自旋鎖的值。它有lock字首,因此是原子的。
         */
        "lock ; decb %0\n\t" \
        /**
         * 如果結果為0(不是負數),說明鎖是開啟的,跳到3f處繼續執行。
         */
        "jns 3f\n" \
        /**
         * 否則,結果為負,說明鎖是關閉的。就執行死迴圈,等待它的值變化。
         */
        "2:\t" \
        "rep;nop\n\t" \
        /**
         * 比較lock值,直到它變化,才跳到開頭,試圖再次獲得鎖。
         * 否則,繼續死迴圈等lock值變化。  ..自選期間不開啟可搶佔
         */
        "cmpb $0,%0\n\t" \
        "jle 2b\n\t" \
        "jmp 1b\n" \
        "3:\n\t"

 

 arch_spin_lock