天天看點

Open vSwitch(OvS)源代碼之Linux RCU鎖機制分析Open vSwitch(OvS)源代碼之Linux RCU鎖機制分析

Open vSwitch(OvS)源代碼之Linux RCU鎖機制分析Open vSwitch(OvS)源代碼之Linux RCU鎖機制分析

<a target="_blank"></a>

本來想繼續順着資料包的處理流程分析upcall調用的,但是發現在分析upcall調用時必須先了解linux中核心和使用者空間通信接口netlink機制,是以就一直耽擱了對upcall的分析。如果對open vswitch有些了解的話,你會發現其實open vswitch是在linux系統上運作的,因為open vswitch中有很多的機制,子產品等都是直接調用linux核心的。比如:現在要分析的rcu鎖機制、upcall調用、以及一些結構體的定義都是直接從linux核心中擷取的。是以如果你在檢視源代碼的一些結構(或者子產品,機制性代碼)時,發現在open vswitch中沒有定義(我用的是source insight來檢視和分析源碼,可以很好的檢視是否定義過),那麼很可能就是open vswitch包含了linux頭檔案引用了linux核心的一些定義。

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

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

第二、要操作的資料區上了寫鎖;則不管是什麼請求都必須等待資料區的寫鎖釋放掉後才能上鎖通路。

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

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

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

總結下rcu鎖機制特性,允許多個讀者和多個寫者同時通路共享資料區的内容。而且這種鎖對多讀少寫的資料來說是非常高效的,可以讓cpu減少些額外的開 銷。如果寫得操作多了的話,這種機制就沒讀寫鎖那麼好了。因為rcu寫資料開銷還是很大的,要拷貝資料,然後還要修改,最後還要等待替換。其實這個機制就 好比我們在一台共享伺服器上放了個檔案,有很多個人一起使用。如果你隻是看看這個檔案内容,那麼直接在伺服器上cat檢視就可以。但如果你要修改該檔案, 那麼你不能直接在伺服器上修改,因為你這樣操作會影響到将要看這個檔案或者寫這個檔案的人。是以你隻能先拷貝到自己本機上修改,當最後确認保證正确時,然 後就替換掉伺服器上的原資料。

下面看下rcu機制下修改資料(以連結清單為例)。

Open vSwitch(OvS)源代碼之Linux RCU鎖機制分析Open vSwitch(OvS)源代碼之Linux RCU鎖機制分析

根據上面的圖會發現其實替換的時候隻要修改下指針就可以,原資料區内容在被替換後,預設會被垃圾回收機制回收掉。

了解了rcu的這些機制原理,下面來看下linux核心中常使用的一些和rcu鎖有關的操作。注意,本blog并不會過多的去深究rcu最底層的實作機制,因為分享rcu工作機制的目的隻是為了更好的了解open vswitch中使用到的那部分代碼的了解,而不是為了分析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-&gt;next, new); 代碼大概含義:配置設定指向一個新初始化的結構指針,将由rcu讀端臨界區被解除引用,傳回指定的值。(說實話我也不太懂這個注釋是什麼意思)大概的解釋下: 就是讓插入點的前一個節點的next指向新增加的new節點,為什麼要單獨用一條這個語句來實作,而不是用 prev-&gt;next = new;直接實作呢?這是因為prev-&gt;next本來是指向其他值得,有可能有cpu通過prev-&gt;next去通路其他rcu保護的資料 了,是以如果你要插入一個rcu保護的資料結構中必要要調用這個語句,它裡面會幫你處理好一些細節(比如有其他cpu使用後面的資料,直接使用 prev-&gt;next可能會使讀資料的cpu斷開,産生問題),并且讓剛加入的新節點也受到rcu的保護。這類的插入有很多,比如從頭部插入,從尾 部插入等,實作都差不多,這裡不一一細講。

list_for_each_entry_rcu();

這是個周遊rcu連結清單的操作,和一般的連結清單周遊差不多。不同點就是必須要進入rcu保護的cpu(即:調用了rcu_read_lock()函數的 cpu)才能調用這個操作,可以和其他cpu共同周遊這個rcu連結清單。以此相同的還有其他變相的周遊及哈希連結清單的周遊,不細講。

如果在open vswitch源代碼分析中發現了有關rcu的分析和這裡的沖突,可以以這裡為準,當然我也會校對下。

原文釋出時間:2014-11-25

本文來自雲栖合作夥伴“linux中國”

繼續閱讀