偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Linux進(jìn)程管理核心機(jī)制:從調(diào)度到RCU的底層原理

系統(tǒng) Linux
RCU,全稱 Read - Copy - Update,即讀 - 拷貝 - 更新,是 Linux 內(nèi)核中一種用于實(shí)現(xiàn)高效并發(fā)控制的同步機(jī)制,特別適用于讀多寫少的場景。與傳統(tǒng)的鎖機(jī)制(如互斥鎖、讀寫鎖等)不同,RCU 通過獨(dú)特的設(shè)計(jì),盡可能減少讀操作的開銷,實(shí)現(xiàn)讀操作的無鎖化,從而大大提高系統(tǒng)在高并發(fā)讀取情況下的性能。

每天在 Linux 用ps -ef看進(jìn)程、top盯 CPU 時(shí),你或許會(huì)好奇:多進(jìn)程搶 CPU,為何瀏覽器不卡、數(shù)據(jù)庫能響應(yīng)?高并發(fā)下內(nèi)核讀寫共享數(shù)據(jù),怎沒因 “鎖” 拖慢速度?答案藏在 Linux 進(jìn)程管理的兩大核心機(jī)制里:一是進(jìn)程調(diào)度機(jī)制,像 “交通警察” 分配 CPU 資源,決定進(jìn)程運(yùn)行優(yōu)先級(jí)與時(shí)長,直接影響系統(tǒng)響應(yīng);二是RCU 機(jī)制,似 “高并發(fā)數(shù)據(jù)管家”,解決多線程讀寫共享數(shù)據(jù)的 “快與安全” 難題,多核場景下至關(guān)重要。

不管你做后端、運(yùn)維,還是想深入內(nèi)核,懂這兩大機(jī)制都是 “看透 Linux 本質(zhì)” 的關(guān)鍵 —— 比如明白調(diào)度策略,就懂nice命令調(diào)優(yōu)先級(jí)的原理;搞懂 RCU,就理解高并發(fā) “無鎖訪問” 的實(shí)現(xiàn)。接下來會(huì)從實(shí)際問題出發(fā),不堆砌晦澀源碼,拆解進(jìn)程調(diào)度的優(yōu)先級(jí)邏輯、調(diào)度策略,以及 RCU 的 “讀 - 復(fù)制 - 更新” 核心思路,幫你搞懂內(nèi)核如何高效管理資源與數(shù)據(jù)。

一、RCU 機(jī)制是什么?

RCU,全稱 Read - Copy - Update,即讀 - 拷貝 - 更新,是 Linux 內(nèi)核中一種用于實(shí)現(xiàn)高效并發(fā)控制的同步機(jī)制,特別適用于讀多寫少的場景。與傳統(tǒng)的鎖機(jī)制(如互斥鎖、讀寫鎖等)不同,RCU 通過獨(dú)特的設(shè)計(jì),盡可能減少讀操作的開銷,實(shí)現(xiàn)讀操作的無鎖化,從而大大提高系統(tǒng)在高并發(fā)讀取情況下的性能。

在操作系統(tǒng)中,數(shù)據(jù)一致性訪問是一個(gè)非常重要的部分,通常我們可以采用鎖機(jī)制實(shí)現(xiàn)數(shù)據(jù)的一致性訪問。例如,semaphore、spinlock機(jī)制,在訪問共享數(shù)據(jù)時(shí),首先訪問鎖資源,在獲取鎖資源的前提下才能實(shí)現(xiàn)數(shù)據(jù)的訪問。這種原理很簡單,根本的思想就是在訪問臨界資源時(shí),首先訪問一個(gè)全局的變量(鎖),通過全局變量的狀態(tài)來控制線程對(duì)臨界資源的訪問。但是,這種思想是需要硬件支持的,硬件需要配合實(shí)現(xiàn)全局變量(鎖)的讀-修改-寫,現(xiàn)代CPU都會(huì)提供這樣的原子化指令。采用鎖機(jī)制實(shí)現(xiàn)數(shù)據(jù)訪問的一致性存在如下兩個(gè)問題:

  1. 效率問題。鎖機(jī)制的實(shí)現(xiàn)需要對(duì)內(nèi)存的原子化訪問,這種訪問操作會(huì)破壞流水線操作,降低了流水線效率。這是影響性能的一個(gè)因素。另外,在采用讀寫鎖機(jī)制的情況下,寫鎖是排他鎖,無法實(shí)現(xiàn)寫鎖與讀鎖的并發(fā)操作,在某些應(yīng)用下會(huì)降低性能。
  2. 擴(kuò)展性問題。當(dāng)系統(tǒng)中CPU數(shù)量增多的時(shí)候,采用鎖機(jī)制實(shí)現(xiàn)數(shù)據(jù)的同步訪問效率偏低。并且隨著CPU數(shù)量的增多,效率降低,由此可見鎖機(jī)制實(shí)現(xiàn)的數(shù)據(jù)一致性訪問擴(kuò)展性差。

圖片圖片

  • 讀者無鎖訪問:在 RCU 機(jī)制下,讀者線程在訪問被保護(hù)的共享數(shù)據(jù)時(shí),不需要獲取任何鎖。這意味著多個(gè)讀者線程可以同時(shí)并發(fā)地訪問共享數(shù)據(jù),而不會(huì)因?yàn)殒i競爭而產(chǎn)生等待和性能損耗 。例如,在一個(gè)多線程的文件系統(tǒng)中,當(dāng)多個(gè)線程需要讀取文件目錄結(jié)構(gòu)時(shí),如果采用 RCU 機(jī)制,這些讀操作可以并行進(jìn)行,極大提高了讀取效率。
  • 寫者復(fù)制更新:當(dāng)寫者線程需要修改共享數(shù)據(jù)時(shí),不會(huì)直接在原數(shù)據(jù)上進(jìn)行操作。相反,寫者會(huì)首先創(chuàng)建一個(gè)原數(shù)據(jù)的副本,然后在這個(gè)副本上進(jìn)行修改。修改完成后,通過一個(gè)原子操作,將指向原數(shù)據(jù)的指針更新為指向新的修改后的副本。這樣做的好處是,在寫者進(jìn)行修改的過程中,讀者線程仍然可以繼續(xù)訪問原數(shù)據(jù),不會(huì)受到寫操作的影響。比如,在更新網(wǎng)絡(luò)設(shè)備的配置信息時(shí),寫者先復(fù)制當(dāng)前配置數(shù)據(jù),修改副本后再更新指針,讀配置信息的線程不會(huì)被打斷。
  • 寬限期(Grace Period):在寫者完成數(shù)據(jù)更新并切換指針后,并不會(huì)立即釋放舊數(shù)據(jù)的內(nèi)存空間。這是因?yàn)榭赡苓€有一些讀者線程在寫操作開始前就已經(jīng)進(jìn)入臨界區(qū),正在訪問舊數(shù)據(jù)。只有當(dāng)所有在寫操作開始前進(jìn)入臨界區(qū)的讀者都退出臨界區(qū)后,舊數(shù)據(jù)才會(huì)被安全地釋放。從寫者完成更新到舊數(shù)據(jù)被釋放的這段時(shí)間,就稱為寬限期。寬限期的實(shí)現(xiàn)依賴于內(nèi)核中對(duì) CPU 上下文切換等事件的監(jiān)測,當(dāng)所有 CPU 都經(jīng)歷了一次上下文切換(表示之前的讀操作都已完成),寬限期結(jié)束,舊數(shù)據(jù)可以被回收。
  • 發(fā)布 - 訂閱模式(Publish-Subscribe Pattern):為了確保讀者和寫者之間的數(shù)據(jù)一致性,RCU 機(jī)制引入了發(fā)布 - 訂閱模式。寫者在完成數(shù)據(jù)修改并準(zhǔn)備切換指針時(shí),通過特定的操作(如rcu_assign_pointer)“發(fā)布” 新的數(shù)據(jù)。讀者在訪問共享數(shù)據(jù)時(shí),使用rcu_dereference操作 “訂閱” 數(shù)據(jù)。這些操作結(jié)合內(nèi)存屏障(Memory Barrier)技術(shù),保證了寫者發(fā)布新數(shù)據(jù)的操作對(duì)讀者可見,同時(shí)防止指令重排序?qū)е碌臄?shù)據(jù)不一致問題。例如,在路由表更新場景中,寫者更新路由表后發(fā)布新表,讀者通過訂閱獲取最新且一致的路由信息用于數(shù)據(jù)轉(zhuǎn)發(fā)。

