Linux自旋鎖

來源:互聯網
上載者:User

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/yunsongice/archive/2010/05/18/5605264.aspx

加鎖(locking)是一種廣泛應用的同步技術。當核心控制路徑必須訪問共用資料結構或進入臨界區時,就需要為自己擷取一把“鎖”。由鎖機制保護的資源非常類似於限制於房間內的資源,當某人進入房間時,就把門鎖上。如果核心控制路徑希望訪問資源,就試圖擷取鑰匙“開啟門”。若且唯若資源空閑時,它才能成功。然後,只要它還想使用這個資源,門就依然鎖著。當核心控制路徑釋放了鎖時,門就開啟,另一個核心控制路徑就可以進入房間。

Linux鎖的應用之一在多處理器環境中,取名叫自旋鎖(spin lock)。如果核心控制路徑發現自旋鎖“開著”,就擷取鎖並繼續自己的執行。相反,如果核心控制路徑發現鎖由運行在另一個CPU上的核心控制路徑“鎖著”,就在周圍“旋轉”,反覆執行一條緊湊的迴圈指令,直到鎖被釋放。

自旋鎖的迴圈指令表示“忙等”。即使等待的核心控制路徑無事可做(除了浪費時間),它也在CPU上保持運行。不過,自旋鎖通常非常方便,因為很多核心資源只鎖1毫秒的時間片段;所以說,等待自旋鎖的釋放不會消耗太多CPU的時間。

一般來說,由自旋鎖所保護的每個臨界區都是禁止核心搶佔的。在單一處理器系統上,這種鎖本身並不起鎖的作用,自旋鎖技術僅僅是用來禁止或啟用核心搶佔。請注意,在自旋鎖忙等期間,因為並沒有進入臨界區,所以核心搶佔還是有效,因此,等待自旋鎖釋放的進程有可能被更高優先順序的所取代。這種設計是合理的,因為不能因為佔用CPU太久而使系統死結。

在Linux中,每個自旋鎖都用spinlock_t結構表示:
typedef struct {
    raw_spinlock_t raw_lock;
#if defined(CONFIG_PREEMPT) && defined(CONFIG_SMP)
    unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
    unsigned int magic, owner_cpu;
    void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map dep_map;
#endif
} spinlock_t;

typedef struct {
    volatile unsigned int slock;
} raw_spinlock_t;

其中包含兩個重要的欄位意義如下:

slock:該欄位表示自旋鎖的狀態:值為1表示“未加鎖”狀態,而任何負數和0都表示“加鎖”狀態。
break_lock:表示進程正在忙等自旋鎖(只在核心支援SMP和核心搶佔的情況下使用該標誌)。

核心提供六個宏用於初始化、測試及設定自旋鎖。所有這些宏都是基於原子操作的,這樣可以保證即使有多個運行在不同CPU上的進程試圖同時修改自旋鎖,自旋鎖也能夠被正確地更新。

1、spin_lock_init —— 初始化自旋鎖,並把自旋鎖的lock->raw_lock置為1(未鎖)

