国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于Linux內核源碼的RCU實現方案

454398 ? 來源: Chinaunix ? 作者:hiyachen ? 2020-09-25 15:10 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

RCU(Read-Copy Update)是數據同步的一種方式,在當前的Linux內核中發揮著重要的作用。RCU主要針對的數據對象是鏈表,目的是提高遍歷讀取數據的效率,為了達到目的使用RCU機制讀取數據的時候不對鏈表進行耗時的加鎖操作。這樣在同一時間可以有多個線程同時讀取該鏈表,并且允許一個線程對鏈表進行修改(修改的時候,需要加鎖)。RCU適用于需要頻繁的讀取數據,而相應修改數據并不多的情景,例如在文件系統中,經常需要查找定位目錄,而對目錄的修改相對來說并不多,這就是RCU發揮作用的最佳場景。

Linux內核源碼當中,關于RCU的文檔比較齊全,你可以在 /Documentation/RCU/ 目錄下找到這些文件。Paul E. McKenney 是內核中RCU源碼的主要實現者,他也寫了很多RCU方面的文章。他把這些文章和一些關于RCU的論文的鏈接整理到了一起。http://www2.rdrop.com/users/paulmck/RCU/

在RCU的實現過程中,我們主要解決以下問題:

1,在讀取過程中,另外一個線程刪除了一個節點。刪除線程可以把這個節點從鏈表中移除,但它不能直接銷毀這個節點,必須等到所有的讀取線程讀取完成以后,才進行銷毀操作。RCU中把這個過程稱為寬限期(Grace period)。

2,在讀取過程中,另外一個線程插入了一個新節點,而讀線程讀到了這個節點,那么需要保證讀到的這個節點是完整的。這里涉及到了發布-訂閱機制(Publish-Subscribe Mechanism)。

3, 保證讀取鏈表的完整性。新增或者刪除一個節點,不至于導致遍歷一個鏈表從中間斷開。但是RCU并不保證一定能讀到新增的節點或者不讀到要被刪除的節點。

寬限期

通過例子,方便理解這個內容。以下例子修改于Paul的文章。

[cpp]view plaincopy

structfoo{

inta;

charb;

longc;

};

DEFINE_SPINLOCK(foo_mutex);

structfoo*gbl_foo;

voidfoo_read(void)

{

foo*fp=gbl_foo;

if(fp!=NULL)

dosomething(fp->a,fp->b,fp->c);

}

voidfoo_update(foo*new_fp)

{

spin_lock(&foo_mutex);

foo*old_fp=gbl_foo;

gbl_foo=new_fp;

spin_unlock(&foo_mutex);

kfee(old_fp);

}

如上的程序,是針對于全局變量gbl_foo的操作。假設以下場景。有兩個線程同時運行 foo_ read和foo_update的時候,當foo_ read執行完賦值操作后,線程發生切換;此時另一個線程開始執行foo_update并執行完成。當foo_ read運行的進程切換回來后,運行dosomething 的時候,fp已經被刪除,這將對系統造成危害。為了防止此類事件的發生,RCU里增加了一個新的概念叫寬限期(Grace period)。如下圖所示:

圖中每行代表一個線程,最下面的一行是刪除線程,當它執行完刪除操作后,線程進入了寬限期。寬限期的意義是,在一個刪除動作發生后,它必須等待所有在寬限期開始前已經開始的讀線程結束,才可以進行銷毀操作。這樣做的原因是這些線程有可能讀到了要刪除的元素。圖中的寬限期必須等待1和2結束;而讀線程5在寬限期開始前已經結束,不需要考慮;而3,4,6也不需要考慮,因為在寬限期結束后開始后的線程不可能讀到已刪除的元素。為此RCU機制提供了相應的API來實現這個功能。

[cpp]view plaincopy

voidfoo_read(void)

{

rcu_read_lock();

foo*fp=gbl_foo;

if(fp!=NULL)

dosomething(fp->a,fp->b,fp->c);

rcu_read_unlock();

}

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

}