RCU的關(guān)鍵思想有兩個(gè):①復(fù)制后更新;②延遲回收內(nèi)存。典型的RCU更新時(shí)序如下:

  1. 復(fù)制:將需要更新的數(shù)據(jù)復(fù)制到新內(nèi)存地址;
  2. 更新:更新復(fù)制數(shù)據(jù),這時(shí)候操作的新的內(nèi)存地址;
  3. 替換:使用新內(nèi)存地址指針替換舊數(shù)據(jù)內(nèi)存地址指針,
  4. 此后舊數(shù)據(jù)將無法被后續(xù)讀者訪問;
  5. 等待,所有訪問舊數(shù)據(jù)的讀者進(jìn)入靜默期,即訪問舊數(shù)據(jù)完成;
  6. 回收:當(dāng)沒有任何持有舊數(shù)據(jù)結(jié)構(gòu)引用的讀者后,安全地回收舊數(shù)據(jù)內(nèi)存。

二、RCU 機(jī)制如何工作?

RCU 機(jī)制的工作原理主要圍繞讀操作和寫操作展開,同時(shí)涉及寬限期的管理,以確保數(shù)據(jù)一致性和高效的并發(fā)訪問。

2.1讀操作流程

  1. 進(jìn)入臨界區(qū):讀者線程在訪問被 RCU 保護(hù)的共享數(shù)據(jù)前,調(diào)用rcu_read_lock函數(shù)。這個(gè)函數(shù)的主要作用是標(biāo)記讀操作的開始,同時(shí)通過preempt_disable關(guān)閉內(nèi)核搶占(防止在讀取過程中被其他高優(yōu)先級(jí)任務(wù)搶占,導(dǎo)致數(shù)據(jù)訪問不一致),但允許中斷發(fā)生。例如,在一個(gè)多線程的數(shù)據(jù)庫查詢場景中,當(dāng)一個(gè)線程調(diào)用rcu_read_lock后,它就開始了對(duì)數(shù)據(jù)庫表結(jié)構(gòu)(共享數(shù)據(jù))的讀取操作,此時(shí)不會(huì)因?yàn)閮?nèi)核調(diào)度其他任務(wù)而被打斷讀取流程。
  2. 數(shù)據(jù)訪問:進(jìn)入臨界區(qū)后,讀者線程使用rcu_dereference操作來安全地獲取指向共享數(shù)據(jù)的指針并訪問數(shù)據(jù)。rcu_dereference結(jié)合內(nèi)存屏障技術(shù),確保讀者線程能夠看到最新的、一致的數(shù)據(jù)。比如在讀取網(wǎng)絡(luò)配置參數(shù)時(shí),rcu_dereference能保證讀取到的是完整且最新的配置信息,而不會(huì)因?yàn)閷懖僮髡谶M(jìn)行而讀到部分更新或不一致的數(shù)據(jù)。
  3. 離開臨界區(qū):完成數(shù)據(jù)訪問后,讀者線程調(diào)用rcu_read_unlock函數(shù),標(biāo)記讀操作的結(jié)束,并通過preempt_enable重新開啟內(nèi)核搶占。這樣,系統(tǒng)又可以正常調(diào)度其他任務(wù),不會(huì)因?yàn)楸敬巫x操作而影響系統(tǒng)的整體調(diào)度。
#include <linux/rculist.h>
#include <linux/sched.h>
#include <linux/module.h>

// 定義一個(gè)被RCU保護(hù)的共享數(shù)據(jù)結(jié)構(gòu)(示例:簡單鏈表節(jié)點(diǎn))
struct rcu_demo_node {
    int data;
    struct list_head list;
};

// 全局共享鏈表(被RCU保護(hù))
static LIST_HEAD(rcu_demo_list);
static DEFINE_SPINLOCK(rcu_demo_lock); // 寫操作時(shí)使用的鎖

// RCU讀操作示例函數(shù)
static void rcu_reader_example(void)
{
    struct rcu_demo_node *node;

    // 1. 進(jìn)入RCU讀臨界區(qū):禁用搶占,標(biāo)記讀操作開始
    rcu_read_lock();

    // 2. 安全訪問共享數(shù)據(jù):通過rcu_dereference獲取指針并遍歷
    list_for_each_entry_rcu(node, &rcu_demo_list, list) {
        // 訪問數(shù)據(jù)(此時(shí)即使有寫操作,也能看到一致的舊版本或新版本數(shù)據(jù))
        pr_info("RCU Reader: Read data = %d\n", node->data);
    }

    // 3. 退出RCU讀臨界區(qū):恢復(fù)搶占,標(biāo)記讀操作結(jié)束
    rcu_read_unlock();
}

// 模塊初始化函數(shù)(示例:啟動(dòng)一個(gè)讀操作)
static int __init rcu_demo_init(void)
{
    pr_info("RCU demo module loaded\n");
    rcu_reader_example(); // 執(zhí)行RCU讀操作
    return 0;
}

// 模塊退出函數(shù)
static void __exit rcu_demo_exit(void)
{
    pr_info("RCU demo module unloaded\n");
}

