參考
關於ldrex strex這兩個指令是特殊指令講解
https://blog.csdn.net/weixin_29379325/article/details/80313840
https://blog.csdn.net/weixin_29379325/article/details/80313840
http://www.wowotech.net/linux_kenrel/atomic.html
內核搶佔https://www.cnblogs.com/sky-heaven/p/5391887.html
mutex和spin lock的區別和應用(sleep-waiting和busy-waiting的區別)
信號量mutex是sleep-waiting。 就是說當沒有獲得mutex時,會有上下文切換,將自己、加到忙等待隊列中,直到另外一個線程釋放mutex並喚醒它,而這時CPU是空閒的,可以調度別的任務處理。
而自旋鎖spin lock是busy-waiting。就是說當沒有可用的鎖時,就一直忙等待並不停的進行鎖請求,直到得到這個鎖爲止。這個過程中cpu始終處於忙狀態,不能做別的任務。
例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0 和Core1上。 用spin-lock,coer0上的線程就會始終佔用CPU。
另外一個值得注意的細節是spin lock耗費了更多的user time。這就是因爲兩個線程分別運行在兩個核上,大部分時間只有一個線程能拿到鎖,所以另一個線程就一直在它運行的core上進行忙等待,CPU佔用率一直是100%;而mutex則不同,當對鎖的請求失敗後上下文切換就會發生,這樣就能空出一個核來進行別的運算任務了。(其實這種上下文切換對已經拿着鎖的那個線程性能也是有影響的,因爲當該線程釋放該鎖時它需要通知操作系統去喚醒那些被阻塞的線程,這也是額外的開銷)
總結
(1)Mutex適合對鎖操作非常頻繁的場景,並且具有更好的適應性。儘管相比spin lock它會花費更多的開銷(主要是上下文切換),但是它能適合實際開發中複雜的應用場景,在保證一定性能的前提下提供更大的靈活度。
(2)spin lock的lock/unlock性能更好(花費更少的cpu指令),但是它只適應用於臨界區運行時間很短的場景。而在實際軟件開發中,除非程序員對自己的程序的鎖操作行爲非常的瞭解,否則使用spin lock不是一個好主意(通常一個多線程程序中對鎖的操作有數以萬次,如果失敗的鎖操作(contended lock requests)過多的話就會浪費很多的時間進行空等待)。
(3)更保險的方法或許是先(保守的)使用 Mutex,然後如果對性能還有進一步的需求,可以嘗試使用spin lock進行調優。畢竟我們的程序不像Linux kernel那樣對性能需求那麼高(Linux Kernel最常用的鎖操作是spin lock和rw lock)。
spin_lock的實現
Spin_lock基於arm架構的解釋
<include/linux.h>
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock)
<kernel/locking/spinlock.c>
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{
__raw_spin_lock(lock);
}
<spinlock_api_smp.h>
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();//爲啥要關閉搶佔呢??在單核上或者多核cpu均有作用
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
<include/linux.h>
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}
<arch/arm/include/asm/spinlock.h>
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 << TICKET_SHIFT)
: "cc");
while (lockval.tickets.next != lockval.tickets.owner) {
wfe();
lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);
}
smp_mb();
}
static inline int do_raw_spin_trylock(raw_spinlock_t *lock)
{
return arch_spin_trylock(&(lock)->raw_lock);
}
static inline int arch_spin_trylock(arch_spinlock_t *lock)
{
unsigned long contended, res;
u32 slock;
prefetchw(&lock->slock);
do {
__asm__ __volatile__(
" ldrex %0, [%3]\n"
" mov %2, #0\n"
" subs %1, %0, %0, ror #16\n"
" addeq %0, %0, %4\n"
" strexeq %2, %0, [%3]"
: "=&r" (slock), "=&r" (contended), "=&r" (res)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc");
} while (res);
if (!contended) {
smp_mb();
return 1;
} else {
return 0;
}
}
mutex_lock實現
<locking/mutex.c>
void __sched mutex_lock(struct mutex *lock)
{
might_sleep();
/*
* The locking fastpath is the 1->0 transition from
* 'unlocked' into 'locked' state.
*/
__mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
mutex_set_owner(lock);
}