其中foo_read中增加了rcu_read_lock和rcu_read_unlock,這兩個函數用來標記一個RCU讀過程的開始和結束。其實作用就是幫助檢測寬限期是否結束。foo_update增加了一個函數synchronize_rcu(),調用該函數意味著一個寬限期的開始,而直到寬限期結束,該函數才會返回。我們再對比著圖看一看,線程1和2,在synchronize_rcu之前可能得到了舊的gbl_foo,也就是foo_update中的old_fp,如果不等它們運行結束,就調用kfee(old_fp),極有可能造成系統崩潰。而3,4,6在synchronize_rcu之后運行,此時它們已經不可能得到old_fp,此次的kfee將不對它們產生影響。

寬限期是RCU實現中最復雜的部分,原因是在提高讀數據性能的同時,刪除數據的性能也不能太差。

訂閱——發布機制

當前使用的編譯器大多會對代碼做一定程度的優化,CPU也會對執行指令做一些優化調整,目的是提高代碼的執行效率,但這樣的優化,有時候會帶來不期望的結果。如例:

[cpp]view plaincopy

voidfoo_update(foo*new_fp)

{

spin_lock(&foo_mutex);

foo*old_fp=gbl_foo;

new_fp->a=1;

new_fp->b=‘b’;

new_fp->c=100;

gbl_foo=new_fp;

spin_unlock(&foo_mutex);

synchronize_rcu();

kfee(old_fp);

}

這段代碼中,我們期望的是6,7,8行的代碼在第10行代碼之前執行。但優化后的代碼并不對執行順序做出保證。在這種情形下,一個讀線程很可能讀到 new_fp,但new_fp的成員賦值還沒執行完成。當讀線程執行dosomething(fp->a, fp->b , fp->c ) 的 時候,就有不確定的參數傳入到dosomething,極有可能造成不期望的結果,甚至程序崩潰。可以通過優化屏障來解決該問題,RCU機制對優化屏障做了包裝,提供了專用的API來解決該問題。這時候,第十行不再是直接的指針賦值,而應該改為 :

rcu_assign_pointer(gbl_foo,new_fp);

rcu_assign_pointer的實現比較簡單,如下:

[cpp]view plaincopy

#definercu_assign_pointer(p,v)

__rcu_assign_pointer((p),(v),__rcu)

#define__rcu_assign_pointer(p,v,space)

do{

smp_wmb();

(p)=(typeof(*v)__forcespace*)(v);

}while(0)

我們可以看到它的實現只是在賦值之前加了優化屏障 smp_wmb來確保代碼的執行順序。另外就是宏中用到的__rcu,只是作為編譯過程的檢測條件來使用的。

在DEC Alpha CPU機器上還有一種更強悍的優化,如下所示:

[cpp]view plaincopy

voidfoo_read(void)

{

rcu_read_lock();

foo*fp=gbl_foo;

if(fp!=NULL)

dosomething(fp->a,fp->b,fp->c);

rcu_read_unlock();

}

第六行的fp->a,fp->b,fp->c會在第3行還沒執行的時候就預先判斷運行,當他和foo_update同時運行的時候,可能導致傳入dosomething的一部分屬于舊的gbl_foo,而另外的屬于新的。這樣導致運行結果的錯誤。為了避免該類問題,RCU還是提供了宏來解決該問題:

[cpp]view plaincopy

#definercu_dereference(p)rcu_dereference_check(p,0)

#definercu_dereference_check(p,c)

__rcu_dereference_check((p),rcu_read_lock_held()||(c),__rcu)

#define__rcu_dereference_check(p,c,space)

({

typeof(*p)*_________p1=(typeof(*p)*__force)ACCESS_ONCE(p);

rcu_lockdep_assert(c,"suspiciousrcu_dereference_check()"

"usage");

rcu_dereference_sparse(p,space);

smp_read_barrier_depends();

((typeof(*p)__force__kernel*)(_________p1));

})

staticinlineintrcu_read_lock_held(void)

{

if(!debug_lockdep_rcu_enabled())

return1;

if(rcu_is_cpu_idle())

return0;

if(!rcu_lockdep_current_cpu_online())

return0;

returnlock_is_held(&rcu_lock_map);

}

這段代碼中加入了調試信息,去除調試信息,可以是以下的形式(其實這也是舊版本中的代碼):

[cpp]view plaincopy

#definercu_dereference(p)({

typeof(p)_________p1=p;

smp_read_barrier_depends();

(_________p1);

})

在賦值后加入優化屏障smp_read_barrier_depends()。