module_init(rcu_demo_init);
module_exit(rcu_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("RCU Read Operation Example");
  1. 進(jìn)入臨界區(qū):rcu_read_lock() 函數(shù)會(huì)禁用當(dāng)前線程的內(nèi)核搶占(通過 preempt_disable),同時(shí)將當(dāng)前線程標(biāo)記為活躍的 RCU 讀者。這一步確保讀操作不會(huì)被內(nèi)核調(diào)度打斷,避免延長寫操作的等待時(shí)間。
  2. 數(shù)據(jù)訪問:使用 list_for_each_entry_rcu 宏遍歷鏈表(內(nèi)部封裝了 rcu_dereference),確保通過 RCU 安全機(jī)制獲取指針。該宏會(huì)插入內(nèi)存屏障,防止 CPU 指令重排序,保證讀取到的數(shù)據(jù)是一致的快照。
  3. 退出臨界區(qū):rcu_read_unlock() 函數(shù)恢復(fù)內(nèi)核搶占(preempt_enable),并解除當(dāng)前線程的 RCU 讀者標(biāo)記。此時(shí)若有寫操作等待回收舊數(shù)據(jù),系統(tǒng)會(huì)在所有讀者退出后進(jìn)行清理。

注意:此代碼為內(nèi)核態(tài)示例,RCU 是 Linux 內(nèi)核的同步機(jī)制,用戶態(tài)程序通常不直接使用。實(shí)際使用中,還需要配合寫操作的 RCU 機(jī)制(如 rcu_assign_pointer、call_rcu 等)才能完整工作。

2.2寫操作流程

  1. 復(fù)制數(shù)據(jù):當(dāng)寫者線程需要修改共享數(shù)據(jù)時(shí),首先分配一塊新的內(nèi)存空間,用于存放數(shù)據(jù)的副本。例如,在更新一個(gè)鏈表節(jié)點(diǎn)的數(shù)據(jù)時(shí),寫者會(huì)創(chuàng)建一個(gè)新的節(jié)點(diǎn),其結(jié)構(gòu)與原節(jié)點(diǎn)相同。然后將原數(shù)據(jù)的內(nèi)容完整地復(fù)制到新的副本中。以更新文件系統(tǒng)的元數(shù)據(jù)為例,寫者會(huì)復(fù)制當(dāng)前的元數(shù)據(jù)結(jié)構(gòu)到新的內(nèi)存區(qū)域,確保新副本包含原數(shù)據(jù)的所有信息。
  2. 修改副本:在新的副本上進(jìn)行數(shù)據(jù)修改操作。由于這是對(duì)副本進(jìn)行修改,不會(huì)影響正在被讀者線程訪問的原數(shù)據(jù),保證了讀操作的連續(xù)性和一致性。比如在修改路由表項(xiàng)時(shí),寫者在副本上更新目標(biāo)地址、下一跳等信息,整個(gè)修改過程對(duì)讀路由表的線程透明。
  3. 指針替換:完成修改后,寫者使用一個(gè)原子操作(如rcu_assign_pointer)將指向原數(shù)據(jù)的指針更新為指向新的修改后的副本。這個(gè)原子操作保證了指針切換的原子性,避免了讀者線程看到不一致的指針狀態(tài)。例如,在更新系統(tǒng)的設(shè)備列表時(shí),通過原子操作切換指針,使得新的設(shè)備列表信息能夠立即被后續(xù)的讀操作獲取到,同時(shí)保證了當(dāng)前正在進(jìn)行的讀操作不會(huì)受到影響。
  4. 注冊(cè)回調(diào)函數(shù):寫者注冊(cè)一個(gè)回調(diào)函數(shù),用于在寬限期結(jié)束后釋放舊數(shù)據(jù)的內(nèi)存空間。這個(gè)回調(diào)函數(shù)會(huì)被加入到 RCU 的回調(diào)函數(shù)隊(duì)列中,等待寬限期結(jié)束后執(zhí)行。
#include <linux/rculist.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/slab.h>

// 定義帶RCU頭的共享數(shù)據(jù)結(jié)構(gòu)
struct rcu_demo_node {
    int data;
    struct list_head list;
    struct rcu_head rcu; // 用于RCU回調(diào)回收
};

// 全局共享鏈表及寫鎖
static LIST_HEAD(rcu_demo_list);
static DEFINE_SPINLOCK(rcu_demo_lock);

// 舊數(shù)據(jù)回收回調(diào)函數(shù)
static void rcu_node_free(struct rcu_head *rcu)
{
    struct rcu_demo_node *node = container_of(rcu, struct rcu_demo_node, rcu);
    kfree(node); // 寬限期結(jié)束后釋放舊節(jié)點(diǎn)
    pr_info("RCU Writer: Old node freed\n");
}

// RCU寫操作示例函數(shù)(修改指定節(jié)點(diǎn)數(shù)據(jù))
static void rcu_writer_example(int old_val, int new_val)
{
    struct rcu_demo_node *old_node, *new_node;
    unsigned long flags;

    // 1. 查找需要修改的舊節(jié)點(diǎn)(簡化示例,實(shí)際需遍歷查找)
    spin_lock_irqsave(&rcu_demo_lock, flags);
    list_for_each_entry(old_node, &rcu_demo_list, list) {
        if (old_node->data == old_val) {
            // 2. 復(fù)制數(shù)據(jù):分配新節(jié)點(diǎn)并復(fù)制舊數(shù)據(jù)
            new_node = kmalloc(sizeof(*new_node), GFP_KERNEL);
            if (!new_node) {
                spin_unlock_irqrestore(&rcu_demo_lock, flags);
                return;
            }
            *new_node = *old_node; // 復(fù)制舊節(jié)點(diǎn)數(shù)據(jù)

            // 3. 修改副本:在新節(jié)點(diǎn)上修改數(shù)據(jù)
            new_node->data = new_val;
            pr_info("RCU Writer: Modified data from %d to %d\n", old_val, new_val);

            // 4. 指針替換:原子替換鏈表節(jié)點(diǎn)
            list_replace_rcu(&old_node->list, &new_node->list);

            // 5. 注冊(cè)回調(diào):寬限期后釋放舊節(jié)點(diǎn)
            call_rcu(&old_node->rcu, rcu_node_free);
            break;
        }
    }
    spin_unlock_irqrestore(&rcu_demo_lock, flags);
}

// 初始化函數(shù):添加測試節(jié)點(diǎn)并執(zhí)行寫操作
static int __init rcu_demo_init(void)
{
    struct rcu_demo_node *node;
    unsigned long flags;

    // 初始化測試節(jié)點(diǎn)
    node = kmalloc(sizeof(*node), GFP_KERNEL);
    node->data = 100;
    spin_lock_irqsave(&rcu_demo_lock, flags);
    list_add_rcu(&node->list, &rcu_demo_list);
    spin_unlock_irqrestore(&rcu_demo_lock, flags);
  1. 復(fù)制數(shù)據(jù):通過kmalloc分配新節(jié)點(diǎn),使用*new_node = *old_node完整復(fù)制舊節(jié)點(diǎn)數(shù)據(jù),確保新副本包含所有原始信息。
  2. 修改副本:直接在新節(jié)點(diǎn)new_node上修改目標(biāo)字段(new_node->data = new_val),此操作完全獨(dú)立于舊節(jié)點(diǎn),不影響讀者對(duì)舊數(shù)據(jù)的訪問。
  3. 指針替換:使用list_replace_rcu宏(內(nèi)部封裝rcu_assign_pointer)原子性替換鏈表節(jié)點(diǎn),保證讀者要么看到舊指針,要么看到新指針,不會(huì)出現(xiàn)中間狀態(tài)。
  4. 注冊(cè)回調(diào):通過call_rcu注冊(cè)rcu_node_free回調(diào)函數(shù),RCU 機(jī)制會(huì)在所有活躍讀者退出臨界區(qū)(寬限期結(jié)束)后自動(dòng)執(zhí)行該函數(shù),安全釋放舊節(jié)點(diǎn)內(nèi)存。

此示例完整展示了 RCU"讀無鎖、寫復(fù)制" 的核心思想,寫操作不會(huì)阻塞讀操作,讀操作也不會(huì)阻塞寫操作,大幅提升了高并發(fā)場景下的性能。

2.3寬限期的作用與實(shí)現(xiàn)

在 RCU(Read-Copy-Update)機(jī)制中,寬限期(Grace Period)是保障數(shù)據(jù)安全回收的核心機(jī)制,其設(shè)計(jì)直接決定了 RCU 在并發(fā)場景下的正確性。

三、RCU 機(jī)制的優(yōu)勢(shì)

3.1性能提升

在高并發(fā)讀取場景下,RCU 機(jī)制通過允許讀者無鎖訪問共享數(shù)據(jù),顯著減少了鎖競爭和同步開銷,從而極大地提升了系統(tǒng)性能。在傳統(tǒng)的鎖機(jī)制中,當(dāng)多個(gè)讀者線程試圖同時(shí)訪問共享數(shù)據(jù)時(shí),會(huì)因?yàn)殒i的存在而產(chǎn)生競爭。例如,使用互斥鎖時(shí),每次只能有一個(gè)線程獲取鎖并訪問數(shù)據(jù),其他線程必須等待鎖的釋放,這會(huì)導(dǎo)致大量的上下文切換和等待時(shí)間,嚴(yán)重降低系統(tǒng)的并發(fā)處理能力。

而在 RCU 機(jī)制下,讀者線程無需獲取鎖即可直接訪問共享數(shù)據(jù)。以一個(gè)多線程的數(shù)據(jù)庫查詢系統(tǒng)為例,假設(shè)有大量的查詢線程(讀者)需要讀取數(shù)據(jù)庫中的用戶信息表(共享數(shù)據(jù))。在高并發(fā)情況下,如果使用傳統(tǒng)鎖機(jī)制,線程之間會(huì)頻繁競爭鎖資源,導(dǎo)致查詢操作的延遲增加。但采用 RCU 機(jī)制后,這些查詢線程可以同時(shí)無鎖地讀取用戶信息表,大大提高了查詢的并發(fā)處理能力,減少了響應(yīng)時(shí)間 。

3.2擴(kuò)展性好

RCU 機(jī)制在多核系統(tǒng)中展現(xiàn)出良好的擴(kuò)展性,不會(huì)隨著 CPU 數(shù)量的增加而導(dǎo)致性能下降。隨著硬件技術(shù)的發(fā)展,多核處理器被廣泛應(yīng)用,系統(tǒng)中 CPU 核心數(shù)量不斷增多。在這種情況下,傳統(tǒng)的同步機(jī)制面臨著嚴(yán)峻的挑戰(zhàn)。例如,自旋鎖在多核環(huán)境下,如果多個(gè) CPU 核心同時(shí)競爭同一個(gè)鎖,會(huì)導(dǎo)致大量的 CPU 時(shí)間浪費(fèi)在自旋等待上,隨著 CPU 數(shù)量的增加,這種競爭會(huì)更加激烈,從而嚴(yán)重影響系統(tǒng)性能。而 RCU 機(jī)制的設(shè)計(jì)理念使其天然適合多核環(huán)境。在多核系統(tǒng)中,每個(gè) CPU 核心上的線程都可以作為讀者無鎖地訪問共享數(shù)據(jù),不會(huì)因?yàn)?CPU 數(shù)量的增加而產(chǎn)生額外的鎖競爭開銷。

