嵌入式Linux熱門培訓內容之Linux內核自旋鎖

時間:2018-12-13 17:32:18

現在很多CPU都是幾核幾核的了,如果有一個變量A,CPU-X正在訪問,突然CPU-Y也過來訪問他,這時候就可能出現問題,因為這個A非常重要,可能導致系統崩潰,中斷異常等。

在進行中斷操作的時候,用到了自旋鎖,就是擔心正在操作的時候又被調用,聽起來有點拗口,但是就是那么一回事。

自旋鎖(spinlock)是用在多個CPU系統中的鎖機制,當一個CPU正訪問自旋鎖保護的臨界區時,臨界區將被鎖上,其他需要訪問此臨界區的CPU只能忙等待,直到前面的CPU已訪問完臨界區,將臨界區開鎖。自旋鎖上鎖后讓等待線程進行忙等待而不是睡眠阻塞,而信號量是讓等待線程睡眠阻塞。自旋鎖的忙等待浪費了處理器的時間,但時間通常很短,在1毫秒以下。

自旋鎖用于多個CPU系統中,在單處理器系統中,自旋鎖不起鎖的作用,只是禁止或啟用內核搶占。在自旋鎖忙等待期間,內核搶占機制還是有效的,等待自旋鎖釋放的線程可能被更高優先級的線程搶占CPU。

自旋鎖基于共享變量。一個線程通過給共享變量設置一個值來獲取鎖,其他等待線程查詢共享變量是否為0來確定鎖現是否可用,然后在忙等待的循環中"自旋"直到鎖可用為止。

通用自旋鎖解析

自旋鎖的狀態值為1表示解鎖狀態,說明有1個資源可用;0或負值表示加鎖狀態,0說明可用資源數為0。Linux內核為通用自旋鎖提供了API函數初始化、測試和設置自旋鎖。API函數功能說明如下表。

通用自旋鎖API函數功能說明

函數定義    功能說明

spin_lock_init(lock)    初始化自旋鎖,將自旋鎖設置為1,表示有一個資源可用。

spin_is_locked(lock)    如果自旋鎖被置為1(未鎖),返回0,否則返回1。

spin_unlock_wait(lock)    等待直到自旋鎖解鎖(為1),返回0;否則返回1。

spin_trylock(lock)    嘗試鎖上自旋鎖(置0),如果原來鎖的值為1,返回1,否則返回0。

spin_lock(lock)    循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。

spin_unlock(lock)    將自旋鎖解鎖(置為1)。

spin_lock_irqsave(lock, flags)    循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。關中斷,將狀態寄存器值存入flags。

spin_unlock_irqrestore(lock, flags)    將自旋鎖解鎖(置為1)。開中斷,將狀態寄存器值從flags存入狀態寄存器。

spin_lock_irq(lock)    循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。關中斷。

spin_unlock_irq(lock)    將自旋鎖解鎖(置為1)。開中斷。

spin_unlock_bh(lock)    將自旋鎖解鎖(置為1)。開啟底半部的執行。

spin_lock_bh(lock)    循環等待直到自旋鎖解鎖(置為1),然后,將自旋鎖鎖上(置為0)。阻止軟中斷的底半部的執行。

自旋鎖小例子

下面用一個使用自旋鎖鎖住鏈表的樣例,代碼列出如下(在gt9xx.c中):

/*初始化變量鎖*/

spin_lock_init(&ts->irq_lock);          // 2.6.39 內核版本后的初始化使用方式

// ts->irq_lock = SPIN_LOCK_UNLOCKED;   // 2.6.39 內核版本之前的初始化使用方式

void gtp_irq_enable(struct goodix_ts_data *ts)

{

    unsigned long irqflags = 0;// 中斷上下文使用的變量

    GTP_DEBUG_FUNC();

    spin_lock_irqsave(&ts->irq_lock, irqflags);//加鎖

    if (ts->irq_is_disable) 

    {

        enable_irq(ts->client->irq);

        ts->irq_is_disable = 0; 

    }

    spin_unlock_irqrestore(&ts->irq_lock, irqflags);//解鎖

}

自旋鎖用結構spinlock_t描述,在include/linux/spinlock.h中有類型 spinlock_t定義,列出如下:

