自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Linux內(nèi)核中的RCU鎖:解鎖高效并發(fā)的奧秘

系統(tǒng) Linux
我們使用 RCU 來保護(hù)鏈表的訪問。添加節(jié)點(diǎn)時(shí),我們不需要獲取鎖來保護(hù)共享資源。刪除節(jié)點(diǎn)時(shí),我們使用了 list_del_rcu 來刪除節(jié)點(diǎn),并使用 call_rcu 函數(shù)來安排釋放內(nèi)存的回調(diào)函數(shù)。

在 Linux 內(nèi)核這片充滿挑戰(zhàn)與機(jī)遇的技術(shù)海洋中,高效的并發(fā)控制始終是開發(fā)者們不懈追求的目標(biāo)。多處理器環(huán)境下,數(shù)據(jù)的一致性與并發(fā)訪問的安全性,猶如兩座巍峨的高山,橫亙?cè)诿恳晃粌?nèi)核開發(fā)者的前行道路上。傳統(tǒng)的鎖機(jī)制,如自旋鎖、互斥鎖和讀寫鎖等,雖各有其用武之地,但在面對(duì)復(fù)雜多變的應(yīng)用場(chǎng)景時(shí),往往會(huì)暴露出性能瓶頸或使用局限性。

它以獨(dú)特的設(shè)計(jì)理念和巧妙的工作機(jī)制,在眾多鎖機(jī)制中脫穎而出,尤其在處理讀多寫少的場(chǎng)景時(shí),展現(xiàn)出了無與倫比的性能優(yōu)勢(shì)。從文件系統(tǒng)到網(wǎng)絡(luò)協(xié)議棧,從進(jìn)程管理到內(nèi)核數(shù)據(jù)結(jié)構(gòu)維護(hù),RCU 鎖的身影無處不在,默默支撐著 Linux 內(nèi)核高效穩(wěn)定地運(yùn)行。今天,讓我們深入探討一種別具一格且高效強(qiáng)大的鎖機(jī)制 ——RCU 鎖。

一、RCU鎖的概述

RCU,即 Read - Copy - Update,從字面上看,它的操作似乎僅包含讀取、復(fù)制和更新三個(gè)簡(jiǎn)單步驟,但實(shí)際機(jī)制遠(yuǎn)比這復(fù)雜。它專為讀多寫少的場(chǎng)景而設(shè)計(jì),核心思想是允許讀操作無鎖并發(fā)執(zhí)行,極大地提升讀操作的效率。對(duì)于寫操作,它則采用了一種獨(dú)特的策略:先復(fù)制數(shù)據(jù),在副本上進(jìn)行修改,待所有讀操作完成后,再將新副本替換舊數(shù)據(jù)。

在 RCU 機(jī)制中,讀取共享數(shù)據(jù)結(jié)構(gòu)的操作是無鎖的,因此讀取操作可以并發(fā)進(jìn)行,不會(huì)相互干擾。寫入共享數(shù)據(jù)結(jié)構(gòu)的操作則使用了延遲刪除的策略,即寫入操作并不直接修改共享數(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的實(shí)現(xiàn)過程中,我們主要解決以下問題:

  • 在讀取過程中,另外一個(gè)線程刪除了一個(gè)節(jié)點(diǎn)。刪除線程可以把這個(gè)節(jié)點(diǎn)從鏈表中移除,但它不能直接銷毀這個(gè)節(jié)點(diǎn),必須等到所有的讀取線程讀取完成以后,才進(jìn)行銷毀操作。RCU中把這個(gè)過程稱為寬限期(Grace period)。
  • 在讀取過程中,另外一個(gè)線程插入了一個(gè)新節(jié)點(diǎn),而讀線程讀到了這個(gè)節(jié)點(diǎn),那么需要保證讀到的這個(gè)節(jié)點(diǎn)是完整的。這里涉及到了發(fā)布-訂閱機(jī)制(Publish-Subscribe Mechanism)。
  • 保證讀取鏈表的完整性。新增或者刪除一個(gè)節(jié)點(diǎn),不至于導(dǎo)致遍歷一個(gè)鏈表從中間斷開。但是RCU并不保證一定能讀到新增的節(jié)點(diǎn)或者不讀到要被刪除的節(jié)點(diǎn)。

二、RCU鎖的工作原理

RCU(Read-Copy Update),顧名思義就是讀-拷貝修改,它是基于其原理命名的。對(duì)于被RCU保護(hù)的共享數(shù)據(jù)結(jié)構(gòu),讀者不需要獲得任何鎖就可以訪問它,但寫者在訪問它時(shí)首先拷貝一個(gè)副本,然后對(duì)副本進(jìn)行修改,最后使用一個(gè)回調(diào)(callback)機(jī)制在適當(dāng)?shù)臅r(shí)機(jī)把指向原來數(shù)據(jù)的指針重新指向新的被修改的數(shù)據(jù)。這個(gè)時(shí)機(jī)就是所有引用該數(shù)據(jù)的CPU都退出對(duì)共享數(shù)據(jù)的操作。

