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