Linux核心無鎖程式設計

2021-07-05 21:39:12 字數 3994 閱讀 3505

多核多執行緒已經成為當下乙個時髦的話題,而無鎖程式設計更是這個時髦話題中的熱點話題。linux核心可能是當今最大最複雜的並行程式之一,為我們分析多核多執行緒提供了絕佳的範例。核心設計者已經將最新的無鎖程式設計技術帶進了2。6系統核心中,本文以2。6。10版本為藍本,帶領您領略多核多執行緒程式設計的真諦,窺探無鎖程式設計的奧秘,體味大師們的高雅設計!

非阻塞型同步(non-blockingsynchronization)簡介

如何正確有效的保護共享資料是編寫並行程式必須面臨的乙個難題,通常的手段就是同步。同步可分為阻塞型同步(blockingsynchronization)和非阻塞型同步(non-blockingsynchronization)。

阻塞型同步是指當乙個執行緒到達臨界區時,因另外乙個執行緒已經持有訪問該共享資料的鎖,從而不能獲取鎖資源而阻塞,直到另外乙個執行緒釋放鎖。常見的同步原語有mutex、semaphore等。如果同步方案採用不當,就會造成死鎖(deadlock),活鎖(livelock)和優先順序反轉(priorityinversion),以及效率低下等現象。

為了降低風險程度和提高程式執行效率,業界提出了不採用鎖的同步方案,依照這種設計思路設計的演算法稱為非阻塞型演算法,其本質特徵就是停止乙個執行緒的執行不會阻礙系統中其他執行實體的執行。

當今比較流行的non-blockingsynchronization實現方案有三種:

wait-free

wait-free是指任意執行緒的任何操作都可以在有限步之內結束,而不用關心其它執行緒的執行速度。wait-free是基於per-thread的,可以認為是starvation-free的。非常遺憾的是實際情況並非如此,採用wait-free的程式並不能保證starvation-free,同時記憶體消耗也隨執行緒數量而線性增長。目前只有極少數的非阻塞演算法實現了這一點。

lock-free

lock-free是指能夠確保執行它的所有執行緒中至少有乙個能夠繼續往下執行。由於每個執行緒不是starvation-free的,即有些執行緒可能會被任意地延遲,然而在每一步都至少有乙個執行緒能夠往下執行,因此系統作為乙個整體是在持續執行的,可以認為是system-wide的。所有wait-free的演算法都是lock-free的。

obstruction-free

obstruction-free是指在任何時間點,乙個孤立執行執行緒的每乙個操作可以在有限步之內結束。只要沒有競爭,執行緒就可以持續執行。一旦共享資料被修改,obstruction-free要求中止已經完成的部分操作,並進行回滾。所有lock-free的演算法都是obstruction-free的。

綜上所述,不難得出obstruction-free是non-blockingsynchronization中效能最差的,而wait-free效能是最好的,但實現難度也是最大的,因此lock-free演算法開始被重視,並廣泛運用於當今正在執行的程式中,比如linux核心。

一般採用原子級的read-modify-write原語來實現lock-free演算法,其中ll和sc是lock-free理論研究領域的理想原語,但實現這些原語需要cpu指令的支援,非常遺憾的是目前沒有任何cpu直接實現了sc原語。根據此理論,業界在原子操作的基礎上提出了著名的cas(compare-and-swap)操作來實現lock-free演算法,intel實現了一條類似該操作的指令:cmpxchg8。

清單1。cas偽碼

boolcas(t*addr,texpected,tnewvalue)

else

returnfalse;

}在實際開發過程中,利用cas進行同步,**如下所示:

清單2。cas實際操作

dowhile(!cas(記憶體位址,備份的舊資料,新資料))

就是指當兩者進行比較時,如果相等,則證明共享資料沒有被修改,替換成新值,然後繼續往下執行;如果不相等,說明共享資料已經被修改,放棄已經所做的操作,然後重新執行剛才的操作。容易看出cas操作是基於共享資料不會被修改的假設,採用了類似於資料庫的commit-retry的模式。當同步衝突出現的機會很少時,這種假設能帶來較大的效能提公升。

其中標註為紅色字型的方案為blockingsynchronization,黑色字型為non-blockingsynchronization。lock-based和lockless-based兩者之間的區別僅僅是加鎖粒度的不同。圖中最底層的方案就是大家經常使用的mutex和semaphore等方案,**複雜度低,但執行效率也最低。

linux核心中的無鎖分析

linux核心可能是當今最大最複雜的並行程式之一,它的並行主要來至於中斷、核心搶占及smp等。核心設計者們為了不斷提高linux核心的效率,從全域性著眼,逐步廢棄了大核心鎖來降低鎖的粒度;從細處下手,不斷對區域性**進行優化,用無鎖程式設計替代基於鎖的方案,如seqlock及rcu等;不斷減少鎖衝突程度、降低等待時間,如double-checkedlocking和原子鎖等。