因此RCU實(shí)際上是一種改進(jìn)的rwlock,讀者幾乎沒有什么同步開銷,它不需要鎖,不使用原子指令,而且在除alpha的所有架構(gòu)上也不需要內(nèi)存柵(Memory Barrier),因此不會(huì)導(dǎo)致鎖競(jìng)爭(zhēng),內(nèi)存延遲以及流水線停滯。不需要鎖也使得使用更容易,因?yàn)樗梨i問題就不需要考慮了。寫者的同步開銷比較大,它需要延遲數(shù)據(jù)結(jié)構(gòu)的釋放,復(fù)制被修改的數(shù)據(jù)結(jié)構(gòu),它也必須使用某種鎖機(jī)制同步并行的其它寫者的修改操作。讀者必須提供一個(gè)信號(hào)給寫者以便寫者能夠確定數(shù)據(jù)可以被安全地釋放或修改的時(shí)機(jī)。

有一個(gè)專門的垃圾收集器來探測(cè)讀者的信號(hào),一旦所有的讀者都已經(jīng)發(fā)送信號(hào)告知它們都不在使用被RCU保護(hù)的數(shù)據(jù)結(jié)構(gòu),垃圾收集器就調(diào)用回調(diào)函數(shù)完成最后的數(shù)據(jù)釋放或修改操作。 RCU與rwlock的不同之處是:它既允許多個(gè)讀者同時(shí)訪問被保護(hù)的數(shù)據(jù),又允許多個(gè)讀者和多個(gè)寫者同時(shí)訪問被保護(hù)的數(shù)據(jù)(注意:是否可以有多個(gè)寫者并行訪問取決于寫者之間使用的同步機(jī)制),讀者沒有任何同步開銷,而寫者的同步開銷則取決于使用的寫者間同步機(jī)制。但RCU不能替代rwlock,因?yàn)槿绻麑懕容^多時(shí),對(duì)讀者的性能提高不能彌補(bǔ)寫者導(dǎo)致的損失。

讀者在訪問被RCU保護(hù)的共享數(shù)據(jù)期間不能被阻塞,這是RCU機(jī)制得以實(shí)現(xiàn)的一個(gè)基本前提,也就說當(dāng)讀者在引用被RCU保護(hù)的共享數(shù)據(jù)期間,讀者所在的CPU不能發(fā)生上下文切換,spinlock和rwlock都需要這樣的前提。寫者在訪問被RCU保護(hù)的共享數(shù)據(jù)時(shí)不需要和讀者競(jìng)爭(zhēng)任何鎖,只有在有多于一個(gè)寫者的情況下需要獲得某種鎖以與其他寫者同步。