例如,在一個(gè)具有多個(gè) CPU 核心的服務(wù)器系統(tǒng)中,網(wǎng)絡(luò)路由表(共享數(shù)據(jù))需要被頻繁讀取和偶爾更新。使用 RCU 機(jī)制,各個(gè) CPU 核心上的網(wǎng)絡(luò)處理線程可以高效地讀取路由表,而寫者線程在更新路由表時(shí),也不會(huì)影響其他 CPU 核心上的讀操作,系統(tǒng)的整體性能能夠隨著 CPU 核心數(shù)量的增加而線性提升 。

3.3無死鎖風(fēng)險(xiǎn)

死鎖是多線程編程中常見的問題,當(dāng)多個(gè)線程相互等待對(duì)方釋放鎖資源時(shí),就會(huì)陷入死鎖狀態(tài),導(dǎo)致程序無法繼續(xù)執(zhí)行。傳統(tǒng)的鎖機(jī)制,如互斥鎖、讀寫鎖等,如果使用不當(dāng),很容易出現(xiàn)死鎖問題。例如,線程 A 持有鎖 1 并試圖獲取鎖 2,而線程 B 持有鎖 2 并試圖獲取鎖 1,此時(shí)就會(huì)發(fā)生死鎖。而 RCU 機(jī)制能有效避免死鎖問題。

在 RCU 中,讀者線程在訪問共享數(shù)據(jù)時(shí)不需要獲取鎖,這就從根本上消除了因?yàn)樽x者和寫者之間或讀者之間的鎖依賴而導(dǎo)致的死鎖可能性。寫者線程雖然在更新數(shù)據(jù)時(shí)需要進(jìn)行一些同步操作,但由于其采用復(fù)制更新和寬限期的策略,也不會(huì)與讀者線程形成死鎖關(guān)系。例如,在一個(gè)多線程的文件系統(tǒng)實(shí)現(xiàn)中,使用 RCU 機(jī)制來保護(hù)文件元數(shù)據(jù)的訪問。讀者線程在讀取文件元數(shù)據(jù)時(shí)無需鎖,寫者線程在更新元數(shù)據(jù)時(shí),先復(fù)制數(shù)據(jù)進(jìn)行修改,然后等待寬限期結(jié)束后才替換舊數(shù)據(jù),整個(gè)過程中不存在鎖的循環(huán)等待情況,確保了系統(tǒng)的穩(wěn)定性和可靠性 。

四、RCU 機(jī)制的局限性

4.1寫操作開銷大

RCU 機(jī)制雖然在提升讀操作性能方面表現(xiàn)出色,但寫操作卻存在較大開銷。寫者在更新數(shù)據(jù)時(shí),需要進(jìn)行數(shù)據(jù)復(fù)制操作,這不僅消耗額外的內(nèi)存資源,還增加了時(shí)間開銷。以更新一個(gè)包含大量元素的數(shù)組為例,寫者需要分配新的內(nèi)存空間,并將原數(shù)組的所有元素逐一復(fù)制到新的副本中,這個(gè)過程會(huì)占用較多的內(nèi)存帶寬和 CPU 時(shí)間。

