日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux kernel 同步机制(下篇)

發布時間:2023/12/20 linux 53 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux kernel 同步机制(下篇) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

之前的文章

Linux kernel同步機制

在上一部分,我們討論了最基本常見的幾類同步機制,這一部分我們將討論相對復雜的幾種同步機制,尤其是讀寫信號量和RCU,在操作系統內核中有相當廣泛的應用。

  • 讀寫信號量(rw_semaphore)

  • BKL(Big Kernel Lock,只包含在2.4內核中,不講)

  • Rwlock

  • brlock(只包含在2.4內核中,不講)

  • RCU(只包含在2.6內核及以后的版本中)

一、讀寫信號量(RW_Semaphore)

讀寫信號量與信號量有相似也有不同,它是如下一種同步機制:讀寫信號量將訪問者分為讀者或者寫者,讀者在持有讀寫信號量期間只能對該信號量保護的共享資源進行讀訪問,而只要一個任務需要寫,它就被歸類為寫者,其進行訪問之前必先獲得寫者身份,在其不需寫訪問時可降級為讀者。讀寫信號量可同時擁有不受限的讀者數,寫者是排他性的,獨占性的,而讀者不排他。若讀寫信號量未被寫者持有或者等待,讀者就可以獲得讀寫信號量,否則必須等待直到寫者釋放讀寫信號量為止;若讀寫信號量沒有被讀者或寫者持有,也沒用寫者等待,寫者可以獲得該讀寫信號量,否則等待至信號量全部釋放(沒有其他訪問者)為止。

Structure Definition

若從上述結構定義看,最關鍵的前三個字段與mutex、信號量十分相似不再贅述,后面的OSQ字段在Mutex中提起過。由于內核有關讀寫信號量的實現有兩種,取決于CONFIG_RWSEM_GENERIC_SPINLOCK的配置,但是一般默認該配置是關的,因此選用默認版本的實現進行解讀。讀寫信號量同mutex一樣,在最近的改進中均引入了OSQ lock機制實現自旋等待。

讀寫信號量與信號量之間的關系

讀寫信號量可能會引起進程阻塞,但是它允許N個讀執行單元同時訪問共享資源,而最多只允許有一個寫執行單元訪問共享資源;因此,讀寫信號量是一種相對放寬條件的、粒度稍大于信號量的互斥機制。信號量不允許任何操作之間有并發,即:讀操作與讀操作之間、讀操作與寫操作之間、寫操作與寫操作之間,都不允許并發;而讀寫信號量則只允許讀操作與讀操作之間的并發,但不允許讀操作與寫操作之間的并發,也不允許寫操作與寫操作之間的并發。因此讀寫信號量比較適合讀多寫少的情況,可良好地利用讀者并發的特性。

Count 字段在讀寫信號量的表示含義

讀寫信號量中的count字段并不如信號量一般表示可用資源數量,而是標記了當前的訪問情況,我們取32位的情況分析,默認是取32位配置。

先觀察如下宏常量:

然后我們再考慮count,我們發現均是上述宏組合的結果,可以歸類為以下幾種情況:

所以可見count可以標記并區分許多訪問情況, 尤其是當存在寫者或阻塞時,其對應的有符號數(atomic_long_t)均為負數,可以作為判斷的標記。

在傳統的讀寫信號量中,會直接進阻塞,因此只有等待隊列非空還是為空的問題,但是在最近的改進中存在自旋等待的問題,因此使得在鎖的獲取中可能出現自旋狀態的寫者偷出鎖的情況。

__down_read & __up_read

根據count字段的含義,count + 1小于0說明原本存在寫者或者等待隊列非空,因此不能獲得鎖,rwsem_down_read_failed調用

一個讀者釋放后count - 1小于-1說明等待隊列非空,因此還需喚醒等待的寫者

Rwsem_down_read不能直接獲取時調用,首先判斷等待隊列是否為空,為空則字段置為非空,并將count回退之前讀的嘗試,將當前task壓入等待隊列,如果當前沒有人持有或正在獲取鎖鎖,則喚醒等待隊列的前面的進程,同時將喚醒進程的waiter.task置NULL,在調度中若發現自己的waiter.task為NULL,說明輪到本進程運行,置為TASK_RUNNING

down_write & up_write

一個寫者獲取鎖后,如果返回的count不是0xffff0001,那么寫者獲取信號量失敗

Rwsem_down_write_failed的基本邏輯與read相似,回退先前count的變化,對waitlist的處理,等待獲取鎖,有興趣可以自己閱讀源碼。

一個寫者釋放鎖后,如果count返回小于0,說明等待非空,將其喚醒。

RW_Semaphore ?API

二、讀寫鎖(rw_lock)

讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對于自旋鎖而言,能提高并發性,因為在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數為實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。