寫者修改數(shù)據(jù)前首先拷貝一個(gè)被修改元素的副本,然后在副本上進(jìn)行修改,修改完畢后它向垃圾回收器注冊(cè)一個(gè)回調(diào)函數(shù)以便在適當(dāng)?shù)臅r(shí)機(jī)執(zhí)行真正的修改操作。等待適當(dāng)時(shí)機(jī)的這一時(shí)期稱為grace period,而CPU發(fā)生了上下文切換稱為經(jīng)歷一個(gè)quiescent state,grace period就是所有CPU都經(jīng)歷一次quiescent state所需要的等待的時(shí)間。垃圾收集器就是在grace period之后調(diào)用寫者注冊(cè)的回調(diào)函數(shù)來完成真正的數(shù)據(jù)修改或數(shù)據(jù)釋放操作的。

要想使用好RCU,就要知道RCU的實(shí)現(xiàn)原理。我們拿linux 2.6.21 kernel的實(shí)現(xiàn)開始分析,為什么選擇這個(gè)版本的實(shí)現(xiàn)呢?因?yàn)檫@個(gè)版本的實(shí)現(xiàn)相對(duì)較為單純,也比較簡(jiǎn)單。當(dāng)然之后內(nèi)核做了不少改進(jìn),如搶占RCU、可睡眠RCU、分層RCU。但是基本思想都是類似的。所以先從簡(jiǎn)單入手。

首先,我們提到的寫者在訪問它時(shí)首先拷貝一個(gè)副本,然后對(duì)副本進(jìn)行修改,最后使用一個(gè)回調(diào)(callback)機(jī)制在適當(dāng)?shù)臅r(shí)機(jī)把指向原來數(shù)據(jù)的指針重新指向新的被修改的數(shù)據(jù)。而這個(gè)“適當(dāng)?shù)臅r(shí)機(jī)”就是所有CPU經(jīng)歷了一次進(jìn)程切換(也就是一個(gè)grace period)。

為什么這么設(shè)計(jì)?

因?yàn)镽CU讀者的實(shí)現(xiàn)就是關(guān)搶占執(zhí)行讀取,讀完了當(dāng)然就可以進(jìn)程切換了,也就等于是寫者可以操作臨界區(qū)了。

那么就自然可以想到,內(nèi)核會(huì)設(shè)計(jì)兩個(gè)元素,來分別表示寫者被掛起的起始點(diǎn),以及每cpu變量,來表示該cpu是否經(jīng)過了一次進(jìn)程切換(quies state)。

就是說,當(dāng)寫者被掛起后要以下步驟:

  1. 重置每cpu變量,值為0。
  2. 當(dāng)某個(gè)cpu經(jīng)歷一次進(jìn)程切換后,就將自己的變量設(shè)為1。
  3. 當(dāng)所有的cpu變量都為1后,就可以喚醒寫者了。

下面我們來分別看linux里是如何完成這三步的:

我們從一個(gè)例子入手,這個(gè)例子來源于linux kernel文檔中的whatisRCU.txt。這個(gè)例子使用RCU的核心API來保護(hù)一個(gè)指向動(dòng)態(tài)分配內(nèi)存的全局指針。

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

DEFINE_SPINLOCK(foo_mutex);  

struct foo *gbl_foo;  

void foo_read (void)  
{  
    foo *fp = gbl_foo;  
    if ( fp != NULL )  
        dosomething(fp->a, fp->b , fp->c );  
}  

void foo_update( foo* new_fp )  
{  
    spin_lock(&foo_mutex);  
    foo *old_fp = gbl_foo;  
    gbl_foo = new_fp;  
    spin_unlock(&foo_mutex);  
    kfee(old_fp);  
}

如上代碼所示,RCU被用來保護(hù)全局指針struct foo *gbl_foo,foo_get_a()用來從RCU保護(hù)的結(jié)構(gòu)中取得gbl_foo的值。而foo_update_a()用來更新被RCU保護(hù)的gbl_foo的值(更新其a成員)。首先,我們思考一下,為什么要在foo_update_a()中使用自旋鎖foo_mutex呢?假設(shè)中間沒有使用自旋鎖.那foo_update_a()的代碼如下:

void foo_read(void)
{
 rcu_read_lock();
 foo *fp = gbl_foo;
 if ( fp != NULL )
 dosomething(fp->a,fp->b,fp->c);
 rcu_read_unlock();
}

