好久沒有更文,上次更文時西安天氣還很熱,現(xiàn)在“寒氣”它還真來了。在前一階段經(jīng)歷了一些公司的面試,經(jīng)常會問到RCU鎖的原理,其實(shí)在跟對方口述表達(dá)時才真正能體現(xiàn)出來自己到底懂不懂,關(guān)于RCU鎖的原理與使用,我打算分若干個次文章整理出來,本次就先從一個大概的原理上進(jìn)行講解。
Read-Copy Update,簡稱RCU,中文對應(yīng)"讀取-拷貝-更新",
先給出一個解釋:
對于被RCU保護(hù)的共享數(shù)據(jù)結(jié)構(gòu),讀者不需要獲得任何鎖就可以直接訪問,但寫者在訪問它時首先要拷貝一個副本,然后對副本進(jìn)行修改,最后使用一個回調(diào)機(jī)制在適當(dāng)?shù)臅r機(jī)把指向數(shù)據(jù)的指針重新指向新的被修改的數(shù)據(jù)。
簡化來說:
記錄指針:記錄所有的指向“共享數(shù)據(jù)”的指針。
讀取-拷貝: " 指針持有者 “ 修改該 ” 共享數(shù)據(jù) " ,:先創(chuàng)建一個共享數(shù)據(jù) " 副本 " , 然后在副本中修改 。
更新數(shù)據(jù):讀者讀取”共享數(shù)據(jù)“,離開”讀者臨界區(qū)“后,指向原來 " 共享數(shù)據(jù) " 的 指針 重新指向 " 副本 " , 然后再回收處理舊的 " 共享數(shù)據(jù) " 。
RCU鎖優(yōu)劣:
讀者不需要承擔(dān)同步開銷(同步開銷:1、獲取鎖;2、執(zhí)行”原子指令“;3、執(zhí)行”內(nèi)存屏障“),因?yàn)樽x端不需要鎖,不使用原子指令,故不會導(dǎo)致鎖競爭。
寫者承擔(dān)很大的同步開銷,需要讀取并復(fù)制共享數(shù)據(jù),還有使用互斥鎖機(jī)制等。
關(guān)于具體場景:
RCU鎖是 Linux 內(nèi)核實(shí)現(xiàn)的一種針對“讀多寫少”的共享數(shù)據(jù)的同步機(jī)制。
RCU主要針對的數(shù)據(jù)對象是鏈表,目的是提高遍歷讀取數(shù)據(jù)的效率,為了達(dá)到目的使用RCU機(jī)制讀取數(shù)據(jù)的時候不對鏈表進(jìn)行耗時的加鎖操作。RCU機(jī)制極大提高"鏈表"數(shù)據(jù)結(jié)構(gòu)的讀取效率,多個線程同時讀取鏈表時,使用rcu_read_lock()即可,在多線程讀取的同時還允許有1個線程修改鏈表。在Linux內(nèi)核中專門提供了頭文件:
include/linux/rculist.h定義了一些宏函數(shù)用于RCU處理鏈表,如下表中是該頭文件中的宏定義.在內(nèi)核編程時可根據(jù)需要查詢該頭文件中源碼選擇,如list_entry_rcu與list_for_each_entry_rcu:
list_for_each_entry_rcu用于遍歷由RCU保護(hù)的鏈表head,只要在讀端臨界區(qū)使用該函數(shù),它就可以安全地和其它_rcu鏈表操作函數(shù)(如list_add_rcu)并發(fā)運(yùn)行。
RCU鏈表遍歷操作相關(guān)宏:
直接上代碼來個簡單的Demo:僅創(chuàng)建一個讀者和寫者去感受一下RCU鎖的使用,下面的例子通過RCU機(jī)制保護(hù)rcu_test_init()函數(shù)分配的共享數(shù)據(jù)結(jié)構(gòu)struct foo *test ;并創(chuàng)建一個讀者和一個寫者來模擬同步場景。
運(yùn)行截圖:
對于讀線程:
通過rcu_read_lock()函數(shù)和rcu_read_unlock()函數(shù)來構(gòu)建一個讀者臨界區(qū),rcu_read_lock() 和 rcu_read_unlock(),是 RCU “隨意讀” 的關(guān)鍵,它們的效果是聲明了一個讀端的臨界區(qū)。讀者在臨界區(qū)中,不能發(fā)生進(jìn)程上下文切換,否則,因?yàn)閷懻咝枰却x者完成,寫者進(jìn)程也會一直被阻塞。
調(diào)用list_for_each_entry_rcu宏函數(shù),遍歷獲取被保護(hù)數(shù)據(jù),此時P指向被保護(hù)的數(shù)據(jù)。
對于寫線程:
調(diào)用list_first_or_null_rcu宏函數(shù),讀取元素,然后開始復(fù)制一份副本。
對副本進(jìn)行修改操作。
調(diào)用list_replace_rcu宏函數(shù),用新節(jié)點(diǎn)替換掉舊節(jié)點(diǎn),實(shí)際也是調(diào)用了rcu_assign_pointer()更新了元素,rcu_assign_pointer用來為被RCU保護(hù)的指針分配一個新的值,這樣是為了安全更改其值,這個原語保護(hù)并發(fā)讀不受更新操作的影響。寫者調(diào)用rcu_assign_pointer后,對于讀者就"可見"了,調(diào)用rcu_assign_pointer前就已經(jīng)開始讀取舊值的依然可以訪問舊值。
調(diào)用synchronize_rcu宏函數(shù),為了確保沒有讀者正在訪問要回收的臨界資源,需要等待所有的讀者退出臨界區(qū),該宏函數(shù)通過阻塞來做到這一點(diǎn),直到所有cpu上所有預(yù)先存在的RCU讀端臨界區(qū)都完成,相當(dāng)于給讀者一個安全退出的寬限區(qū)。
kfree釋放舊數(shù)據(jù)。
以上分析與記錄的相關(guān)概念都比較簡單,RCU的實(shí)現(xiàn)很復(fù)雜,本文對一些細(xì)節(jié)沒有展開,如回收舊資源時的寬限區(qū)等,所以本次只是一個原理層面一個大概的分析,后面有時間會繼續(xù)分析。
評論
查看更多