天天看點

Linux核心之核心同步(三)——自旋鎖自旋鎖

自旋鎖

上回,我們說到為了避免并發,防止競争,核心提供了一些方法來實作對核心共享資料的保護。如果臨界區隻是一個變量,那麼使用原子操作即可,但實際上臨界區大多是一些資料操作的集合,這時候使用原子操作不太合理,我們就要用到鎖機制。Linux核心中常見的鎖機制有忙等待和睡眠鎖兩種,鎖代表分别為自旋鎖和信号量,本文主要介紹自旋鎖。

自旋鎖同一時刻隻能被一個核心任務持有,一個核心任務試圖獲得一個自旋鎖,如果該鎖沒有被其它核心任務争用,那麼它就可以馬上獲得這個自旋鎖,如果該鎖被其他核心任務争用,那麼它就一直在忙等待(自旋等待,占用CPU時間),直到另一個核心任務釋放該自旋鎖。
Linux核心之核心同步(三)——自旋鎖自旋鎖

自旋鎖常用于多處理器保護臨界區資源,在核心中主要用于中斷處理,它适用于短期輕量級鎖定,若是長時間鎖定最好用信号量。

自旋鎖的特性:

  • 忙等待
  • 同一時刻一個任務一把鎖
  • 盡快完成臨界區任務
  • 自旋鎖可在中斷上下文中使用

自旋鎖的定義:

自旋鎖定義的資料結構考慮了不同處理器體系結構的支援和實時性核心的要求,定義了 arch_spinlock_t 資料結構和 raw_spinlock 資料結構。在4.19核心中,自旋鎖 spinlock 資料結構定義在

include\linux\spinlock_types.h

中,spinlock 結構定義如下:

typedef struct spinlock {
	union {
		struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
		struct {
			u8 __padding[LOCK_PADSIZE];
			struct lockdep_map dep_map;
		};
#endif
	};
} spinlock_t;
           

可以看到 spinlock 對 raw_spinlock 做了封裝,再看raw_spinlock 結構:

typedef struct raw_spinlock {
	arch_spinlock_t raw_lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned int magic, owner_cpu;
	void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} raw_spinlock_t;
           

可以看到 raw_spinlock 對 arch_spinlock_t 做了封裝,再看 arch_spinlock_t 結構,在4.19核心中,arch_spinlock_t 結構定義在

include\linux\spinlock_types_up.h

中,arch_spinlock_t 結構定義如下:

#ifdef CONFIG_DEBUG_SPINLOCK

typedef struct {
	volatile unsigned int slock;
} arch_spinlock_t;

#define __ARCH_SPIN_LOCK_UNLOCKED { 1 }

#else

typedef struct { } arch_spinlock_t;

#define __ARCH_SPIN_LOCK_UNLOCKED { }

#endif

typedef struct {
	/* no debug version on UP */
} arch_rwlock_t;
           

對于以上結構:

  • 不同的處理器架構需要自定義資料類型 arch_spinlock_t ;
  • 盡量使用 spinlock ;
  • 絕對不允許被搶占和睡眠的地方,使用raw_spinlock,否則使用spinlock ;
  • 如果臨界區足夠小,使用raw_spinlock。

自旋鎖的使用:

簡單的使用舉例

DEFINE_SPINLOCK(my_lock); //定義一個自旋鎖
spin_lock(&my_lock); //加鎖
/*
/*臨界區*/
*/
spin_unlock(&my_locl); //解鎖
           

自旋鎖變種

請大家先設想這樣一種情況,我們在核心中操作一個連結清單,此時操作連結清單的地方就是一個臨界區,為了防止多處理器競争,我們使用自旋鎖來進行保護。當處于臨界區時,突然發生了外部硬體中斷,此時系統暫停目前程序的執行,轉去處理該中斷。沒想到的是,這個外部中斷也要操作這個連結清單,它首先要保護這個臨界區,就去申請自旋鎖了。可是,這個自旋鎖已經被别人持有了,中斷處理函數隻能在那裡忙等待,而自旋鎖又被中斷打斷而不能盡快釋放鎖,這時候就發生了死鎖。

Linux核心之核心同步(三)——自旋鎖自旋鎖

這時候就出現了Linux核心自旋鎖的變種spin_lock_irq() ,該函數在擷取自旋鎖時關閉本地CPU中斷,防止本地中斷處理程式和自旋鎖持有者之間争用鎖,其定義在4.19版核心

include\linux\spinlock.h

中,具體定義如下:

static __always_inline void spin_lock_irq(spinlock_t *lock)
{
	raw_spin_lock_irq(&lock->rlock);
}
           

raw_spin_lock_irq() 函數定義如下:

_raw_spin_lock_irq() 函數定義在4.19核心

include\linux\spinlock_api_smp.h

中:

#ifdef CONFIG_INLINE_SPIN_LOCK_IRQ
#define _raw_spin_lock_irq(lock) __raw_spin_lock_irq(lock)
#endif
           

__raw_spin_lock_irq() 函數定義如下:

static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
	local_irq_disable();
	preempt_disable();
	spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
	LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
           

spin_lock_irq() 函數比 spin_lock() 函數,多了一個 local_irq_disable() 函數,作用就是關閉本地處理器中斷,這樣就可以保證在擷取到自旋鎖時不會發生中斷,進而避免發生死鎖。

使用自旋鎖的重要原則是:擁有自旋鎖的臨界區代碼必須是原子執行,不能休眠或主動排程。

常見自旋鎖操作函數如下:

函數 作用
DEFINE_SPINLOCK(lock) 定義并且初始化靜态自旋鎖
spin_lock_init(spinlock_t *lock) 在運作時動态初始化自旋鎖
spin_lock(spinlock_t *lock) 申請自旋鎖,如果鎖被其他處理器占有,目前處理器自旋等待
spin_unlock(spinlock_t *lock) 釋放自旋鎖
spin_lock_bh(spinlock_t *lock) 申請自旋鎖,并且禁止目前處理器的軟中斷
spin_unlock_bh(spinlock_t *lock) 釋放自旋鎖,并且開啟目前處理器的軟中斷
spin_lock_irq(spinlock_t *lock) 申請自旋鎖,并且禁止目前處理器的硬中斷
spin_unlock_irq(spinlock_t *lock) 釋放自旋鎖,并且開啟目前處理器的硬中斷
spin_lock_irqsave(spinlock_t *lock,unsigned long flags) 申請自旋鎖,儲存目前處理器的硬中斷狀态,并且禁止目前處理器的硬中斷
spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags) 釋放自旋鎖,并且恢複目前處理器的硬中斷狀态
spin_trylock(spinlock_t *lock) 申請自旋鎖,如果申請成功,傳回1;如果鎖被其他處理器占有,目前處理器不等待,立即傳回0

繼續閱讀