深刻理解Linux用戶空間的鎖機制

1.         緣起 nginx

隨着SMP(Symmetrical Multi-Processing)架構的流行和epoll類系統調用對非阻塞fd監視的支持,高性能服務器端的開發已經可以實現CPU計算和IO的分離。爲了充分發揮CPU的計算能力,服務器端的設計必需要儘可能減小線程切換。引發線程切換最重要的緣由之一就是對mutex和semaphor等鎖的使用。本文從計算機體系架構、操做系統的支持和mutex的實現完全分析Linux用戶空間mutex的實現,分析的源碼版本是glib-2.3.4和kernel-2.6.8。  
2.         體系結構和指令的支持 
在UP(uni processor)架構下,從用戶空間的角度看,中斷打斷了程序的正常執行。操做系統在處理完中斷以後,返回用戶空間的以前,從新調度系統中的線程執行。因爲CPU是在執行彙編指令結束後響應中斷,那麼單條彙編指令的執行就是原子的。  
在SMP下,因爲存在CPU Local Cache和每一個CPU的指令週期不一樣,單條彙編指令的執行不會是原子的。X86 SMP提供了一個lock指令前綴,使得某些彙編指令的執行是原子的。看以下x86_64體系結構的彙編代碼,來自glibc。  
Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中對cmpxchg指令的解釋以下:  
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.  
全部以lock爲前綴的指令都起內存柵欄的做用。內存柵欄使編譯器確保對RAM中數據的改變對全部CPU都是可見的。  
上述彙編對應的僞代碼:  
   
 Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2中對cmpxchg指令的解釋以下:  
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.  
 
 

 

by gaolingfei 
全部以lock爲前綴的指令都起內存柵欄的做用。內存柵欄使編譯器確保對RAM中數據的改變對全部CPU都是可見的。  
上述彙編對應的僞代碼:  
  
 
 
3.         操做系統支持   
按照操做系統的經典定義,進程是資源分配的最小單位,線程是調度的最小單位。 Linux操做系統提供了 futex系統調用以支持 mutex等鎖的實現。 futex的主要功能是使得線程以 TASK_INTERRUPTIBLE狀態等待處於進程空間的某變量的改變,或者使得某線程能夠喚醒等待該變量的其餘線程。  
實現 wait的步驟以下:  
C.        計算 key。若是是單進程單線程, Key爲用戶空間地址。若是爲單進程多線程,須要執行 spin_lock獲得用戶地址對應的page,而後 spin_unlockpage_table_lock會影響相應進程的page fault的處理。  (& current->mm->page_table_lock)(& current->mm->page_table_lock);
E.         up_read  (& current->mm->mmap_sem);
H.        將自身從 futex_hash_bucket移除。  
實現 wake up的步驟以下:  
A.        執行 waitAC。  
B.        spin_lock給相應桶加鎖。  (&bh-> lock);
C.        喚醒在鎖上的一個等待線程。  
D.        spin_unlock  (&bh-> lock);
E.         up_read  (& current->mm->mmap_sem);
4.         pthread_mutex 實現分析   
pthread_mutex_lock()實如今 glibc-2.3.4 pthread_mutex_lock.c文件的 33行,該函數會根據 mutexinit的時候設置的屬性,選擇不一樣的執行路徑。 mutex的屬性有四種:  
A.        PTHREAD_MUTEX_TIMED_NP:默認屬性。 pthread_mutex_lock()直接調用 lll_mutex_lock()。  
B.        PTHREAD_MUTEX_RECURSIVE_NP:檢查 mutex owner 是否爲當前線程。該屬性容許線程屢次獲取該鎖。  
C.        PTHREAD_MUTEX_ERRORCHECK_NP:若是同一線程兩次 lock,會返回錯誤。  
D.        PTHREAD_MUTEX_ADAPTIVE_NP:該鎖會先 n次調用 lll_mutex_trylock()n爲用戶定義和 100的最小值。若是仍然失敗,則調用 lll_mutex_lock()lll_mutex_trylock()不會調用 futex。  
5.         spin lock 實現   
nginx實現了 spin lock以保護多進程對 listen port的互斥 acceptspinlock的實現以下:  
 
 

 

Spinlock本質上是一個「忙等」鎖,因爲其不存在下節中總結的 mutex的缺點,其對於小資源是最高效的鎖。相比上節中 mutexPTHREAD_MUTEX_ADAPTIVE_NP屬性, nginxspinlock是一個更完美的實現方案。  
 
6.         總結  
在設置 PTHREAD_MUTEX_TIMED_NP屬性和單進程多線程模型下, pthread_mutex_lock()對同進程的其餘線程的影響以下:  
A.        pthread_mutex_lock()佔用的大部分 CPU時間當中,直接影響其餘線程調用 mmap()brk()mallocfree()。  
B.        對進程處理 page fault也會有影響。  
C.        若是整個操做系統的用戶進程使用了過多的 mutex之類的鎖,那麼全部鎖共享的 futex_hash_bucket將是一個瓶頸。  
D.        最重要的是,鎖的使用會引發線程的頻繁切換,致使 cpu cache missTLB miss。  
對於系統中,須要互斥訪問的資源,以下建議:  
A.        內核中對於小資源如鏈表的增刪,可能是使用 spin lock保護。  
B.        在設置 PTHREAD_MUTEX_ADAPTIVE_NP屬性下, mutex既能夠是 spin lock,也能夠是阻塞鎖。  
C.        使用 atomic_add_return(i, v),原子對變量 i增長 v值,而且返回操做後的值。相反操做: atomic_sub_return(i, v)。  
D.        使用 Per-CPU variables,例如多線程程序中要每隔 1秒,統計某項操做的值。該變量最好是 cache alignment。  
E.         對於如數據庫頻繁更新的操做,可使用數據庫的多版本併發控制方法減小對 mutexlock。  
相關文章
相關標籤/搜索