什么是 RCU
RCU(Read-Copy-Update)是一種用于并發(fā)訪問(wèn)共享數(shù)據(jù)結(jié)構(gòu)的機(jī)制,主要用于讀多寫(xiě)少的場(chǎng)景。RCU 機(jī)制的主要特點(diǎn)是無(wú)鎖讀(lock-free read)和延遲刪除(deferred deletion),它不需要使用顯式的互斥鎖來(lái)保護(hù)共享數(shù)據(jù)結(jié)構(gòu)的訪問(wèn),而是通過(guò)一些巧妙的手段來(lái)實(shí)現(xiàn)并發(fā)訪問(wèn)共享數(shù)據(jù)結(jié)構(gòu)的安全性和高效性。
在 RCU 機(jī)制中根穷,讀取共享數(shù)據(jù)結(jié)構(gòu)的操作是無(wú)鎖的,因此讀取操作可以并發(fā)進(jìn)行导坟,不會(huì)相互干擾缠诅。寫(xiě)入共享數(shù)據(jù)結(jié)構(gòu)的操作則使用了延遲刪除的策略,即寫(xiě)入操作并不直接修改共享數(shù)據(jù)結(jié)構(gòu)乍迄,而是將要?jiǎng)h除的數(shù)據(jù)結(jié)構(gòu)標(biāo)記為“已刪除”管引,并在之后的某個(gè)時(shí)間點(diǎn)(通常是在不會(huì)干擾讀取操作的時(shí)候)真正刪除這些數(shù)據(jù)結(jié)構(gòu)。
RCU 機(jī)制的實(shí)現(xiàn)依賴于一些底層機(jī)制闯两,比如內(nèi)存屏障褥伴、原子操作等谅将。在 Linux 內(nèi)核中,RCU 機(jī)制被廣泛應(yīng)用于多個(gè)子系統(tǒng)重慢,比如進(jìn)程管理饥臂、網(wǎng)絡(luò)協(xié)議棧等,以提高內(nèi)核的并發(fā)性能似踱。
鎖與 RCU
看起來(lái) RCU 和讀寫(xiě)鎖比較相似隅熙,當(dāng)然 kernel 中并沒(méi)有讀寫(xiě)鎖的實(shí)現(xiàn),只提供了互斥鎖和自旋鎖核芽,但 RCU 是種不同的并發(fā)編程技術(shù)囚戚,它們各自有自己的優(yōu)點(diǎn)和適用場(chǎng)景。
適合鎖的場(chǎng)景
鎖適用于短時(shí)間內(nèi)占用共享資源的情況轧简。當(dāng)一個(gè)線程獲得鎖后驰坊,其他線程需要等待該線程釋放鎖后才能獲得鎖。如果獲得鎖的線程占用時(shí)間很長(zhǎng)哮独,那么其他等待的線程會(huì)被阻塞拳芙,這樣會(huì)造成嚴(yán)重的性能問(wèn)題。因此皮璧,鎖適用于短時(shí)間內(nèi)占用共享資源的情況舟扎。
適合 RCU 的場(chǎng)景
而 RCU 則適用于讀多寫(xiě)少的情況。RCU 允許多個(gè)線程并發(fā)地讀取共享資源悴务,而不需要獲得鎖或等待其他線程釋放鎖浆竭,從而提高了并發(fā)度。當(dāng)需要修改共享資源時(shí)惨寿,RCU 會(huì)延遲對(duì)共享資源的釋放,從而避免了讀取共享資源的線程被阻塞的問(wèn)題删窒。因此裂垦,RCU 適用于讀多寫(xiě)少的情況。
在實(shí)際的系統(tǒng)開(kāi)發(fā)中肌索,自旋鎖和 RCU 可以根據(jù)實(shí)際的情況進(jìn)行選擇蕉拢,以保證系統(tǒng)的性能和正確性。
RCU 相關(guān)的 API
在 Linux 5.4.43 中诚亚,提供了以下常用的 RCU API:
- rcu_read_lock():獲取 RCU 讀鎖晕换,讀取共享數(shù)據(jù)結(jié)構(gòu)時(shí)必須先獲取讀鎖。
- rcu_read_unlock():釋放 RCU 讀鎖站宗。
- synchronize_rcu():等待當(dāng)前所有使用 RCU 讀取共享數(shù)據(jù)結(jié)構(gòu)的讀者完成后再執(zhí)行后續(xù)操作闸准。
- call_rcu():將一個(gè)回調(diào)函數(shù)關(guān)聯(lián)到 RCU 機(jī)制中,當(dāng)沒(méi)有任何 RCU 讀者在使用共享數(shù)據(jù)結(jié)構(gòu)時(shí)執(zhí)行該回調(diào)函數(shù)梢灭。
- rcu_assign_pointer():原子地將指針賦值給共享數(shù)據(jù)結(jié)構(gòu)夷家,并確保該指針在 RCU 讀者完成讀取之前一直存在蒸其。
- rcu_dereference():讀取共享數(shù)據(jù)結(jié)構(gòu)中的指針,返回一個(gè)指針的副本库快,并確保該指針在 RCU 讀者完成讀取之前一直存在摸袁。
這些 API 可以幫助我們使用 RCU 機(jī)制來(lái)保護(hù)共享數(shù)據(jù)結(jié)構(gòu),避免出現(xiàn)數(shù)據(jù)競(jìng)爭(zhēng)和死鎖等問(wèn)題义屏。
RCU 示例
#include <linux/module.h>
#include <linux/rcupdate.h>
struct my_node {
int val;
struct rcu_head rcu;
struct list_head list;
};
LIST_HEAD(my_list);
/* 添加一個(gè)節(jié)點(diǎn)到鏈表中 */
void add_node(int val)
{
struct my_node *new_node = kmalloc(sizeof(*new_node), GFP_KERNEL);
if (!new_node) {
printk(KERN_ERR "Failed to allocate memory for new node\n");
return;
}
new_node->val = val;
INIT_LIST_HEAD(&new_node->list);
/* 加入鏈表 */
list_add(&new_node->list, &my_list);
}
/* 刪除值為 val 的節(jié)點(diǎn) */
void del_node(int val)
{
struct my_node *node, *tmp;
/* 遍歷鏈表并刪除匹配節(jié)點(diǎn) */
list_for_each_entry_safe(node, tmp, &my_list, list) {
if (node->val == val) {
list_del_rcu(&node->list);
call_rcu(&node->rcu, kfree);
}
}
}
/* 遍歷整個(gè)鏈表靠汁,打印節(jié)點(diǎn)的值 */
void traverse_list(void)
{
struct my_node *node;
/* 進(jìn)入 RCU 讀取臨界區(qū) */
rcu_read_lock();
/* 遍歷鏈表并打印節(jié)點(diǎn)的值 */
list_for_each_entry_rcu(node, &my_list, list) {
printk(KERN_INFO "Node value: %d\n", node->val);
}
/* 離開(kāi) RCU 讀取臨界區(qū) */
rcu_read_unlock();
}
示例中,我們使用 RCU 來(lái)保護(hù)鏈表的訪問(wèn)闽铐。添加節(jié)點(diǎn)時(shí)蝶怔,我們不需要獲取鎖來(lái)保護(hù)共享資源。刪除節(jié)點(diǎn)時(shí)阳啥,我們使用了 list_del_rcu 來(lái)刪除節(jié)點(diǎn)添谊,并使用 call_rcu 函數(shù)來(lái)安排釋放內(nèi)存的回調(diào)函數(shù)。在遍歷鏈表時(shí)察迟,我們使用了 rcu_read_lock 和 rcu_read_unlock 來(lái)進(jìn)入和離開(kāi) RCU 讀取臨界區(qū)斩狱。
copy:https://github.com/cc14514/notes/blob/main/linux-kernel/rcu.md