void foo_update( foo* new_fp )
{
 spin_lock(&foo_mutex);
 foo *old_fp = gbl_foo;
 gbl_foo = new_fp;
 spin_unlock(&foo_mutex);
 synchronize_rcu();
 kfee(old_fp);
}

假設(shè)A進(jìn)程在上圖—-標(biāo)識(shí)處被B進(jìn)程搶點(diǎn).B進(jìn)程也執(zhí)行了goo_ipdate_a().等B執(zhí)行完后,再切換回A進(jìn)程.此時(shí),A進(jìn)程所持的old_fd實(shí)際上已經(jīng)被B進(jìn)程給釋放掉了.此后A進(jìn)程對(duì)old_fd的操作都是非法的。所以在此我們得到一個(gè)重要結(jié)論:RCU允許多個(gè)讀者同時(shí)訪問被保護(hù)的數(shù)據(jù),也允許多個(gè)讀者在有寫者時(shí)訪問被保護(hù)的數(shù)據(jù)(但是注意:是否可以有多個(gè)寫者并行訪問取決于寫者之間使用的同步機(jī)制)。

說明:本文中說的進(jìn)程不是用戶態(tài)的進(jìn)程,而是內(nèi)核的調(diào)用路徑,也可能是內(nèi)核線程或軟中斷等。

三、RCU的核心機(jī)制

寬限期的確定是 RCU 鎖實(shí)現(xiàn)的難點(diǎn)與核心。為了準(zhǔn)確判斷寬限期,RCU 機(jī)制有以下限制:

  • 禁止內(nèi)核搶占:在使用 RCU 鎖前,必須禁止內(nèi)核搶占。這意味著 CPU 不能隨意調(diào)度到其他線程,只能等待當(dāng)前線程離開臨界區(qū)(不再引用舊數(shù)據(jù))才能進(jìn)行調(diào)度。
  • 臨界區(qū)內(nèi)限制:在 RCU 鎖保護(hù)的臨界區(qū)中,不能使用可能觸發(fā)調(diào)度的函數(shù)。因?yàn)橐坏┌l(fā)生調(diào)度,就意味著當(dāng)前線程已經(jīng)退出了臨界區(qū),不再引用舊數(shù)據(jù)。

當(dāng)所有 CPU 都至少發(fā)生過一次調(diào)度時(shí),就可以確定沒有任何線程再引用舊數(shù)據(jù),此時(shí)寬限期結(jié)束,寫者便可以安全地釋放舊數(shù)據(jù)。

四、RCU鎖的使用場(chǎng)景

(1)文件系統(tǒng)

在 Linux 文件系統(tǒng)中,RCU 鎖有著廣泛的應(yīng)用。例如,當(dāng)多個(gè)進(jìn)程同時(shí)讀取文件系統(tǒng)的目錄結(jié)構(gòu)時(shí),讀操作可以并行進(jìn)行,而當(dāng)需要對(duì)目錄結(jié)構(gòu)進(jìn)行修改(如創(chuàng)建新文件、刪除文件等)時(shí),寫操作會(huì)在確保所有讀操作完成后進(jìn)行,保證了文件系統(tǒng)的一致性,同時(shí)提升了整體性能。

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

在網(wǎng)絡(luò)協(xié)議棧中,對(duì)于一些只讀的網(wǎng)絡(luò)配置信息(如路由表),讀操作頻繁,而寫操作相對(duì)較少。使用 RCU 鎖可以讓多個(gè)網(wǎng)絡(luò)數(shù)據(jù)包處理線程快速讀取路由信息,而當(dāng)網(wǎng)絡(luò)管理員需要修改路由配置時(shí),寫操作會(huì)在合適的時(shí)機(jī)進(jìn)行,避免了讀操作的阻塞。

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

Linux 內(nèi)核在管理進(jìn)程表、inode 表等數(shù)據(jù)結(jié)構(gòu)時(shí),也常常借助 RCU 鎖。以進(jìn)程表為例,眾多線程可能頻繁讀取進(jìn)程信息,而對(duì)進(jìn)程表的修改(如進(jìn)程創(chuàng)建、銷毀)相對(duì)較少。通過 RCU 鎖,讀操作可以高效進(jìn)行,寫操作也能在不影響讀性能的前提下有序完成。

