1. 程式人生 > >linux RCU鎖機制分析

linux RCU鎖機制分析

nbsp -i html 都在 而且 content 服務器 單詞 插入

openVswitch(OVS)源代碼之linux RCU鎖機制分析

分類: linux內核 | 標簽: 雲計算,openVswitch,linux內核,RCU鎖機制 | 作者: yuzhihui_no1 相關 | 發布日期 : 2014-10-19 | 熱度 : 1044°

前言

本來想繼續順著數據包的處理流程分析upcall調用的,但是發現在分析upcall調用時必須先了解linux中內核和用戶空間通信接口Netlink機制,所以就一直耽擱了對upcall的分析。如果對openVswitch有些了解的話,你會發現其實openVswitch是在linux系統上運行的,因為openVswitch中有很多的機制,模塊等都是直接調用linux內核的。比如:現在要分析的RCU鎖機制、upcall調用、以及一些結構體的定義都是直接從linux內核中獲取的。所以如果你在查看源代碼的一些結構(或者模塊,機制性代碼)時,發現在openVswitch中沒有定義(我用的是Source Insight來查看和分析源碼,可以很好的查看是否定義過),那麽很可能就是openVswitch包含了linux頭文件引用了linux內核的一些定義。

RCU是linux的新型鎖機制(RCU是在linux 2.6內核版本中開始正式使用的),本來一直糾結要不要用篇blog來說下這個鎖機制。因為在openVswitch中有很多的地方用到了RCU鎖,我開始分析的時候都是用一種鎖機制一筆帶過(可以看下openVswitch(OVS)源代碼分析之數據結構裏面有很多地方都用到了RCU鎖機制)。後來發現有很多地方還用到了該鎖機制的鏈表插入和刪除操作,而且後面分析的代碼中也有RCU的出現,所以就稍微的說下這個鎖機制的一些特性和操作。

RCU運行原理

我們先來回憶下讀寫鎖(rwlock)運行機制,這樣可以分析RCU的時候可以對照著分析。讀寫鎖分為讀鎖(也稱共享鎖),寫鎖(也稱排他鎖,或者獨占鎖)。分情況來分析下讀寫鎖:

第一、要操作的數據區被上了讀鎖;1、若請求是讀數據時,上讀鎖,多個讀鎖不排斥(即,在訪問數據的讀者上線未達到時,可以對該數據區再上讀鎖);2、若請求是寫數據,則不能馬上上寫鎖,而是要等到數據區的所有鎖(包括讀鎖和寫鎖)都釋放掉後才能開始上寫訪問。

第二、要操作的數據區上了寫鎖;則不管是什麽請求都必須等待數據區的寫鎖釋放掉後才能上鎖訪問。

同理來分析下RCU鎖機制: RCU是read copy udate的縮寫,按照單詞意思就知道這是一種針對數據的讀、復制、修改的保護鎖機制。鎖機制原理:

第一、寫數據的時候,不需要像讀寫鎖那樣等待所有鎖的釋放。而是會拷貝一份數據區的副本,然後在副本中修改,等待修改完後。用這個副本替換原來的數據區,替換的時候就要像讀寫鎖中上寫鎖那樣,等到原數據區上所有訪問者都退出後,才進行數據的替換;根據這種特性可以推斷出,用RCU鎖可以有多個寫者,拷貝了多份數據區數據,修改後各個寫著陸續的替換掉原數據區內容。

第二、讀數據的時候,不需要上任何鎖,也幾乎不需要什麽等待(讀寫鎖中如果數據區有寫鎖則要等待)就可以直接訪問數據。為什麽說幾乎不需要等待呢?因為寫數據中替換原數據時,只要修改個指針就可以,消耗的時間可以說幾乎不算,所以說讀數據不需要其他額外開銷。

總結下RCU鎖機制特性,允許多個讀者和多個寫者同時訪問共享數據區的內容。而且這種鎖對多讀少寫的數據來說是非常高效的,可以讓CPU減少些額外的開銷。如果寫得操作多了的話,這種機制就沒讀寫鎖那麽好了。因為RCU寫數據開銷還是很大的,要拷貝數據,然後還要修改,最後還要等待替換。其實這個機制就好比我們在一臺共享服務器上放了個文件,有很多個人一起使用。如果你只是看看這個文件內容,那麽直接在服務器上cat查看就可以。但如果你要修改該文件,那麽你不能直接在服務器上修改,因為你這樣操作會影響到將要看這個文件或者寫這個文件的人。所以你只能先拷貝到自己本機上修改,當最後確認保證正確時,然後就替換掉服務器上的原數據。

