linux kernel 中的 rcu 機(jī)制

什么是 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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容