一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

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

3天內(nèi)不再提示

基于Linux內(nèi)核源碼的RCU實現(xiàn)方案

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

掃碼添加小助手

加入工程師交流群

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

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

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

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

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

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

寬限期

通過例子,方便理解這個內(nèi)容。以下例子修改于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的操作。假設(shè)以下場景。有兩個線程同時運行 foo_ read和foo_update的時候,當foo_ read執(zhí)行完賦值操作后,線程發(fā)生切換;此時另一個線程開始執(zhí)行foo_update并執(zhí)行完成。當foo_ read運行的進程切換回來后,運行dosomething 的時候,fp已經(jīng)被刪除,這將對系統(tǒng)造成危害。為了防止此類事件的發(fā)生,RCU里增加了一個新的概念叫寬限期(Grace period)。如下圖所示:

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

[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,這兩個函數(shù)用來標記一個RCU讀過程的開始和結(jié)束。其實作用就是幫助檢測寬限期是否結(jié)束。foo_update增加了一個函數(shù)synchronize_rcu(),調(diào)用該函數(shù)意味著一個寬限期的開始,而直到寬限期結(jié)束,該函數(shù)才會返回。我們再對比著圖看一看,線程1和2,在synchronize_rcu之前可能得到了舊的gbl_foo,也就是foo_update中的old_fp,如果不等它們運行結(jié)束,就調(diào)用kfee(old_fp),極有可能造成系統(tǒng)崩潰。而3,4,6在synchronize_rcu之后運行,此時它們已經(jīng)不可能得到old_fp,此次的kfee將不對它們產(chǎn)生影響。

寬限期是RCU實現(xiàn)中最復(fù)雜的部分,原因是在提高讀數(shù)據(jù)性能的同時,刪除數(shù)據(jù)的性能也不能太差。

訂閱——發(fā)布機制

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

[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行代碼之前執(zhí)行。但優(yōu)化后的代碼并不對執(zhí)行順序做出保證。在這種情形下,一個讀線程很可能讀到 new_fp,但new_fp的成員賦值還沒執(zhí)行完成。當讀線程執(zhí)行dosomething(fp->a, fp->b , fp->c ) 的 時候,就有不確定的參數(shù)傳入到dosomething,極有可能造成不期望的結(jié)果,甚至程序崩潰??梢酝ㄟ^優(yōu)化屏障來解決該問題,RCU機制對優(yōu)化屏障做了包裝,提供了專用的API來解決該問題。這時候,第十行不再是直接的指針賦值,而應(yīng)該改為 :

rcu_assign_pointer(gbl_foo,new_fp);

rcu_assign_pointer的實現(xiàn)比較簡單,如下:

[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)

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

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

[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行還沒執(zhí)行的時候就預(yù)先判斷運行,當他和foo_update同時運行的時候,可能導(dǎo)致傳入dosomething的一部分屬于舊的gbl_foo,而另外的屬于新的。這樣導(dǎo)致運行結(jié)果的錯誤。為了避免該類問題,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);

}

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

[cpp]view plaincopy

#definercu_dereference(p)({

typeof(p)_________p1=p;

smp_read_barrier_depends();

(_________p1);

})

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

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

數(shù)據(jù)讀取的完整性

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

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

我們再看一下刪除一個節(jié)點的例子:

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