五、RCU鎖的優(yōu)勢(shì)

(1)高性能讀操作

由于讀操作無需加鎖,在高并發(fā)讀的場(chǎng)景下,RCU 鎖能夠顯著提高系統(tǒng)的性能。相比傳統(tǒng)的鎖機(jī)制,讀操作的延遲大大降低,吞吐量顯著提升。

(2)減少鎖爭(zhēng)用

在多處理器環(huán)境下,鎖爭(zhēng)用是影響性能的重要因素。RCU 鎖減少了讀操作和寫操作之間的鎖爭(zhēng)用,使得系統(tǒng)能夠更好地利用多核處理器的性能。

(3)簡(jiǎn)化代碼設(shè)計(jì)

對(duì)于讀多寫少的場(chǎng)景,使用 RCU 鎖可以簡(jiǎn)化代碼的同步邏輯。讀端代碼無需復(fù)雜的鎖獲取和釋放操作,使代碼更加簡(jiǎn)潔明了,易于維護(hù)。

六、在Linux內(nèi)核中使用RCU鎖

在 Linux 內(nèi)核中,使用 RCU 鎖需要遵循特定的 API:

讀端 API:

  • rcu_read_lock():用于進(jìn)入RCU讀臨界區(qū),本質(zhì)上是禁止CPU搶占。
  • rcu_read_unlock():用于離開RCU讀臨界區(qū),開啟CPU搶占。

寫端 API:

  • synchronize_rcu():等待寬限期結(jié)束,確保所有已開始的 RCU 讀操作完成。
  • call_rcu():用于延遲執(zhí)行函數(shù),通常用于釋放數(shù)據(jù)結(jié)構(gòu)或?qū)ο?,在寬限期結(jié)束后執(zhí)行。
  • kfree_rcu():call_rcu()的特殊情況,專門用于釋放動(dòng)態(tài)分配的內(nèi)存。

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);
    }

    /* 離開 RCU 讀取臨界區(qū) */
    rcu_read_unlock();
}

示例中,我們使用 RCU 來保護(hù)鏈表的訪問。添加節(jié)點(diǎn)時(shí),我們不需要獲取鎖來保護(hù)共享資源。刪除節(jié)點(diǎn)時(shí),我們使用了 list_del_rcu 來刪除節(jié)點(diǎn),并使用 call_rcu 函數(shù)來安排釋放內(nèi)存的回調(diào)函數(shù)。在遍歷鏈表時(shí),我們使用了 rcu_read_lock 和 rcu_read_unlock 來進(jìn)入和離開 RCU 讀取臨界區(qū)。

責(zé)任編輯:武曉燕 來源: 深度Linux
相關(guān)推薦

2025-03-31 00:01:12

2023-11-22 13:18:02

Linux調(diào)度

2024-02-02 18:29:54

C++線程編程

2024-03-05 09:55:00

C++右值引用開發(fā)

2023-11-09 15:28:32

Spring開發(fā)

2024-07-25 11:53:53

2024-02-29 09:44:36

Java工具

2024-07-12 15:27:58

2009-10-29 09:41:01

Linux內(nèi)核DeviceMappe

2023-09-26 11:34:56

Python庫(kù)

2024-01-22 09:00:00

編程C++代碼

2024-08-13 09:39:13

2023-11-03 08:32:53

Flask高并發(fā)

2011-01-14 13:50:37

2009-09-28 10:09:09

Linux內(nèi)核Linux循環(huán)鏈表

2023-05-15 08:58:41

塊設(shè)備驅(qū)動(dòng)Linux

2025-04-28 02:22:00

2023-11-24 11:15:21

協(xié)程編程

2017-09-04 15:15:48

Linux內(nèi)核內(nèi)存屏障

2025-04-15 06:00:00

點(diǎn)贊
收藏

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