參考
Spinlock 可以分成 UP(Uni Processing) 以及 SMP(Symmetric Multi-Processing) 兩個部份來看,這代表兩種不同的架構下,spinlock 的實作會有些不同
如果想要知道每個檔案詳細代表什麼,可以參考這份檔案 include/linux/spinlock.h 裡面的註解,有詳細解釋每個檔案在幹麻,但他的檔案路徑有些有打錯,比如說 asm
其實應該要是 asm-generic
如果是要了解 Debug 的模式,還需要其他檔案,但我們以下完全不探討如何 Debug
typedef struct spinlock {
struct raw_spinlock rlock;
} spinlock_t;
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
} raw_spinlock_t;
typedef struct qspinlock {
atomic_t val;
} arch_spinlock_t;
typedef struct {
volatile int counter;
} atomic_t;
只是一個 volatile int
在 Kernel 中居然需要包這麼多層 ...... 非常的不直覺,也讓人無法理解這命名到底在衝三小
上面那篇文章提到,有人提出了一個新的版本,希望在 Critical Section(CS) 中,要使得 spin_lock
允許被搶佔,但 Linux 中已經使用了大量的 spinlock_t
,如果要全部的地方都做修改,太過不切實際,所以就將 spinlock_t
改名為 raw_spinlock_t
,並將原本的 raw_spinlock_t
改名為 arch_spinlock_t
,這樣就可以直接使用 spinlock_t
了
總之,在那次更新 spinlock
就已經不是教科書上定義的 spinlock
了,在 Linux 中是可以被搶佔的
作者幫我們下了三個結論
spin_lock
raw_spin_lock
raw_spin_lock
spinlock_t
允許被搶佔
raw_spinlock_t
不允許被搶佔
arch_spinlock_t
和處理器架構相關的程式碼,會依照架構不同要重新撰寫,如:x86 與 ARM 的差別
以下將對應的 Function 放在一起,但只解釋前者的意義
spin_lock_init
初始化spin_lock
, spin_unlock
獲取 Lockspin_lock_irq
, spin_unlock_irq
在 Local Processor 上禁止硬體中斷產生,並獲取指定 Lockspin_lock_irqsave
, spin_lock_irqrestore
保存 Local Processor 當前的 irq 狀態,然後在 Local Processor 上禁止硬體中斷產生,並獲取指定 Lockspin_lock_bh
, spin_unlock_bh
禁止軟體中斷產生,並獲取指定 Lockspin_trylock
嘗試獲得 Lock ,失敗的話會直接返回,而不是執行 Spin可以發現有四種上鎖方法 spin_lock
, spin_lock_irq
, spin_lock_irqsave
, spin_lock_bh
,只是前置作業不太一樣,但他核心運作原理,都與 spin_lock
相同,所以只需要挑 spin_lock_init
, spin_lock
來閱讀,即可知道運作原理
spin_lock_init
#define spin_lock_init(_lock) \
do { \
spinlock_check(_lock); \
raw_spin_lock_init(&(_lock)->rlock); \
} while (0)
static __always_inline raw_spinlock_t *spinlock_check(spinlock_t *lock)
{
return &lock->rlock;
}
#define raw_spin_lock_init(lock) \
do { \
*(lock) = __RAW_SPIN_LOCK_UNLOCKED(lock); \
} while (0)
#define __RAW_SPIN_LOCK_UNLOCKED(lockname) \
(raw_spinlock_t) __RAW_SPIN_LOCK_INITIALIZER(lockname)
#define __RAW_SPIN_LOCK_INITIALIZER(lockname) \
{ \
.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED, \
}
#define __ARCH_SPIN_LOCK_UNLOCKED { { .val = ATOMIC_INIT(0) } }
#define ATOMIC_INIT(i) { (i) }
這部份很直觀的就能夠理解,最終就是給予一個 0 作為初始值,代表 Unlocked 的意思
spin_lock
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock)
實作都要分成兩個部份來看 UP
, SMP
,從 _raw_spin_lock
開始產生差異
#define _raw_spin_lock(lock) __LOCK(lock)
#define ___LOCK(lock) \
do { __acquire(lock); (void)(lock); } while (0)
#define __LOCK(lock) \
do { preempt_disable(); ___LOCK(lock); } while (0)
只是單純的禁止搶佔
#define _raw_spin_lock(lock) __raw_spin_lock(lock)
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
禁止搶佔後,嘗試獲得 Lock,以及搶佔 Lock
preeempt_disable
#define preempt_disable() \
do { \
preempt_count_inc(); \
barrier(); \
} while (0)
static __always_inline int preempt_count(void)
{
return READ_ONCE(current_thread_info()->preempt_count);
}
preempt_count
是一個搶佔用的計數器,但他又不只是搶佔計數器,他同時代表了搶佔計數器、software irq counter
, hard irq counter
,反正他就是一個 int 數值,透過他是否等於 0 ,來決定是否可以搶佔,而這個數值會放在 thread_info
的架構中,因為一個 Process 在執行的時候,基礎單位就是 Thread 嘛,所以等於你自己帶在身上的意思啦,如果想知道更多 preempt_count
的定義,可以查閱 include/linux/preempt.h 的註解
spin_acquire
#define spin_acquire(l, s, t, i) lock_acquire_exclusive(l, s, t, NULL, i)
#define lock_acquire_exclusive(l, s, t, n, i) lock_acquire(l, s, t, 0, 1, n, i)
void lock_acquire(struct lockdep_map *lock, unsigned int subclass,
int trylock, int read, int check,
struct lockdep_map *nest_lock, unsigned long ip)
{
unsigned long flags;
if (unlikely(current->lockdep_recursion))
return;
raw_local_irq_save(flags);
check_flags(flags);
current->lockdep_recursion = 1;
trace_lock_acquire(lock, subclass, trylock, read, check, nest_lock, ip);
__lock_acquire(lock, subclass, trylock, read, check,
irqs_disabled_flags(flags), nest_lock, ip, 0, 0);
current->lockdep_recursion = 0;
raw_local_irq_restore(flags);
}
raw_local_irq_save
就是指 irq_save
,但這跟硬體相關,所以再幫他包一層界面,方便不同硬體實作各自的涵式
lockdep
如何偵測 Deadlock,這有一整套機制,等我們了解完 Spinlock 再做研究
__lock_acquire
原始碼在這裡 是關於如何獲得 Lock 的部份,但程式碼太多了,就不全部貼上來看,在檔案註解中有提到幾點
// 確認現在深度,但暫時不把深度++
unsigned int depth;
depth = curr->lockdep_depth;
......
// 對 depth 做完各種確認,就可以開始初始化 hlock
struct held_lock *hlock;
hlock = curr->held_locks + depth;
......
// 初始化 hlock 完成,沒啥問題就可以幫 depth++
curr->lockdep_depth++;
經過一連串檢查都沒有問題的話,就可以成功獲得 Lock
LOCK_CONTENDED
#define LOCK_CONTENDED(_lock, try, lock) \
do { \
if (!try(_lock)) { \
lock_contended(&(_lock)->dep_map, _RET_IP_); \
lock(_lock); \
} \
lock_acquired(&(_lock)->dep_map, _RET_IP_); \
} while (0)
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}
#define arch_spin_lock(l) queued_spin_lock(l)
終於牽扯到 Queued Spinlock 了,見下個小節
PS:本人今明兩天出門不在家,這都是預先寫好的稿,有點水,請見諒