我們之前的第四行代碼改為foo *fp = rcu_dereference(gbl_foo);,就可以防止上述問題。

數據讀取的完整性

還是通過例子來說明這個問題:

如圖我們在原list中加入一個節點new到A之前,所要做的第一步是將new的指針指向A節點,第二步才是將Head的指針指向new。這樣做的目的是當插入操作完成第一步的時候,對于鏈表的讀取并不產生影響,而執行完第二步的時候,讀線程如果讀到new節點,也可以繼續遍歷鏈表。如果把這個過程反過來,第一步head指向new,而這時一個線程讀到new,由于new的指針指向的是Null,這樣將導致讀線程無法讀取到A,B等后續節點。從以上過程中,可以看出RCU并不保證讀線程讀取到new節點。如果該節點對程序產生影響,那么就需要外部調用做相應的調整。如在文件系統中,通過RCU定位后,如果查找不到相應節點,就會進行其它形式的查找,相關內容等分析到文件系統的時候再進行敘述。

我們再看一下刪除一個節點的例子:

如圖我們希望刪除B,這時候要做的就是將A的指針指向C,保持B的指針,然后刪除程序將進入寬限期檢測。由于B的內容并沒有變更,讀到B的線程仍然可以繼續讀取B的后續節點。B不能立即銷毀,它必須等待寬限期結束后,才能進行相應銷毀操作。由于A的節點已經指向了C,當寬限期開始之后所有的后續讀操作通過A找到的是C,而B已經隱藏了,后續的讀線程都不會讀到它。這樣就確保寬限期過后,刪除B并不對系統造成影響。

小結