RCU寫者工作圖示

下面看下RUC機制下修改數據(以鏈表為例)

技術分享

根據上面的圖會發現其實替換的時候只要修改下指針就可以,原數據區內容在被替換後,默認會被垃圾回收機制回收掉。

linux內核RCU機制API

了解了RCU的這些機制原理,下面來看下linux內核中常使用的一些和RCU鎖有關的操作。註意,本blog並不會過多的去深究RCU最底層的實現機制,因為分享RCU工作機制的目的只是為了更好的了解openVswitch中使用到的那部分代碼的理解,而不是為了分析linux內核源代碼,不要本末倒置。如果遇到個知識點就拼命的深挖,那麽你看一份源代碼估計得幾個月。

rcu_read_lock();看到這裏有人可能會覺得和上面有矛盾,不是說好的讀者不需要鎖嗎?其實這不是和上讀寫鎖的那種上鎖,這僅僅只是標識了臨界區的開始位置。表明在臨界區內不能阻塞和休眠,也不能讓寫者進行數據的替換(其實這功能遠不止這些)。rcu _read_unlock()則是和上面rcu_read_lock()對應的,用來界定一個臨界區(就是要用鎖保護起來的數據區)。

synchronize_rcu();當該函數被一個CPU調用時(一般是有寫者替換數據時調用),而其他的CPU都在RCU保護的臨界區讀數據,那麽synchronize_rcu()將會保證阻塞寫者,直到所有其它讀數據的CPU都退出臨界區時,才中止阻塞,讓寫著開始替換數據。該函數作用就是保證在替換數據前,所有讀數據的CPU能夠安全的退出臨界區。同樣,還有個call_rcu()函數功能也是類似的。如果call_rcu()被一個CPU調用,而其他的CPU都在RCU保護的臨界區內讀數據,相應的RCU回調的調用將被推遲到其他讀臨界區數據的CPU全部安全退出後才執行(可以看linux內核源文件的註釋,在Rcupdate.h文件中rcu_read_look()函數前面的註釋)。

rcu_dereference(); 獲取在一個RCU保護的指針,指向RCU讀端臨界區。他的指針以後可能會被安全地解除引用。說到底就是一個RCU保護指針。

list_add_rcu();往RCU保護的數據結構中添加一個數據節點進去。這個和一般的往鏈表中增加一個節點操作是類似的,唯一不同的是多了這條代碼:rcu_assign_pointer(prev->next, new); 代碼大概含義:分配指向一個新初始化的結構指針,將由RCU讀端臨界區被解除引用,返回指定的值。(說實話我也不太懂這個註釋是什麽意思)大概的解釋下:就是讓插入點的前一個節點的next指向新增加的new節點,為什麽要單獨用一條這個語句來實現,而不是用 prev->next = new;直接實現呢?這是因為prev->next本來是指向其他值得,有可能有CPU通過prev->next去訪問其他RCU保護的數據了,所以如果你要插入一個RCU保護的數據結構中必要要調用這個語句,它裏面會幫你處理好一些細節(比如有其他CPU使用後面的數據,直接使用prev->next可能會使讀數據的CPU斷開,產生問題),並且讓剛加入的新節點也受到RCU的保護。這類的插入有很多,比如從頭部插入,從尾 部插入等,實現都差不多,這裏不一一細講。

list_for_each_entry_rcu();這是個遍歷RCU鏈表的操作,和一般的鏈表遍歷差不多。不同點就是必須要進入RCU保護的CPU(即:調用了rcu_read_lock()函數的CPU)才能調用這個操作,可以和其他CPU共同遍歷這個RCU鏈表。以此相同的還有其他變相的遍歷及哈希鏈表的遍歷,不細講。

如果在openVswitch源代碼分析中發現了有關RCU的分析和這裏的矛盾,可以以這裏為準,當然我也會校對下。

linux RCU鎖機制分析