無論什麼時候當臨界區中的**僅僅需要加鎖一次,同時當其獲取鎖的時候必須是執行緒安全的,此時就可以利用double-checkedlocking模式來減少鎖競爭和加鎖載荷。目前double-checkedlocking已經廣泛應用於單例(singleton)模式中。核心設計者基於此思想,巧妙的將double-checkedlocking方法運用於核心**中。

當乙個程序已經僵死,即程序處於task_zombie狀態,如果父程序呼叫waitpid()系統呼叫時,父程序需要為子程序做一些清理性的工作,**如下所示:

清單3。少鎖操作

984staticintwait_task_zombie(task_t*p,intnoreap,

985structsiginfo__user*infop,

986int__user*stat_addr,structrusage__user*ru)

9871121write_unlock_irq(&tasklist_lock);

1122}

……1127}

如果將write_lock_irq放置於1103行之前,鎖的範圍過大,鎖的負載也會加重,影響效率;如果將加鎖的**放到判斷裡面,且沒有1106行的**,程式會正確嗎?在單核情況下是正確的,但在雙核情況下問題就出現了。乙個非主程序在乙個cpu上執行,正準備呼叫exit退出,此時主程序在另外乙個cpu上執行,在子程序呼叫release_task函式之前呼叫上述**。子程序在exit_notify函式中,先持有讀寫鎖tasklist_lock,呼叫forget_original_parent。主程序執行到1104處,由於此時子程序先持有該鎖,所以父程序只好等待。在forget_original_parent函式中,如果該子程序還有子程序,則會呼叫reparent_thread(),將執行p->parent=p->real_parent;語句,導致兩者相等,等非主程序釋放讀寫鎖tasklist_lock時,另外乙個cpu上的主程序被喚醒,一旦開始執行,繼續執行將會導致bug。

嚴格的說,double-checkedlocking不屬於無鎖程式設計的範疇,但由原來的每次加鎖訪問到大多數情況下無須加鎖,就是乙個巨大的進步。同時從這裡也可以看出一點端倪,核心開發者為了降低鎖衝突率,減少等待時間,提高執行效率,一直在持續不斷的進行改進。

原子操作可以保證指令以原子的方式執行——執行過程不被打斷。核心提供了兩組原子操作介面:一組針對於整數進行操作,另外一組針對於單獨的位進行操作。核心中的原子操作通常是內聯函式,一般是通過內嵌彙編指令來完成。對於一些簡單的需求,例如全域性統計、引用計數等等,可以歸結為是對整數的原子計算。

1。lock-free應用場景一——spinlock

spinlock是一種輕量級的同步方法,一種非阻塞鎖。當lock操作被阻塞時,並不是把自己掛到乙個等待佇列,而是死迴圈cpu空轉等待其他執行緒釋放鎖。spinlock鎖實現**如下:

清單4。spinlock實現**

staticinlinevoid__preempt_spin_lock(spinlock_t*lock)

while(!_raw_spin_trylock(lock));

}staticinlineint_raw_spin_trylock(spinlock_t*lock)

組合語言指令xchgb原子性的交換8位oldval(存0)和lock->lock的值,如果oldval為1(lock初始值為1),則獲取鎖成功,反之,則繼續迴圈,接著relax休息一會兒,然後繼續周而復始,直到成功。

linux無鎖話程式設計,原子

linux支援的哪些操作是具有原子特性的?知道這些東西是理解和設計無鎖化程式設計演算法的基礎。下面的東西整理自網路。先感謝大家的分享!sync fetch and add系列的命令,發現這個系列命令講的最好的一篇文章,英文好的同學可以直接去看原文。multithreaded data type ac...

無鎖程式設計 大綱

鎖定被迫交出時間片。鎖定意味著阻塞,多個執行緒 程序 排隊獲取資源,無法充分發揮系統效能。鎖定的阻塞無法通過fd進行通知,對效能有進一步的影響 理想的伺服器模型是全域性一處阻塞統一等待訊息 一些鎖限制了必須使用執行緒的方式進行開發,而執行緒無法充分利用系統的記憶體。pthread庫在特殊情況下可能產...

無鎖程式設計 六 seqlock 順序鎖

用於能夠區分讀與寫的場合,並且是讀操作很多 寫操作很少,寫操作的優先權大於讀操作。seqlock 的實現思路是,用乙個遞增的整型數表示sequence。寫操作進入臨界區時,sequence 退出臨界區時,sequence再 寫操作還需要獲得乙個鎖 比如mutex 這個鎖僅用於寫寫互斥,以保證同一時間...