typedef struct {

    raw_spinlock_t raw_lock;

#ifdef CONFIG_GENERIC_LOCKBREAK    /*引入另一個自旋鎖*/

    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;  /*映射lock實例到lock-class對象

#endif

} spinlock_t;

(1)spin_lock_init

函數spin_lock_init將自旋鎖狀態值設置為1,表示未鎖狀態。其列出如下(在include/linux/spinlock.h中):

# define spin_lock_init(lock)                    /

    do { *(lock) = SPIN_LOCK_UNLOCKED; } while (0)

宏__SPIN_LOCK_UNLOCKED列出如下(在include/linux/spinlock_types.h中):

# define __SPIN_LOCK_UNLOCKED(lockname) /

    (spinlock_t)    {   .raw_lock = __RAW_SPIN_LOCK_UNLOCKED,   /

                SPIN_DEP_MAP_INIT(lockname) }

#define __RAW_SPIN_LOCK_UNLOCKED    { 1 }

(2)函數spin_lock_irqsave

  函數spin_lock_irqsave等待直到自旋鎖解鎖,即自旋鎖值為1,它還關閉本地處理器上的中斷。其列出如下(在include/linux/spinlock.h中):

#define spin_lock_irqsave(lock, flags)    flags = _spin_lock_irqsave(lock)

函數spin_lock_irqsave分析如下(在kernel/spinlock.c中):

unsigned long __lockfunc _spin_lock_irqsave(spinlock_t *lock)

{

    unsigned long flags;

    local_irq_save(flags);  //將狀態寄存器的值寫入flags保存

    preempt_disable();      //關閉內核搶占,內核搶占鎖加1

    spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);

#ifdef CONFIG_LOCKDEP

    LOCK_CONTENDED(lock, _raw_spin_trylock, _raw_spin_lock);

#else

    _raw_spin_lock_flags(lock, &flags);

#endif

    return flags;

}

宏定義local_irq_save保存了狀態寄存器的內容到x中,同時關中斷。這個宏定義列出如下:

#define local_irq_save(x)    __asm__ __volatile__("pushfl ; popl %0 ; cli":"=g" (x): /* no input */ :"memory")

上述語句中,指令pushfl將當前處理器的狀態寄存器的內容壓入堆棧保護。指令popl %0 將狀態寄存器的內容存入x中,其中%0這里指x。

函數_raw_spin_lock_flags空操作等待直到自旋鎖的值為1,表示有資源可用,就跳出循環等待,準備執行本函數后面的操作。其列出如下:

# define _raw_spin_lock_flags(lock, flags) /

        __raw_spin_lock_flags(&(lock)->raw_lock, *(flags

函數__raw_spin_lock_flags列出如下(在include/asm-x86/spinlock.h中):

#define __raw_spin_lock_flags(lock, flags) __raw_spin_lock(lock)

static __always_inline void __raw_spin_lock(raw_spinlock_t *lock)

{

    int inc = 0x00010000;

    int tmp;

    /*指令前綴lock用來鎖住內存控制器,不讓其他處理器訪問,保證指令執行的原子性*/

    asm volatile("lock ; xaddl %0, %1/n"   // lock->slock=lock->slock+inc

             "movzwl %w0, %2/n/t"         //tmp=inc

             "shrl $16, %0/n/t"           //inc >> 16 后,inc=1

             "1:/t"

             "cmpl %0, %2/n/t"           //比較inc與lock->slock

             "je 2f/n/t"                 //如果inc與lock->slock相等,跳轉到2

             "rep ; nop/n/t"           //空操作

             "movzwl %1, %2/n/t"       //tmp=lock->slock

             /* 這里不需要讀內存屏障指令lfence,因為裝載是排序的*/

             "jmp 1b/n"               //跳轉到1

             "2:"

             : "+Q" (inc), "+m" (lock->slock), "=r" (tmp)

             :

             : "memory", "cc");

}

(3)函數spin_unlock_irqrestore

宏定義spin_unlock_irqrestore是解鎖,開中斷,并把flags值存入到狀態寄存器中,這個宏定義分析如下:

#define spin_unlock_irqrestore(lock, flags)    _spin_unlock_irqrestore(lock, flags)

函數_spin_unlock_irqrestore列出如下(在kernel/spinlock.c中):

void __lockfunc _spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)

{

    spin_release(&lock->dep_map, 1, _RET_IP_);

    _raw_spin_unlock(lock);    //解鎖

    local_irq_restore(flags);  //開中斷,將flag的值存入狀態寄存器

    preempt_enable();          //開啟內核搶占

}

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

函數__raw_spin_unlock將自旋鎖狀態值加1,表示有1個資源可用,從而釋放自旋鎖,其列出如下(在include/asm-x86/spinlock.h中):

static __always_inline void __raw_spin_unlock(raw_spinlock_t *lock)

{

    asm volatile(UNLOCK_LOCK_PREFIX "incw %0"       // lock->slock= lock->slock +1

             : "+m" (lock->slock)

             :

             : "memory", "cc");

}

自旋鎖調試:

由于自旋鎖的性能嚴重地影響著操作系統的性能,Linux內核提供了Lock-class和Lockdep跟蹤自旋鎖的使用對象和鎖的狀態,并可從/proc文件系統查詢自旋鎖的狀態信息。自旋鎖的調試通過配置項CONFIG_DEBUG_*項打開。

對于對稱多處理器系統(SMP),slock為一個int數據類型,對于單個處理器系統,slock定義為空。SMP的slock定義列出如下(在include/linux/spinlock_types.h):

typedef struct {    

     volatile unsigned int slock;

} raw_spinlock_t;

? 江苏快3号码表