此外,寫者還需要等待寬限期結(jié)束后才能釋放舊數(shù)據(jù)的內(nèi)存空間,這意味著在寬限期內(nèi),系統(tǒng)需要維護(hù)新舊兩份數(shù)據(jù),進(jìn)一步增加了內(nèi)存的使用壓力。如果寫操作頻繁發(fā)生,這些開銷可能會(huì)對(duì)系統(tǒng)的整體性能產(chǎn)生顯著影響,導(dǎo)致系統(tǒng)響應(yīng)變慢、內(nèi)存利用率降低等問題 。

4.2適用場景有限

RCU 機(jī)制主要適用于讀多寫少的場景,在這種場景下,其無鎖讀的特性能夠充分發(fā)揮優(yōu)勢(shì),提高系統(tǒng)的并發(fā)性能。然而,在寫操作頻繁的場景中,RCU 機(jī)制的性能表現(xiàn)可能并不理想。由于寫者在更新數(shù)據(jù)時(shí)需要復(fù)制數(shù)據(jù)和等待寬限期,這會(huì)導(dǎo)致寫操作的延遲增加。例如,在一個(gè)實(shí)時(shí)數(shù)據(jù)庫系統(tǒng)中,如果寫操作頻繁,RCU 機(jī)制可能無法滿足系統(tǒng)對(duì)寫操作的實(shí)時(shí)性要求。

此外,對(duì)于那些需要頻繁進(jìn)行數(shù)據(jù)一致性更新的場景,RCU 機(jī)制可能也不太適用。因?yàn)?RCU 機(jī)制在更新數(shù)據(jù)時(shí),存在一定的時(shí)間窗口,期間讀者可能會(huì)讀取到舊數(shù)據(jù),這在一些對(duì)數(shù)據(jù)一致性要求極高的場景(如金融交易系統(tǒng))中是不可接受的 。

4.3實(shí)現(xiàn)復(fù)雜

RCU 機(jī)制的實(shí)現(xiàn)依賴于底層的內(nèi)存屏障和原子操作等技術(shù),這使得其實(shí)現(xiàn)和理解都相對(duì)復(fù)雜。內(nèi)存屏障用于確保內(nèi)存操作的順序性和可見性,防止 CPU 或編譯器的優(yōu)化導(dǎo)致數(shù)據(jù)不一致問題。原子操作則用于保證數(shù)據(jù)更新的原子性,避免并發(fā)訪問時(shí)的數(shù)據(jù)沖突。例如,在 x86 架構(gòu)下,rcu_assign_pointer函數(shù)中會(huì)使用特定的內(nèi)存屏障指令(如mfence)來確保指針更新的可見性和順序性。這些底層技術(shù)對(duì)于開發(fā)者來說,需要深入了解硬件和操作系統(tǒng)的原理才能正確運(yùn)用。此外,RCU機(jī)制的調(diào)試也比較困難,因?yàn)槠渖婕暗綇?fù)雜的并發(fā)控制和寬限期管理。當(dāng)出現(xiàn)數(shù)據(jù)不一致或性能問題時(shí),很難快速定位和解決問題,需要開發(fā)者具備豐富的經(jīng)驗(yàn)和深入的知識(shí) 。

五、RCU 機(jī)制的應(yīng)用場景

5.1內(nèi)核數(shù)據(jù)結(jié)構(gòu)管理

在 Linux 內(nèi)核中,鏈表和哈希表是常用的數(shù)據(jù)結(jié)構(gòu),用于管理各種系統(tǒng)資源和信息。RCU 機(jī)制在這些數(shù)據(jù)結(jié)構(gòu)的管理中發(fā)揮著重要作用,極大地提高了內(nèi)核在多線程環(huán)境下的并發(fā)性能。

以鏈表為例,在傳統(tǒng)的鏈表操作中,如果多個(gè)線程同時(shí)對(duì)鏈表進(jìn)行讀寫操作,需要使用鎖機(jī)制來保證數(shù)據(jù)的一致性和完整性。例如,當(dāng)一個(gè)線程要遍歷鏈表(讀操作)時(shí),另一個(gè)線程可能正在刪除鏈表中的節(jié)點(diǎn)(寫操作),如果沒有鎖的保護(hù),讀操作可能會(huì)訪問到已經(jīng)被刪除的節(jié)點(diǎn),導(dǎo)致程序崩潰或數(shù)據(jù)錯(cuò)誤。

而使用 RCU 機(jī)制,讀者線程在遍歷鏈表時(shí)不需要獲取鎖,可以無鎖并發(fā)地訪問鏈表。當(dāng)寫者線程要?jiǎng)h除鏈表中的節(jié)點(diǎn)時(shí),先將節(jié)點(diǎn)從鏈表中移除,但并不立即釋放該節(jié)點(diǎn)的內(nèi)存。而是等待寬限期結(jié)束,確保所有可能訪問該節(jié)點(diǎn)的讀者線程都已完成訪問后,再安全地釋放節(jié)點(diǎn)內(nèi)存。這樣,既保證了讀操作的高效性,又確保了寫操作不會(huì)影響正在進(jìn)行的讀操作 。

對(duì)于哈希表,RCU 機(jī)制同樣能提升其并發(fā)性能。哈希表常用于快速查找數(shù)據(jù),在 Linux 內(nèi)核中,如網(wǎng)絡(luò)協(xié)議棧中的路由表就常以哈希表的形式實(shí)現(xiàn)。當(dāng)多個(gè)線程需要查找哈希表中的數(shù)據(jù)(讀操作)時(shí),RCU 機(jī)制允許它們無鎖地進(jìn)行并發(fā)查找,提高了查找效率。而當(dāng)寫者線程要更新哈希表中的數(shù)據(jù)(如添加或刪除一個(gè)路由表項(xiàng))時(shí),先創(chuàng)建一個(gè)新的哈希表副本,在副本上進(jìn)行修改,然后通過原子操作將指向原哈希表的指針更新為指向新的副本。在寬限期內(nèi),舊的哈希表仍然保留,以確保正在進(jìn)行的讀操作可以繼續(xù)正常進(jìn)行。這種方式避免了傳統(tǒng)鎖機(jī)制下讀寫操作相互等待的問題,提高了哈希表在高并發(fā)環(huán)境下的性能和穩(wěn)定性 。

5.2文件系統(tǒng)

在文件系統(tǒng)中,文件元數(shù)據(jù)包含了文件的各種屬性信息,如文件大小、創(chuàng)建時(shí)間、所有者等,對(duì)文件元數(shù)據(jù)的高效讀寫對(duì)于文件系統(tǒng)的性能至關(guān)重要。RCU 機(jī)制通過其獨(dú)特的設(shè)計(jì),有效地提升了文件系統(tǒng)對(duì)文件元數(shù)據(jù)的處理能力。