在讀寫鎖保持期間也是搶占失效的。如果讀寫鎖當前沒有讀者,也沒有寫者,那么寫者可以立刻獲得讀寫鎖,否則它必須自旋在那里,直到沒有任何寫者或讀者。如果讀寫鎖沒有寫者,那么讀者可以立即獲得該讀寫鎖,否則讀者必須自旋在那里,直到寫者釋放該讀寫鎖。

Structure Definition

從結構上看,讀寫鎖與自旋鎖基本相似,實際上二者的實現也十分相似,二者的關系可以類比讀寫信號量與信號量的關系。

arch_read_lock & arch_read_unlock

Read_lock實現上判斷lock+1是否為負,為負說明有寫者持有鎖(0x80000000),此時調用wfe進入一小段自旋狀態后再度執行;若非負,則將lock+1更新至lock中。

對應read_lock,read_unlock僅僅需要將lock -1 更新至lock。

arch_write_lock & arch_write_unlock

write_lock 在嘗試獲得鎖時,檢查lock是否為0,不為0則說明有讀者或者寫者持有鎖,此時wfe進入一小段等待直到lock為0,若lock為0則賦值lock獲得鎖。

Write_unlock只需將lock置零即可。

從這里可以看出,讀寫鎖的實現上以及功能上,相當于針對自旋鎖對于讀多寫少的場景提高并發度,設計原理與讀寫信號量十分類似。

RW_Lock API

三、順序鎖(seqlock)

順序鎖是對讀寫鎖的一種優化:讀者絕不會被寫者阻塞,也就說,讀者可以在寫者對被順序鎖保護的共享資源進行寫操作時仍然可以繼續讀,不必等待寫者完成寫操作,寫者也不需要等待所有讀者完成讀操作才去進行寫操作。但是,寫者與寫者之間仍然是互斥的。寫操作的優先級大于讀操作。

順序鎖有一個限制,它必須要求被保護的共享資源不含有指針,因為寫者可能使得指針失效,但讀者如果正要訪問該指針,將導致OOPs。如果讀者在讀操作期間,寫者已經發生了寫操作,那么,讀者必須重新讀取數據,以便確保得到的數據是完整的。順序鎖適用于讀多寫少的情況。

這種鎖對于讀寫同時進行的概率比較小的情況,性能是非常好的,而且它允許讀寫同時進行,更大地提高了并發性。順序鎖的一個典型的應用在于時鐘系統。

Structure Definition

從結構上看,也是依賴于自旋鎖的,seqcount用于同步寫者訪問的順序以更新讀者訪問,自旋鎖的作用在于實現寫操作之間的互斥,讀者訪問不受限制。

write_seqlock & write_sequnlock

順序鎖對寫操作之間必須互斥,實現上調用spin_lock進行互斥,另外對seqcount操作以同步讀者的訪問。

seqcount的計數符合以下規則:進入臨界區時加一,離開臨界區時也加一

read_seqretry & read_seqbegin

read_seqcount_begin返回當前seqlock的seqcount, 在讀完后,需調用read_seqretry查看讀者讀完后的seqcount是否與讀之前一致,一致則結束,不一致則說明有寫操作正在或已經執行,需要重新讀一次以更新數據。另外read_seqbegin返回的是lock.seqcount/2,實際上是寫操作發生的次數。

seqlock API

其他_irqsave,_irq,_bh版本均是與其他鎖類似的。

四、RCU(Read-Copy Update)

RCU是讀寫鎖的高性能版本,既允許多個讀者同時訪問被保護的數據,又允許多個讀者和多個寫者同時訪問被保護的數據(注意:是否可以有多個寫者并行訪問取決于寫者之間使用的同步機制),讀者沒有任何同步開銷,而寫者的同步開銷則取決于使用的寫者間同步機制。

對于被RCU保護的共享數據結構,讀者不需要獲得任何鎖就可以訪問它,但寫者在訪問它時首先拷貝一個副本,然后對副本進行修改,最后使用一個回調(callback)機制在適當的時機(所有引用該數據的CPU都退出對共享數據的操作時)把指向原來數據的指針重新指向新的被修改的數據。有一個專門的垃圾收集器探測讀者的信號,一旦所有讀者都已發送信號告知它們不在使用被RCU保護的數據結構,垃圾收集器就調用回調函數完成最后的數據釋放或修改操作。

RCU不能替代讀寫鎖,當寫比較多時,對讀者的性能提高不能彌補寫者導致的損失,但是大多數情況下RCU的表現高于讀寫鎖。

RCU Basic API

RCU 臨界區管理

之前的同步機制中,均是利用鎖或原子操作實現的,一個鎖管理一個臨界區,并通過加鎖解鎖控制進程進入或者離開臨界區。一個程序中可以存在若干的臨界區,因此可以對應存在若干把鎖分別管理,這是之前所有鎖機制的基礎。