RCU的原理并不復雜,應用也很簡單。但代碼的實現確并不是那么容易,難點都集中在了寬限期的檢測上,后續分析源代碼的時候,我們可以看到一些極富技巧的實現方式。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    88

    文章

    11758

    瀏覽量

    219009
  • 數據同步
    +關注

    關注

    0

    文章

    17

    瀏覽量

    8370
  • rcu
    rcu
    +關注

    關注

    0

    文章

    21

    瀏覽量

    5749
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    送4本!206 張手繪圖+源碼+視頻!Linux 老兵嘔心之作,讓小白也能吃透底層邏輯

    作為開發者,你是否也有過這樣的困擾?想學Linux網絡編程,卻被枯燥的理論、復雜的內核源碼勸退;照搬別人的代碼能跑通項目,但遇到問題不知如何排查,更談不上架構優化;市面上的教材要么只講API用法,要么滿
    的頭像 發表于 02-05 08:08 ?120次閱讀
    送4本!206 張手繪圖+<b class='flag-5'>源碼</b>+視頻!<b class='flag-5'>Linux</b> 老兵嘔心之作,讓小白也能吃透底層邏輯

    Linux內核的“心跳”:jiffies如何為系統計時?

    Linux 內核的世界里,有一個默默工作的 "計時器"——jiffies。它不像我們手機上的時鐘那樣顯示年月日,卻掌控著內核中絕大多數時間相關的操作:從進程調度到設備驅動的定時檢查,都離不開它的身影。
    的頭像 發表于 02-04 16:27 ?816次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>的“心跳”:jiffies如何為系統計時?

    深入RK3588內核:rockchip_linux_defconfig的作用與調試價值

    在 RK3588 芯片的 Linux 開發中,有一個文件始終是開發者繞不開的核心 ——kernel/arch/arm64/configs/rockchip_linux_defconfig。無論是首次
    的頭像 發表于 02-03 15:56 ?1156次閱讀
    深入RK3588<b class='flag-5'>內核</b>:rockchip_<b class='flag-5'>linux</b>_defconfig的作用與調試價值

    Linux系統內核參數調優實戰指南

    Linux 內核參數調優是系統性能優化的核心環節。隨著云原生架構的普及和硬件性能的飛速提升,默認的內核參數配置往往無法充分發揮系統潛力。在高并發 Web 服務、大數據處理、容器化部署等場景下,合理的
    的頭像 發表于 01-28 14:27 ?426次閱讀

    【「Linux 設備驅動開發(第 2 版)」閱讀體驗】Linux內核開發基礎

    ()、msleep_interruptible()這些API用于簡單睡眠 內核延遲或忙等待 可以調用ndelay()、udelay()、mdelay()這些API實現 Linux內核
    發表于 01-12 22:45

    【「Linux 設備驅動開發(第 2 版)」閱讀體驗】+讀內核處理的核心輔助函數

    “處理內核的核心輔助函數”進行學習。 第3章又是以5個主題展開討論學習,①、Linux內核加鎖機制和共享資源;②、處理內核等待、睡眠和延遲機制;③、深入理解
    發表于 01-10 22:08

    深入Linux內核:進程調度的核心邏輯與實現細節

    ,背后都離不開內核調度算法的精準操控。今天,我們就從優先級、調度算法、時間片分配到底層實現,全方位拆解Linux內核進程調度的核心邏輯。 一、進程調度的“身份標識”:優先級與分類 要理
    的頭像 發表于 12-24 07:05 ?4299次閱讀
    深入<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>:進程調度的核心邏輯與<b class='flag-5'>實現</b>細節

    基于 DR1M90 的 Linux-RT 內核開發:從編譯配置到 GPIO / 按鍵應用實現(1)

    本手冊由創龍科技研發,針對 DR1M90,詳述 Linux-RT 實時內核開發:含實時性測試(LinuxLinux-RT 對比、CPU 空載 / 滿負荷 / 隔離狀態測試)、
    的頭像 發表于 12-02 10:38 ?1175次閱讀
    基于 DR1M90 的 <b class='flag-5'>Linux</b>-RT <b class='flag-5'>內核</b>開發:從編譯配置到 GPIO / 按鍵應用<b class='flag-5'>實現</b>(1)

    如何將 GPIO PWM 和 GPIO Capture 驅動程序導入 Linux 內核實現 PWM 輸出并檢測引腳的變化狀態?

    如何將 GPIO PWM 和 GPIO Capture 驅動程序導入 Linux 內核實現 PWM 輸出并檢測引腳的變化狀態
    發表于 08-20 08:20

    Linux內核參數調優方案

    在高并發微服務環境中,網絡性能往往成為K8s集群的瓶頸。本文將深入探討如何通過精細化的Linux內核參數調優,讓你的K8s節點網絡性能提升30%以上。
    的頭像 發表于 08-06 17:50 ?947次閱讀

    迅為RK3568開發板OpeHarmony學習開發手冊1.1-內核移植優化

    ;copy 到 out 目錄-->打新補丁 那么優化思路是什么呢? out/kernel/src_tmp/linux-5.10/ linux-5.10/ 下的內核源碼是已經打過原
    發表于 07-26 10:37

    如何配置和驗證Linux內核參數

    Linux系統運維和性能優化中,內核參數(sysctl)的配置至關重要。合理的參數調整可以顯著提升網絡性能、系統穩定性及資源利用率。然而,僅僅修改參數是不夠的,如何驗證這些參數是否生效同樣關鍵。
    的頭像 發表于 05-29 17:40 ?1150次閱讀

    迅為RK3568開發板內核模塊實現-編寫 Makefile

    ;lt;文件>:將指定的文件(需要是以.o 結尾)設為編譯時以模塊形式編譯 第 4 行是設備樹內核源碼路徑,請大家根據實際內核路徑進行修改。 第 5 行是獲取當前目錄的變量 第 7 行
    發表于 04-24 13:36

    Linux內核編譯失敗?移動硬盤和虛擬機的那些事兒

    Linux內核卻失敗了,這是咋回事?FAT和NTFS文件系統不能支持軟鏈接,在這寫格式的磁盤里編譯內核會失敗,同樣也不能在這樣的磁盤里解壓內核源碼
    的頭像 發表于 04-11 11:36 ?992次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>編譯失敗?移動硬盤和虛擬機的那些事兒

    樹莓派4 性能大比拼:標準Linux與實時Linux 4.19內核的延遲測試

    引言本文是對我之前關于RaspberryPi3同一主題的帖子的更新。與之前的帖子一樣,我使用的是隨Raspbian鏡像提供的標準內核,以及應用了RT補丁的相似內核版本。對于實時版,我
    的頭像 發表于 03-25 09:39 ?809次閱讀
    樹莓派4 性能大比拼:標準<b class='flag-5'>Linux</b>與實時<b class='flag-5'>Linux</b> 4.19<b class='flag-5'>內核</b>的延遲測試