當(dāng)多個(gè)線程需要讀取文件元數(shù)據(jù)時(shí),RCU 機(jī)制允許這些讀操作無鎖并發(fā)進(jìn)行。例如,在一個(gè)多用戶的服務(wù)器系統(tǒng)中,多個(gè)用戶可能同時(shí)查看同一個(gè)目錄下的文件列表,每個(gè)用戶的操作都涉及讀取文件元數(shù)據(jù)。使用 RCU 機(jī)制,這些讀操作可以并行執(zhí)行,大大提高了文件系統(tǒng)的響應(yīng)速度,減少了用戶等待時(shí)間。

而當(dāng)寫者線程需要更新文件元數(shù)據(jù)時(shí),如修改文件的權(quán)限或所有者信息,寫者首先復(fù)制當(dāng)前的文件元數(shù)據(jù)結(jié)構(gòu),在副本上進(jìn)行修改。完成修改后,通過原子操作將指向原文件元數(shù)據(jù)的指針更新為指向新的修改后的副本。在寬限期內(nèi),舊的文件元數(shù)據(jù)仍然可供讀者線程訪問,確保了讀操作的連續(xù)性。只有當(dāng)寬限期結(jié)束,所有可能訪問舊文件元數(shù)據(jù)的讀者線程都完成訪問后,舊的文件元數(shù)據(jù)才會(huì)被安全地釋放。這種方式避免了傳統(tǒng)鎖機(jī)制下讀寫操作相互阻塞的問題,提高了文件系統(tǒng)在處理大量并發(fā)讀寫請(qǐng)求時(shí)的性能和穩(wěn)定性 。

5.3網(wǎng)絡(luò)協(xié)議棧

在網(wǎng)絡(luò)協(xié)議棧中,路由表用于存儲(chǔ)網(wǎng)絡(luò)路由信息,指導(dǎo)數(shù)據(jù)包的轉(zhuǎn)發(fā)。路由表的查詢操作非常頻繁,而更新操作相對(duì)較少,這使得 RCU 機(jī)制成為優(yōu)化路由表操作的理想選擇。

當(dāng)網(wǎng)絡(luò)設(shè)備接收到一個(gè)數(shù)據(jù)包時(shí),需要查詢路由表來確定數(shù)據(jù)包的轉(zhuǎn)發(fā)路徑。在高并發(fā)的網(wǎng)絡(luò)環(huán)境中,可能有大量的數(shù)據(jù)包同時(shí)到達(dá),需要頻繁查詢路由表。使用 RCU 機(jī)制,多個(gè)查詢線程(讀者)可以無鎖并發(fā)地訪問路由表,大大提高了查詢效率,確保數(shù)據(jù)包能夠快速轉(zhuǎn)發(fā),減少網(wǎng)絡(luò)延遲。

當(dāng)網(wǎng)絡(luò)拓?fù)浒l(fā)生變化或新的路由信息加入時(shí),需要更新路由表(寫操作)。寫者線程在更新路由表時(shí),首先創(chuàng)建一個(gè)新的路由表副本,在副本上進(jìn)行修改,如添加、刪除或修改路由表項(xiàng)。修改完成后,通過原子操作將指向原路由表的指針更新為指向新的副本。在寬限期內(nèi),舊的路由表仍然保留,以確保正在進(jìn)行的查詢操作可以繼續(xù)正常進(jìn)行。這樣,既保證了路由表查詢操作的高效性,又確保了路由表更新操作不會(huì)影響網(wǎng)絡(luò)數(shù)據(jù)包的正常轉(zhuǎn)發(fā),提高了網(wǎng)絡(luò)通信的效率和穩(wěn)定性 。

六、如何使用 RCU 機(jī)制

6.1相關(guān) API 介紹

如果指針ptr指向被RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu),直接反引用指針是被禁止的,首先必須調(diào)用rcu_dereference(ptr),然后反引用返回的結(jié)果,需要使用rcu_read_lock和rcu_read_unlock調(diào)用來進(jìn)行保護(hù)。

rcu_read_lock()
rcu_read_unlock()
synchronize_rcu()/call_rcu()
rcu_assign_pointer()
rcu_dereference()

①rcu_read_lock():用于標(biāo)記讀操作的開始,關(guān)閉內(nèi)核搶占,確保在讀取共享數(shù)據(jù)期間不會(huì)因?yàn)閮?nèi)核調(diào)度而被打斷,從而保證數(shù)據(jù)訪問的一致性。例如在文件系統(tǒng)中讀取文件目錄結(jié)構(gòu)時(shí),調(diào)用rcu_read_lock()可以防止在讀取過程中被其他高優(yōu)先級(jí)任務(wù)搶占,導(dǎo)致目錄結(jié)構(gòu)讀取不完整。它的實(shí)現(xiàn)原理主要是通過preempt_disable來禁止內(nèi)核搶占,在一些不支持搶占的內(nèi)核中,可能僅僅是執(zhí)行一條內(nèi)存屏障指令 。

void rcu_read_lock(void);

讀者讀取受RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu)時(shí)使用,通知回收者讀者進(jìn)入了RCU的讀端臨界區(qū)。在RCU讀端臨界區(qū)訪問的任何受RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu)都會(huì)保證在臨界區(qū)期間保持未回收狀態(tài)。另外,引用計(jì)數(shù)可以與RCU一起使用,以維護(hù)對(duì)數(shù)據(jù)結(jié)構(gòu)的長期引用。在RCU讀側(cè)臨界區(qū)阻塞是非法的。rcu_read_lock的實(shí)現(xiàn)非常簡單,是關(guān)閉搶占:

static inline void __rcu_read_lock(void)
{
    preempt_disable();
}

②rcu_read_unlock():與rcu_read_lock()成對(duì)使用,標(biāo)記讀操作的結(jié)束,重新開啟內(nèi)核搶占。例如在完成對(duì)文件目錄結(jié)構(gòu)的讀取后,調(diào)用rcu_read_unlock(),系統(tǒng)就可以正常調(diào)度其他任務(wù),不會(huì)因?yàn)楸敬巫x操作而影響系統(tǒng)的整體調(diào)度。它通過preempt_enable來實(shí)現(xiàn)重新開啟內(nèi)核搶占的功能。

void rcu_read_unlock(void);

讀者結(jié)束讀取后使用,用于通知回收者其退出了讀端臨界區(qū)。RCU的讀端臨界區(qū)可能被嵌套或重疊。rcu_read_unlock的實(shí)現(xiàn)是開發(fā)搶占。

static inline void __rcu_read_unlock(void)
{
    preempt_enable();
}

