Linux內核學習系列---排隊自旋鎖

      傳統的自旋鎖實際上就是一個整數,值爲1時表示沒有被佔用,值爲0或負數時表示鎖已經被佔用,此時spin_lock循環等待,直到spin_unlock將自旋鎖的值置爲1,在這個過程中沒有保存線程申請自旋鎖的順序信息,後進入等待的線程有可能先獲得自旋鎖。
      排隊自旋鎖(FIFO Ticket Spinlock)是 Linux 內核 2.6.25 版本引入的一種新型自旋鎖,它通過保存執行線程申請鎖的順序信息解決了這種問題,讓先申請自旋鎖的線程先得到鎖。
1. Ticket spinlock的實現原理
        排隊自旋鎖還是使用一個整形slock,並將其分爲兩個部分:
                   Ticket Number 
        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

        slock初始化時被置爲0,Next和Owner都被置爲0,當內核線程申請自旋鎖時,比較原始的Next和Owner並將Next加1,如果原始的Next域和Owner域相等則表示鎖處理未使用狀態,否則改線程輪詢等待直到Next域和Owner域相等。當釋放鎖時將Owner域加1.

2. Ticket spinlock的實現代碼
下面查看__ticket_spin_lock和__ticket_spin_unlock兩個函數(NR_CPUS < 256):
加鎖:

點擊(此處)摺疊或打開

  1. static __always_inline void __ticket_spin_lock(arch_spinlock_t *lock)
  2. {
  3.   short inc = 0x0100;

  4.   asm volatile (
  5.     LOCK_PREFIX "xaddw %w0, %1\n"
  6.     "1:\t"
  7.     "cmpb %h0, %b0\n\t"
  8.     "je 2f\n\t"
  9.     "rep ; nop\n\t"
  10.     "movb %1, %b0\n\t"
  11.     /* don't need lfence here, because loads are in-order */
  12.     "jmp 1b\n"
  13.     "2:"
  14.     : "+Q" (inc), "+m" (lock->slock)
  15.     :
  16.     : "memory", "cc");
  17. }

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相等。

解鎖

  1. static __always_inline void __ticket_spin_unlock(arch_spinlock_t *lock)
  2. {
  3.   asm volatile(UNLOCK_LOCK_PREFIX "incb %0" // 將owner加1
  4.          : "+m" (lock->slock)
  5.          :
  6.          : "memory", "cc");
  7. }
a. 解鎖只執行一個操作就是講slock的Owner字段加1;

通過這種方式,線程調用__ticket_spin_lock的順序存放在Next字段中,Next字段小的線程會先得到鎖。
 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章