傳統的自旋鎖實際上就是一個整數,值爲1時表示沒有被佔用,值爲0或負數時表示鎖已經被佔用,此時spin_lock循環等待,直到spin_unlock將自旋鎖的值置爲1,在這個過程中沒有保存線程申請自旋鎖的順序信息,後進入等待的線程有可能先獲得自旋鎖。
排隊自旋鎖(FIFO Ticket Spinlock)是 Linux 內核 2.6.25 版本引入的一種新型自旋鎖,它通過保存執行線程申請鎖的順序信息解決了這種問題,讓先申請自旋鎖的線程先得到鎖。
1. Ticket spinlock的實現原理
排隊自旋鎖還是使用一個整形slock,並將其分爲兩個部分:
Next和Owner的長度與CPU的個數相關, 當CPU的個數 < 256時,Next和Owner爲8位。當CPU的個數 > 256時,Next和Owner爲16位。
#if (NR_CPUS < 256)
#define TICKET_SHIFT 8
...
#else
#define TICKET_SHIFT 16
2. Ticket spinlock的實現代碼
下面查看__ticket_spin_lock和__ticket_spin_unlock兩個函數(NR_CPUS < 256):
加鎖:
點擊(此處)摺疊或打開
- static __always_inline void __ticket_spin_lock(arch_spinlock_t
*lock)
- {
- short inc
= 0x0100;
- asm volatile (
- LOCK_PREFIX "xaddw %w0, %1\n"
- "1:\t"
- "cmpb %h0, %b0\n\t"
- "je 2f\n\t"
- "rep ; nop\n\t"
- "movb %1, %b0\n\t"
- /* don't need lfence here, because loads are in-order */
- "jmp 1b\n"
- "2:"
- : "+Q"
(inc),
"+m" (lock->slock)
- :
- : "memory",
"cc");
- }
a. xaddw %w0, %1: 將inc和lock->slock的低16位置交換,並將相加後的值存貸lock->slock中.
例:slock = 0x00 00 11 10,操作之後slock = 0x00 00 12 10, inc = 0x00 00 11 10.
b. cmpb %h0, %b0: 比較inc的低8位(Owner)和高8位(Next),相等則獲得鎖返回,不相等則繼續執行;
c. 不斷輪詢lock->slock, 等待直到Next和Owner相等。
解鎖
- static __always_inline void __ticket_spin_unlock(arch_spinlock_t
*lock)
- {
- asm volatile(UNLOCK_LOCK_PREFIX
"incb %0" // 將owner加1
- : "+m"
(lock->slock)
- :
- : "memory",
"cc");
- }
通過這種方式,線程調用__ticket_spin_lock的順序存放在Next字段中,Next字段小的線程會先得到鎖。