③synchronize_rcu():寫者調(diào)用該函數(shù),等待寬限期結(jié)束,即等待所有在寫操作開始前進(jìn)入臨界區(qū)的讀者都退出臨界區(qū)。在更新網(wǎng)絡(luò)設(shè)備的配置信息時(shí),寫者完成配置數(shù)據(jù)的修改并切換指針后,調(diào)用synchronize_rcu(),確保所有可能訪問舊配置信息的讀者都已經(jīng)完成訪問,然后才進(jìn)行后續(xù)的操作,如釋放舊數(shù)據(jù)的內(nèi)存空間,保證了數(shù)據(jù)的一致性和安全性 。

void synchronize_rcu(void);

synchronize_rcu 函數(shù)的關(guān)鍵思想是等待。確保讀者完成對(duì)舊結(jié)構(gòu)體的操作后釋放舊結(jié)構(gòu)體。synchronize_rcu 的調(diào)用點(diǎn)標(biāo)志著“更新者代碼的結(jié)束”和“回收者代碼的開始”。它通過阻塞來做到這一點(diǎn),直到所有cpu上所有預(yù)先存在的RCU讀端臨界區(qū)都完成。

需要注意的是,synchronize_rcu()只需要等待調(diào)用它之前的讀端臨界區(qū)完成,不需要等待調(diào)用它之后開始的讀取者完成。另外,synchronize_rcu()不一定在最后一個(gè)預(yù)先存在的RCU讀端臨界區(qū)完成之后立即返回。具體實(shí)現(xiàn)中可能會(huì)有延時(shí)調(diào)度。同時(shí),為了提高效率,許多RCU實(shí)現(xiàn)請(qǐng)求批量處理,這可能會(huì)進(jìn)一步延遲 synchronize_rcu() 的返回。

④call_rcu():寫者使用這個(gè)函數(shù)注冊(cè)一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)會(huì)在寬限期結(jié)束后被調(diào)用,通常用于釋放舊數(shù)據(jù)的內(nèi)存空間。例如在刪除鏈表節(jié)點(diǎn)時(shí),寫者調(diào)用call_rcu()注冊(cè)一個(gè)釋放節(jié)點(diǎn)內(nèi)存的回調(diào)函數(shù),當(dāng)寬限期結(jié)束,所有可能訪問該節(jié)點(diǎn)的讀者都已完成訪問后,系統(tǒng)會(huì)自動(dòng)調(diào)用這個(gè)回調(diào)函數(shù),安全地釋放節(jié)點(diǎn)內(nèi)存 。

在上面的例子中,rcu_st_update阻塞直到一個(gè)寬限期結(jié)束。這很簡單,但在某些情況下,人們不能等這么久——可能還有其他高優(yōu)先級(jí)的工作要做。 在這種情況下,使用call_rcu()而不是synchronize_rcu()。call_rcu() API如下:

void call_rcu(struct rcu_head * head, void (*func)(struct rcu_head *head));

此函數(shù)在寬限期過后調(diào)用func(heda)。此調(diào)用可能發(fā)生在softirq或進(jìn)程上下文中,因此不允許阻止該函數(shù)。rcu_st結(jié)構(gòu)需要添加一個(gè)rcu-head結(jié)構(gòu),可能如下所示:

struct foo {
    int a;
    char b; 
    long c;
    struct rcu_head rcu; 
 };

foo_update_a()函數(shù)示例如下:

/*
* Create a new struct foo that is the same as the one currently
* * pointed to by gbl_foo, except that field "a" is replaced 
* * with "new_a". Points gbl_foo to the new structure, and 
* * frees up the old structure after a grace period. *
* Uses rcu_assign_pointer() to ensure that concurrent readers 
* * see the initialized version of the new structure.
* * Uses call_rcu() to ensure that any readers that might have
* * references to the old structure complete before freeing the * old structure.
* */
void foo_update_a(int new_a) {
    struct foo *new_fp = NULL; 
    struct foo *old_fp = NULL;

    new_fp = kmalloc(sizeof(*new_fp), GFP_KERNEL); 

    spin_lock(&foo_mutex);
    old_fp = rcu_dereference_protected(gbl_foo, lockdep_is_held(&foo_mutex)); 

    *new_fp = *old_fp; 
    new_fp->a = new_a;
    rcu_assign_pointer(gbl_foo, new_fp); 

    spin_unlock(&foo_mutex);
     /* 掛接釋放函數(shù) */
    call_rcu(&old_fp->rcu, foo_reclaim); 
} 

// The foo_reclaim() function might appear as follows:
void foo_reclaim(struct rcu_head *rp) 
{
    struct foo *fp = container_of(rp, struct foo, rcu); 
    foo_cleanup(fp->a); 
    kfree(fp);
}

container_of() 原語是一個(gè)宏,給定指向結(jié)構(gòu)的指針,結(jié)構(gòu)的類型以及結(jié)構(gòu)內(nèi)的指向字段,該宏將返回指向結(jié)構(gòu)開頭的指針。

使用 call_rcu() 可使 foo_update_a() 的調(diào)用方立即重新獲得控制權(quán),而不必?fù)?dān)心新近更新的元素的舊版本。 它還清楚地顯示了更新程序 foo_update_a()和回收程序 foo_reclaim() 之間的RCU區(qū)別。

在從受RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu)中刪除數(shù)據(jù)元素之后,請(qǐng)使用call_rcu()-以注冊(cè)一個(gè)回調(diào)函數(shù),該函數(shù)將在所有可能引用該數(shù)據(jù)項(xiàng)的RCU讀取側(cè)完成后調(diào)用。如果call_rcu()的回調(diào)除了在結(jié)構(gòu)上調(diào)用kfree()之外沒有做其他事情,則可以使用kfree_rcu()代替call_rcu()來避免編寫自己的回調(diào):kfree_rcu(old_fp,rcu)

⑤rcu_assign_pointer():寫者在完成數(shù)據(jù)修改并準(zhǔn)備切換指針時(shí),使用這個(gè)函數(shù)將指向原數(shù)據(jù)的指針更新為指向新的修改后的副本。這個(gè)函數(shù)結(jié)合內(nèi)存屏障技術(shù),保證了指針更新操作的原子性和可見性,確保讀者能夠看到最新的、一致的數(shù)據(jù)。比如在更新系統(tǒng)的設(shè)備列表時(shí),寫者通過 rcu_assign_pointer() 將指向舊設(shè)備列表的指針更新為指向新的設(shè)備列表,使得新的設(shè)備列表信息能夠立即被后續(xù)的讀操作獲取到,同時(shí)保證了當(dāng)前正在進(jìn)行的讀操作不會(huì)受到影響 。

voidrcu_assign_pointer(p,typeof(p)v);

rcu_assign_pointer()通過宏實(shí)現(xiàn)。將新指針賦給RCU結(jié)構(gòu)體,賦值前的讀者看到的還是舊的指針。更新者使用這個(gè)函數(shù)為受rcu保護(hù)的指針分配一個(gè)新值,以便安全地將更新的值更改傳遞給讀者。 此宏不計(jì)算rvalue,但它執(zhí)行某CPU體系結(jié)構(gòu)所需的內(nèi)存屏障指令。保證內(nèi)存屏障前的指令一定會(huì)先于內(nèi)存屏障后的指令被執(zhí)行。