# define spin_lock_init(lock)                    \
do {                                \
    static struct lock_class_key __key;            \
                                \
    __spin_lock_init((lock), #lock, &__key);        \
} while (0)

void __spin_lock_init(spinlock_t *lock, const char *name,
              struct lock_class_key *key)
{
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    /*
     * Make sure we are not reinitializing a held lock:
     */
    debug_check_no_locks_freed((void *)lock, sizeof(*lock));
    lockdep_init_map(&lock->dep_map, name, key, 0);
#endif
    lock->raw_lock = (raw_spinlock_t)__RAW_SPIN_LOCK_UNLOCKED;
    lock->magic = SPINLOCK_MAGIC;
    lock->owner = SPINLOCK_OWNER_INIT;
    lock->owner_cpu = -1;
}

#define __RAW_SPIN_LOCK_UNLOCKED    { 1 }
#define SPINLOCK_MAGIC        0xdead4ead
#define SPINLOCK_OWNER_INIT    ((void *)-1L)

2、spin_unlock —— 把自旋鎖置為1(未鎖)

#if defined(CONFIG_DEBUG_SPINLOCK) || defined(CONFIG_PREEMPT) || \
    !defined(CONFIG_SMP)
# define spin_unlock(lock)        _spin_unlock(lock)
#else //我們還是重點關注後面的吧
# define spin_unlock(lock)        __raw_spin_unlock(&(lock)->raw_lock)
#endif

void __lockfunc _spin_unlock(spinlock_t *lock)
{
    spin_release(&lock->dep_map, 1, _RET_IP_);
    _raw_spin_unlock(lock);
    preempt_enable();
}

# define _raw_spin_unlock(lock)        __raw_spin_unlock(&(lock)->raw_lock)

static inline void __raw_spin_unlock(raw_spinlock_t *lock)
{
    __asm__ __volatile__(
        __raw_spin_unlock_string
    );
}

#define __raw_spin_unlock_string \
    "movb $1,%0" \
        :"+m" (lock->slock) : : "memory"

spin_unlock宏釋放以前獲得的自旋鎖,上面的代碼本質上執行下列組合語言指令:
movb $1, slp->slock
並在隨後調用preempt_enable()(如果不支援核心搶佔,preempt_enable()什麼都做)。注意,因為現在的80x86微處理器總是原子地執行記憶體中的唯寫訪問,所以不用lock位元組。

3、spin_unlock_wait —— 等待,直到自旋鎖變為1(未鎖)

#define spin_unlock_wait(lock)    __raw_spin_unlock_wait(&(lock)->raw_lock)
#define __raw_spin_unlock_wait(lock) \
    do { while (__raw_spin_is_locked(lock)) cpu_relax(); } while (0)
#define __raw_spin_is_locked(x) \
        (*(volatile signed char *)(&(x)->slock) <= 0) //如果大於0則為真,表示未鎖,則跳出while迴圈
#define cpu_relax()    rep_nop() //在迴圈中執行一條空指令:
static inline void rep_nop(void)
{
    __asm__ __volatile__("rep;nop": : :"memory");
}

4、spin_is_locked( ) —— 如果自旋鎖被置為1(未鎖),返回0;否則,返回1

#define spin_is_locked(lock)    __raw_spin_is_locked(&(lock)->raw_lock)
#define __raw_spin_is_locked(x) \
        (*(volatile signed char *)(&(x)->slock) <= 0)

5、spin_trylock( ) —— 把自旋鎖置為0(鎖上),如果原來鎖的值是1,則返回1;否則,返回0
#define spin_trylock(lock)        __cond_lock(_spin_trylock(lock))
int __lockfunc _spin_trylock(spinlock_t *lock)
{
    preempt_disable();
    if (_raw_spin_trylock(lock)) {
        spin_acquire(&lock->dep_map, 0, 1, _RET_IP_);
        return 1;
    }
   
    preempt_enable();
    return 0;
}

6、spin_lock —— 加鎖:迴圈,直到自旋鎖變為1(未鎖),然後,把自旋鎖置為0(鎖上)

spin_lock是最重要的一個宏。首先,我們看到在include/spinlock.h標頭檔裡,有:

#if defined(CONFIG_SMP) || defined(CONFIG_DEBUG_SPINLOCK)
# include <linux/spinlock_api_smp.h>  //多處理器情況
#else
# include <linux/spinlock_api_up.h>   //單一處理器情況
#endif

#define spin_lock(lock)            _spin_lock(lock)
#ifdef __LINUX_SPINLOCK_API_UP_H  
#define _spin_lock(lock)            __LOCK(lock)   //單一處理器情況
#else 
注意,該代碼上邊有一句#if !defined(CONFIG_PREEMPT) || !defined(CONFIG_SMP) || \
    defined(CONFIG_DEBUG_LOCK_ALLOC)
別去管它,因為上邊的英文注釋寫得很清楚了,這句代碼的意思是即使沒有定義核心搶佔或SMP,或者是自旋鎖調試,只要lockdep啟用了,也就是我們在剛才spinlock_t定義的那裡看到的#ifdef CONFIG_DEBUG_LOCK_ALLOC,都會假設在整個鎖的調試期間保持關中斷。這句#if就是這個意思,千萬別理解成沒有定義核心搶佔、SMP或自旋鎖調試了,切記切記。
/*
 * If lockdep is enabled then we use the non-preemption spin-ops
 * even on CONFIG_PREEMPT, because lockdep assumes that interrupts are
 * not re-enabled during lock-acquire (which the preempt-spin-ops do):
 */
//多處理器情況,並且允許核心搶佔:
void __lockfunc _spin_lock(spinlock_t *lock)
{
    //禁止搶佔
    preempt_disable();
    //這個函數在沒有定義自旋鎖調試的時候是空函數,我們不去管它
    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
    //相當於_raw_spin_lock(lock)
    LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);
}
    
在沒有定義自旋鎖調試的時候,LOCK_CONTENDED宏定義如下
#define LOCK_CONTENDED(_lock, try, lock) \
    lock(_lock)

我們看到其實就是調用_raw_spin_lock宏(位於include/linux/spinlock.h):
# define _raw_spin_lock(lock)        __raw_spin_lock(&(lock)->raw_lock)

於是,定位到include/asm-i386/Spinlock.h
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
    asm(__raw_spin_lock_string : "+m" (lock->slock) : : "memory");
}

展開:
#define __raw_spin_lock_string \
    "\n1:\t" \
    //原子減,如果不為負,跳轉到3f,3f後面沒有任何指令,即為退出
    LOCK_PREFIX " ; decb %0\n\t" \   
    "jns 3f\n" \
    "2:\t" \
    //重複執行nop,nop是x86的小延遲函數,執行空操作
    "rep;nop\n\t" \
    //比較0與lock->slock的值,如果lock->slock不大於0,跳轉到標號2,即繼續重複執行nop
    "cmpb $0,%0\n\t" \
    "jle 2b\n\t" \
    //如果lock->slock大於0,跳轉到標號1,重新判斷鎖的slock成員
    "jmp 1b\n" \
    "3:\n\t"

在上面的函數中,大家可能對"jmp 1b\n“比較難以理解。在我們一般的觀念裡,獲得一個鎖,將其值減1;釋放鎖時將其值加1;實際上在自旋鎖的實現中lock->slock只有兩個可能值,一個是0. 一個是1。釋放鎖的時候並不是將lock->slock加1,而是將其賦為1。請看在前面的自旋鎖釋放代碼spin_unlock中的詳細分析。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.