然而RCU并不基于鎖機制實現,RCU字段是耦合在進程描述符和CPU變量中的,是一種與系統強耦合的同步機制,RCU負責管理進程內所有的臨界區,進程通過調用rcu_read_lock與rcu_read_unlock標記讀者臨界區,通過rcu_assign_pointer、list_add_rcu將數據納入保護區,當寫者copy出新數據時在讀者全部退出臨界區后,將新數據指針更新,舊數據將在垃圾收集器的檢查中被釋放,但存在延遲。

RCU 限制條件

  • RCU只保護動態分配并通過指針引用的數據結構

  • 在被RCU保護的臨界區中,任何內核路徑都不能睡眠(經典實現中)

RCU callback的實現

rcu_head 是RCU回調函數的關鍵結構。此外,回調機制主要涉及兩個基本函數__call_rcu(用于注冊), __rcu_reclaim(用于調用)。

__call_rcu僅僅將func注冊進rcu_head, 便立刻返回。該func一般用于回收釋放copy后遺留的舊數據垃圾,但是RCU采用了延時執行防止讀者還在讀舊數據時回收數據造成崩潰。

Rcp主要用于全局控制,而rcu的回調函數以鏈式組織,next用于遍歷鏈。

__rcu_reclaim用于回收rcu先前分配的舊數據,回調函數也是回收操作的一種。

實際上,synchronize_rcu在等待讀者全數退出臨界區時,也通過call_rcu注冊了回調函數。

相對麻煩的是回收階段,RCU通過一個垃圾收集器檢查需要回收的舊數據并調用回調函數釋放,準確的說調用rcu_check_callbacks檢查是否有需要執行的回調函數,而后調用rcu_process_callbacks執行必要的rcu 回調函數。

那么問題來了,誰去調用rcu_check_callbacks函數呢?時鐘系統,每當時間片消耗完或者出現時鐘中斷,時鐘系統都將調用rcu_check_callbacks進行及時檢查處理,避免過量的舊數據垃圾造成內存浪費。

RCU read

rcu_read_lock與rcu_read_unlock的經典實現是不可搶占的,從代碼看,這兩個函數僅僅用于開關搶占。

RCU read之所以禁止搶占,主要是由于寫者必須等待讀者完全執行完退出臨界區方能修改數據指針。一旦讀者被搶占,那么其退出臨界區的過程將會阻塞,進而阻塞寫者,這對性能是一種不小的開銷。但是現在的linux 內核版本中提供了可搶占的版本,只是對搶占深度做了把控。

RCU Synchronize

可是RCU是如何獲知所有讀者已經離開臨界區?RCU read實現中并沒有設置字段標記進出臨界區,RCU是通過什么判斷的呢?既然RCU read過程不可搶占,那么換言之,若所有 CPU 都已經過一次上下文切換,則所有前置 reader 的臨界區必定全部退出。

我們主要分析以下兩種:

  • rcu_check_callbacks

  • synchronize_rcu

user其實在調用中真實的傳入是user_tick,值為1指用戶時間,0指系統時間,由于RCU必須在內核態執行,因此user為1說明必然不處于lock~unlock的時段,很有可能已經發生過rcu_read,因此發送一個RCU_SOFTIRQ軟中斷,調用rcu_process_callbacks。

synchronize_rcu的核心是wait_rcu_gp函數。

該函數通過注冊一個func為wakeme_after_rcu的rcu_head并等待該rcu_head完成回調來判斷之前的rcu讀者已經全部退出。

由于該rcu_head注冊較晚,當且僅當當前的讀者都已退出臨界區,該rcu_head的回調才可能執行,因此當該func回調完成,就必然已經滿足同步條件。最后銷毀該多余的head內存。

如下圖:

RCU Example

Input.c 中的使用為例。

Grab_device即掛載設備,注意這里的rcu_assign_pointer用于將dev->grab加入rcu保護的共享區,handle(處理函數)是其值。在這里完成了向rcu注冊數據的過程。

Input_pass_value處理所有的輸入事件,首先我們read_lock標記進入臨界區不可搶占,讀出dev->grab并以處理輸入事件,最后read_unlock退出臨界區。

Release device與掛載相對,釋放過程即將原本的handler變為NULL, 最后調用synchronize_rcu通知所有輸入事件handler移除

五、同步機制之間的比較

? ? 推薦閱讀:

? ??專輯|Linux文章匯總

? ??專輯|程序人生

? ??專輯|C語言

嵌入式Linux

微信掃描二維碼,關注我的公眾號?

總結

以上是生活随笔為你收集整理的Linux kernel 同步机制(下篇)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。