小結(jié)

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

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

    關(guān)注

    87

    文章

    11511

    瀏覽量

    213847
  • 數(shù)據(jù)同步
    +關(guān)注

    關(guān)注

    0

    文章

    17

    瀏覽量

    8278
  • rcu
    rcu
    +關(guān)注

    關(guān)注

    0

    文章

    21

    瀏覽量

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

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點推薦

    如何配置和驗證Linux內(nèi)核參數(shù)

    Linux系統(tǒng)運維和性能優(yōu)化中,內(nèi)核參數(shù)(sysctl)的配置至關(guān)重要。合理的參數(shù)調(diào)整可以顯著提升網(wǎng)絡(luò)性能、系統(tǒng)穩(wěn)定性及資源利用率。然而,僅僅修改參數(shù)是不夠的,如何驗證這些參數(shù)是否生效同樣關(guān)鍵。
    的頭像 發(fā)表于 05-29 17:40 ?304次閱讀

    Linux內(nèi)核編譯失???移動硬盤和虛擬機的那些事兒

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

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

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

    基于OpenSBI的linux nommu實現(xiàn)

    Linux內(nèi)核6.10提供了對沒有mmu的riscv處理器工作在S模式下的內(nèi)核的支持,本文介紹基于OpenSBI的linuxnommu的實現(xiàn),供大家參考。1、OpenSBI介紹SBI
    的頭像 發(fā)表于 02-08 13:43 ?674次閱讀
    基于OpenSBI的<b class='flag-5'>linux</b> nommu<b class='flag-5'>實現(xiàn)</b>

    飛凌嵌入式ElfBoard ELF 1板卡-移植前準備之git管理內(nèi)核源碼

    我們前邊已經(jīng)介紹過Git工具,是一個非常實用的代碼管理工具。如果驗證編譯出的內(nèi)核能夠正常啟動,就可以將源碼用git工具管理起來??梢郧宄牧私?b class='flag-5'>源碼改動記錄。如果不小心把源碼改亂了還可以
    發(fā)表于 01-22 10:39

    騰訊云內(nèi)核團隊修復(fù)Linux關(guān)鍵Bug

    騰訊云操作系統(tǒng)(Tencent OS)內(nèi)核團隊近日在Linux社區(qū)取得了顯著成果。他們提交的兩項改進方案,成功解決了自2021年以來一直困擾眾多一線廠商,并在近期讓多個Linux頂級
    的頭像 發(fā)表于 12-31 10:58 ?664次閱讀

    嵌入式學(xué)習-飛凌嵌入式ElfBoard ELF 1板卡-Linux內(nèi)核移植之內(nèi)核簡介

    學(xué)到本章節(jié),大家應(yīng)該對Linux操作系統(tǒng)都有了一定的了解,但可能還不知道我們拿到手的內(nèi)核源碼都經(jīng)歷了什么。linux有一個龐大的開源社區(qū),每個人都可以向開源社區(qū)提交代碼。由于
    發(fā)表于 12-16 13:08

    飛凌嵌入式ElfBoard ELF 1板卡-內(nèi)核源代碼的目錄結(jié)構(gòu)和文件說明

    將:ELF 1開發(fā)板資料包\\02-Linux 源代碼\\02-0 出廠內(nèi)核和uboot源碼\\內(nèi)核源碼\\
    發(fā)表于 12-16 13:04

    飛凌嵌入式ElfBoard ELF 1板卡-Linux內(nèi)核移植之內(nèi)核簡介

    學(xué)到本章節(jié),大家應(yīng)該對Linux操作系統(tǒng)都有了一定的了解,但可能還不知道我們拿到手的內(nèi)核源碼都經(jīng)歷了什么。linux有一個龐大的開源社區(qū),每個人都可以向開源社區(qū)提交代碼。由于
    發(fā)表于 12-13 09:03

    deepin社區(qū)亮相第19屆中國Linux內(nèi)核開發(fā)者大會

    中國 Linux 內(nèi)核開發(fā)者大會,作為中國 Linux 內(nèi)核領(lǐng)域最具影響力的峰會之一,一直以來都備受矚目。
    的頭像 發(fā)表于 10-29 16:35 ?932次閱讀

    linux內(nèi)核中通用HID觸摸驅(qū)動

    linux內(nèi)核中,為HID觸摸面板實現(xiàn)了一個通用的驅(qū)動程序,位于/drivers/hid/hid-multitouch.c文件中。hid觸摸驅(qū)動是以struct hid_driver實現(xiàn)
    的頭像 發(fā)表于 10-29 10:55 ?2340次閱讀
    <b class='flag-5'>linux</b><b class='flag-5'>內(nèi)核</b>中通用HID觸摸驅(qū)動

    詳解linux內(nèi)核的uevent機制

    linux內(nèi)核中,uevent機制是一種內(nèi)核和用戶空間通信的機制,用于通知用戶空間應(yīng)用程序各種硬件更改或其他事件,比如插入或移除硬件設(shè)備(如USB驅(qū)動器或網(wǎng)絡(luò)接口)。uevent表示“用戶空間
    的頭像 發(fā)表于 09-29 17:01 ?1911次閱讀

    linux驅(qū)動程序如何加載進內(nèi)核

    Linux系統(tǒng)中,驅(qū)動程序是內(nèi)核與硬件設(shè)備之間的橋梁。它們允許內(nèi)核與硬件設(shè)備進行通信,從而實現(xiàn)對硬件設(shè)備的控制和管理。 驅(qū)動程序的編寫 驅(qū)動程序的編寫是
    的頭像 發(fā)表于 08-30 15:02 ?1107次閱讀

    Linux內(nèi)核測試技術(shù)

    。內(nèi)核測試技術(shù)是實現(xiàn)這一目標的關(guān)鍵手段。本文將詳細介紹 Linux 內(nèi)核測試的各種技術(shù),包括單元測試、集成測試、功能測試和性能測試等,并討論不同測試方法的優(yōu)缺點及其適用場景。
    的頭像 發(fā)表于 08-13 13:42 ?1317次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>測試技術(shù)

    Linux內(nèi)核中的頁面分配機制

    Linux內(nèi)核中是如何分配出頁面的,如果我們站在CPU的角度去看這個問題,CPU能分配出來的頁面是以物理頁面為單位的。也就是我們計算機中常講的分頁機制。本文就看下Linux內(nèi)核是如何管
    的頭像 發(fā)表于 08-07 15:51 ?642次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內(nèi)核</b>中的頁面分配機制