自旋鎖是一種典型的對臨界資源進行互斥訪問的手段。
它的底層實現邏輯是:原子變量+判斷檢測。
原子變量我們可以理解為一把鎖,通過操作原子變量(鎖)的狀態,并對其進行判斷,如果鎖未被鎖定,我們就繼續往下執行;如果鎖已經被鎖定,我們就原地自旋,直到等到鎖被打開。
在ARM平臺下,自旋鎖的實現使用了ldrex、strex、以及內存屏障指令dmb、dsb、wfe、sev等。
SMP或者單CPU但內核可搶占的情況,對于單CPU內核不可搶占的情況時,自旋鎖退化為空操作。CPU一直處于等待狀態,直到該鎖被釋放。CPU鎖死。SMP的情況下,任何一個核拿到了自旋鎖,該核上的搶占調度也暫時禁止了,但是沒有禁止另外一個核的搶占調度。copy_from_user()、copy_to_user()、kmalloc()和msleep(),否則會導致內核崩潰//?定義自旋鎖
spinlock_t?lock;
//?初始化自旋鎖
spin_lock_init(&lock)
//?獲得自旋鎖
spin_lock(&lock)??//?獲取自旋鎖,如果立即獲得鎖,則直接返回,否則,自旋等待,直到鎖被釋放
spin_trylock(&lock)??//?嘗試獲取自旋鎖,如果立即獲得鎖,返回true,否則直接返回false,不原地等待
//?釋放自旋鎖
spin_unlock(&lock)
自旋鎖保證了不受其他CPU或者單CPU內的搶占進程的干擾,但是對于臨界區代碼,仍然有可能會受到中斷和底半部的影響。
為了解決這種問題,我們就要使用自旋鎖的衍生。
spin_lock_irq()?=?spin_lock()?+?local_irq_disable()???//?獲取自旋鎖并關中斷
spin_unlock_irq()?=?spin_unlock()?+?local_irq_enable()??//?釋放自旋鎖并開中斷
spin_lock_irqsave()?=?spin_lock()?+?local_irq_save()??//?獲取自旋鎖并關中斷,保存中斷狀態
spin_unlock_irqrestore()?=?spin_unlock()?+?local_irq_restore()//釋放自旋鎖,開中斷并恢復中斷狀態
spin_lock_bh()?=?spin_lock()?+?local_bh_disable()???//?獲取自旋鎖并關底半部中斷
spin_unlock_bh()?=?spin_unlock()?+?local_bh_enable()??//?釋放自旋鎖并發開底半部中斷
當我們的臨界區代碼,有可能被進程或者中斷訪問時,就需要在進程上下文中,調用spin_lock_irqsave()、spin_unlock_irqrestore(),在中斷上下文中調用spin_lock()、spin_unlock(),如下圖:

typedef?struct?spinlock?{
????union?{
????????struct?raw_spinlock?rlock;
#ifdef?CONFIG_DEBUG_LOCK_ALLOC
#?define?LOCK_PADSIZE?(offsetof(struct?raw_spinlock,?dep_map))
????????struct?{
????????????u6?__padding[LOCK_PADSIZE];
????????????struct?lockdep_map?dep_map;
????????};
#endif
????};
}?spinlock_t;
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;
typedef?struct?{
????union?{
????????u32?slock;
????????struct?__raw_tickets?{
#ifdef?__ARMEB__
????????????u16?next;
????????????u16?owner;
#else
????????????u16?owner;
????????????u16?next;
#endif
????????}?tickets;
????};
}?arch_spinlock_t;
結構體名稱:spinlock_t、raw_spinlock、arch_spinlock_t
文件位置:include/linux/spinlock.h、arch/arm/include/asm/spinlock_types.h
主要作用:結構體層層嵌套,用于定義一個自旋鎖。
slock:32位無符號整形數據,用于鎖的控制
__raw_tickets:union類型,用于基于票證鎖算法的自旋鎖。
每個線程進入代碼段時,會嘗試獲取自旋鎖,如果獲取失敗,它們會在鎖的等待隊列中排隊。然后,等待隊列中的線程會按照優先級順序依次搶占鎖的擁有權,直到某個線程成功獲取自旋鎖并執行完關鍵代碼,釋放鎖資源為止。
owner :表示當前持有自旋鎖的線程的索引next :表示下一個等待獲取自旋鎖的線程的索引這里使用的
union聯合體,其共享內存空間,其具體區別可看下面:
struct與union區別:https://blog.csdn.net/lishuo0204/article/details/118957959
#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,?\
????SPIN_DEBUG_INIT(lockname)??\
????SPIN_DEP_MAP_INIT(lockname)?}
#define?__ARCH_SPIN_LOCK_UNLOCKED?{?{?0?}?}
函數名稱:spin_lock_init
文件位置:include/linux/spinlock.h
主要作用:初始化自旋鎖
函數調用流程:
//??spin_lock_init
spin_lock_init(include/linux/spinlock.h)
????|-->?spinlock_check?????????//??對鎖進行檢查,判斷是否存在
????|-->?raw_spin_lock_init?????//??初始化鎖
????????|-->?__RAW_SPIN_LOCK_UNLOCKED(include/linux/spinlock_types.h)
????????????|-->?__RAW_SPIN_LOCK_INITIALIZER????//??將鎖初始為__ARCH_SPIN_LOCK_UNLOCKED未上鎖狀態
上述函數主要通過宏定義給變量.raw_lock = __ARCH_SPIN_LOCK_UNLOCKED賦值,初始化為0,即為未上鎖的狀態;并且提供了兩個調試接口:CONFIG_DEBUG_SPINLOCK、CONFIG_DEBUG_LOCK_ALLOC,默認為關閉。
這里面有個關于spinlock_check存在的意義的討論,感興趣的可以看一下:https://stackoverflow.com/questions/52551594/spinlock-initialization-function
static?__always_inline?void?spin_lock(spinlock_t?*lock)
{
????raw_spin_lock(&lock->rlock);
}
#define?raw_spin_lock(lock)?_raw_spin_lock(lock)
#ifndef?CONFIG_INLINE_SPIN_LOCK
void?__lockfunc?_raw_spin_lock(raw_spinlock_t?*lock)
{
????__raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);
#ifndef?CONFIG_INLINE_SPIN_LOCK
void?__lockfunc?_raw_spin_lock(raw_spinlock_t?*lock)
{
????__raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_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);
}
#define?preempt_disable()?\
do?{?\
????preempt_count_inc();?\
????barrier();?\
}?while?(0)
#define?LOCK_CONTENDED(_lock,?try,?lock)?\
????lock(_lock)
static?inline?void?do_raw_spin_lock(raw_spinlock_t?*lock)?__acquires(lock)
{
????__acquire(lock);
????arch_spin_lock(&lock->raw_lock);
}
static?inline?void?arch_spin_lock(arch_spinlock_t?*lock)
{
????unsigned?long?tmp;
????u32?newval;
????arch_spinlock_t?lockval;
????prefetchw(&lock->slock);
????__asm__?__volatile__(
"1:?ldrex?%0,?[%3]\n"
"?add?%1,?%0,?%4\n"
"?strex?%2,?%1,?[%3]\n"
"?teq?%2,?#0\n"
"?bne?1b"
????:?"=&r"?(lockval),?"=&r"?(newval),?"=&r"?(tmp)
????:?"r"?(&lock->slock),?"I"?(1?<????:?"cc");
????while?(lockval.tickets.next?!=?lockval.tickets.owner)?{
????????wfe();
????????lockval.tickets.owner?=?READ_ONCE(lock->tickets.owner);
????}
????smp_mb();
}
函數名稱:spin_lock
文件位置:include/linux/spinlock.h
主要作用:用于在進程或線程首次嘗試獲取鎖的時候進行自旋,不停地檢查鎖的狀態,如果鎖已經被其他進程或線程占用,則自旋等待,直到鎖被釋放。
函數調用流程:
//??spin_lock
spin_lock(include/linux/spinlock.h)
????|-->?raw_spin_lock
????????|-->?_raw_spin_lock(include/linux/spinlock_api_smp.h)
????????????|-->?__raw_spin_lock
????????????????|-->?__raw_spin_lock
????????????????????|-->?preempt_disable
??????????|-->?preempt_count_inc
??????????|-->?barrier
????????????????????|-->?spin_acquire
????????????????????|-->?LOCK_CONTENDED
????????????????????????|-->?do_raw_spin_lock
????????????????????????????|-->?arch_spin_lock(arch/arm/include/asm/spinlock.h)
實現流程:
preempt_disable(); 禁用內核搶占,確保當前 CPU 執行該代碼時不會被其他進程或線程搶占。
preempt_count_inc增加搶占計數器的值,通過搶占計數器來實現對任務的執行順序進行管理。barrier來確保前面的操作完成后再繼續執行后面的代碼。spin_acquire(&lock->dep_map, 0, 0, _RET_IP_); 通過調用 spin_acquire() 函數獲取自旋鎖,用于保護共享資源不被兩個、或者多個線程所修改。
spin_acquire是lockdep工具的一部分,主要用于動態檢測死鎖。lock->dep_map是鎖的依賴地圖,_RET_IP_是調用者的返回地址。這兩個參數都是用于lockdep的調試信息。lockdep是一個強大的鎖調試工具,它可以跟蹤鎖的所有獲取和釋放,并動態地檢測可能的死鎖情況。LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock); 實際為調用do_raw_spin_lock函數來實現獲取鎖并自旋的操作。
下面為arch_spin_lock匯編代碼分析:
prefetchw(&lock->slock) 函數用于提前加載鎖的地址到處理器緩存中,從而提高鎖的獲取效率。
ldrex %0, [%3] 用于以原子方式讀取鎖的值到寄存器 %0 中,%3 為鎖的地址。
add %1, %0, %4用于將當前獲取鎖的 CPU 分配的新值加上原鎖值 %0 以及%4 固定常量,結果存放在%1新值中。
strex %2, %1, [%3] 用于以原子方式將更新的值 %1 寫入鎖的地址所指定的內存位置,%2 為寫入結果。
這里解釋一下為什么要做
add處理:由
spinlock_t的結構體可知,是由聯合體組成,并且可以通過owner和next兩個字段訪問,next在高16位,owner在低16位。這里上鎖的操作,是將其值設置為
1 << TICKET_SHIFT,也就是高16位,即next字段設置為1,表示下一個等待獲取自旋鎖的線程的索引。
teq %2, #0:用于測試寫入結果 %2 是否為0,如果為0,表示鎖獲取成功,反之則跳轉到標簽1b處執行。
當獲取鎖成功之后,程序會執行 while 循環,不斷等待鎖的所有權被賦予當前 CPU, 直到鎖的所有權的擁有者持有鎖為止。
smp_mb() 函數執行一條內存屏障,確保所有關鍵數據的順序性已經刷新到內存中。
綜上,spin_lock代碼的作用是獲取自旋鎖,讓當前線程獲得臨界資源的控制權,避免多個線程同時修改共享資源而造成數據沖突。同時,通過禁用內核搶占和使用內聯函數優化的方式,保證了原子操作的執行效率和可靠性。
static?__always_inline?void?spin_unlock(spinlock_t?*lock)
{
????raw_spin_unlock(&lock->rlock);
}
#define?raw_spin_unlock(lock)??_raw_spin_unlock(lock)
void?__lockfunc?_raw_spin_unlock(raw_spinlock_t?*lock)
{
????__raw_spin_unlock(lock);
}
static?inline?void?__raw_spin_unlock(raw_spinlock_t?*lock)
{
????spin_release(&lock->dep_map,?1,?_RET_IP_);
????do_raw_spin_unlock(lock);
????preempt_enable();
}
static?inline?void?do_raw_spin_unlock(raw_spinlock_t?*lock)?__releases(lock)
{
????arch_spin_unlock(&lock->raw_lock);
????__release(lock);
}
static?inline?void?arch_spin_unlock(arch_spinlock_t?*lock)
{
????smp_mb();
????lock->tickets.owner++;
????dsb_sev();
}
函數名稱:spin_unlock