它用于記錄:哪些指針受 RCU 保護(hù)以及給定結(jié)構(gòu)可供其他CPU訪問的點(diǎn),rcu_assign_pointer()最常通過_rcu列表操作原語(例如list_add_rcu())間接使用。

⑥r(nóng)cu_dereference():讀者使用這個(gè)函數(shù)來安全地獲取指向共享數(shù)據(jù)的指針并訪問數(shù)據(jù)。它結(jié)合內(nèi)存屏障技術(shù),確保讀者能夠看到最新的、一致的數(shù)據(jù),避免了因?yàn)樽x寫并發(fā)導(dǎo)致的數(shù)據(jù)不一致問題。例如在讀取網(wǎng)絡(luò)配置參數(shù)時(shí),讀者通過rcu_dereference()獲取指向配置數(shù)據(jù)的指針,能保證讀取到的是完整且最新的配置信息,而不會(huì)因?yàn)閷懖僮髡谶M(jìn)行而讀到部分更新或不一致的數(shù)據(jù) 。

typeof(p) rcu_dereference(p);

與rcu_assign_pointer()類似,rcu_dereference()也必須通過宏實(shí)現(xiàn)。讀者通過rcu_dereference()獲取受保護(hù)的RCU指針,該指針返回一個(gè)可以安全解除引用的值。 請(qǐng)注意,rcu_dereference()實(shí)際上并未取消對(duì)指針的引用,相反,它保護(hù)指針供以后取消引用。 它還針對(duì)給定的CPU體系結(jié)構(gòu)執(zhí)行任何所需的內(nèi)存屏障指令。

常見的編碼實(shí)踐是使用rcu_dereference() 將一個(gè)受rcu保護(hù)的指針復(fù)制到一個(gè)局部變量,然后解引用這個(gè)局部變量,例如:

p = rcu_dereference(head.next);
return p->data;

然而,上述情況可以整合成如下一句:

return rcu_dereference(head.next)->data;

6.2代碼示例

下面是一個(gè)使用 RCU 機(jī)制保護(hù)鏈表的代碼示例,包括添加節(jié)點(diǎn)、刪除節(jié)點(diǎn)和遍歷節(jié)點(diǎn)的操作:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/rcupdate.h>
#include <linux/list.h>

// 定義鏈表節(jié)點(diǎn)結(jié)構(gòu)
struct my_node {
    int data;
    struct list_head list;
    struct rcu_head rcu;
};

// 定義鏈表頭
static LIST_HEAD(my_list);

// 添加節(jié)點(diǎn)函數(shù)
void add_node(int new_data) {
    struct my_node *new_node = kmalloc(sizeof(struct my_node), GFP_KERNEL);
    if (!new_node) {
        return;
    }
    new_node->data = new_data;
    // 使用RCU機(jī)制添加節(jié)點(diǎn)
    list_add_rcu(&new_node->list, &my_list);
}

// 刪除節(jié)點(diǎn)函數(shù)
void remove_node(struct my_node *node) {
    // 使用RCU機(jī)制刪除節(jié)點(diǎn)
    list_del_rcu(&node->list);
    // 注冊(cè)回調(diào)函數(shù),在寬限期結(jié)束后釋放節(jié)點(diǎn)內(nèi)存
    call_rcu(&node->rcu, (void (*)(struct rcu_head *))kfree);
}

// 遍歷節(jié)點(diǎn)函數(shù)
void traverse_list(void) {
    struct my_node *entry;
    // 進(jìn)入RCU讀臨界區(qū)
    rcu_read_lock();
    list_for_each_entry_rcu(entry, &my_list, list) {
        printk(KERN_INFO "Node data: %d\n", entry->data);
    }
    // 離開RCU讀臨界區(qū)
    rcu_read_unlock();
}

static int __init my_module_init(void) {
    add_node(10);
    add_node(20);
    traverse_list();

    struct my_node *node_to_remove = list_entry(my_list.next, struct my_node, list);
    remove_node(node_to_remove);
    traverse_list();

    return 0;
}

static void __exit my_module_exit(void) {
    struct my_node *entry, *tmp;
    // 確保所有RCU操作完成
    synchronize_rcu();
    list_for_each_entry_safe_rcu(entry, tmp, &my_list, list) {
        list_del_rcu(&entry->list);
        kfree(entry);
    }
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("RCU Linked List Example");
  1. add_node函數(shù)用于向鏈表中添加新節(jié)點(diǎn),通過kmalloc分配內(nèi)存,然后使用list_add_rcu將新節(jié)點(diǎn)添加到鏈表中。
  2. remove_node函數(shù)用于從鏈表中刪除指定節(jié)點(diǎn),首先使用list_del_rcu刪除節(jié)點(diǎn),然后通過call_rcu注冊(cè)一個(gè)回調(diào)函數(shù)kfree,在寬限期結(jié)束后釋放節(jié)點(diǎn)的內(nèi)存。
  3. traverse_list函數(shù)用于遍歷鏈表,在遍歷之前調(diào)用rcu_read_lock進(jìn)入 RCU 讀臨界區(qū),遍歷結(jié)束后調(diào)用rcu_read_unlock離開臨界區(qū),確保在遍歷過程中鏈表不會(huì)被修改,保證數(shù)據(jù)的一致性 。
  4. 在模塊初始化函數(shù)my_module_init中,先添加兩個(gè)節(jié)點(diǎn),然后遍歷鏈表,接著刪除一個(gè)節(jié)點(diǎn),再次遍歷鏈表以驗(yàn)證刪除操作的正確性。
  5. 在模塊退出函數(shù)my_module_exit中,先調(diào)用synchronize_rcu等待所有 RCU 操作完成,確保所有可能訪問鏈表節(jié)點(diǎn)的讀者都已完成訪問,然后安全地釋放鏈表中剩余節(jié)點(diǎn)的內(nèi)存 。
責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2025-04-27 02:33:00

epoll核心機(jī)制服務(wù)器

2025-09-18 09:17:46

2025-09-08 02:00:00

2012-07-03 10:57:54

Hadoop核心機(jī)制

2025-04-07 11:10:00

Python列表開發(fā)

2011-12-15 09:33:19

Java

2025-07-14 02:22:00

2025-05-09 01:30:00

JavaScript事件循環(huán)基石

2023-03-03 00:03:07

Linux進(jìn)程管理

2025-10-11 01:33:00

2025-10-13 04:00:00

2025-06-16 04:00:00

2025-06-04 02:35:00

2023-03-05 15:28:39

CFSLinux進(jìn)程

2021-05-12 07:50:02

CFS調(diào)度器Linux

2020-02-07 18:16:01

進(jìn)程線程底層原理

2025-05-12 09:12:59

2025-09-29 05:00:00

Linux線程棧內(nèi)存

2009-09-16 08:40:53

linux進(jìn)程調(diào)度linuxlinux操作系統(tǒng)

2024-07-30 12:24:23

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)