核心同步之Seq鎖和屏障

2021-04-28 01:59:43 字數 3378 閱讀 5977

5. 完成變數

如果在核心中乙個任務需要發出訊號通知另乙個任務發生了某個特定事件,利用完成變數(completion variable)是兩個任務得以同步的簡單方法。

完成變數由結構completion表示,在中。靜態初始化:

declare_copletion(my_comp);

執行時動態初始化:

int_completion(my_comp);

在乙個指定的完成變數上,需要等待的任務呼叫wait_for_completion()來等待特定事件。當特定事件發生後,產生事件的任務呼叫completion()來傳送訊號喚醒正在等待的任務。使用完成變數的例子可以參考kernel/sched.c和/kernel/fork.c檔案。

6. seq鎖

seq鎖是在2.6核心中才引入的一種新型鎖。實現這種鎖主要依靠乙個序列計數器。當有疑義的資料被寫入時,會得到乙個鎖,並且序列值會增加,在讀取資料之前和之後,序列號都被讀取。如果讀取的序列號值相同,說明在讀操作進行的過程中沒有被寫操作打斷過。如果讀取的值是偶數,則表明寫操作沒有發生(因為鎖的初始值是0,所以寫鎖會使值成奇數,釋放的時候變成偶數)。介面在中定義。

示例如下:

seqlock_t my_seq=seqlock_unlocked;

write_seqlock(&my_seq);

/*寫入資料*/

write_sequnlock(&my_seq);

這和普通的自旋鎖類似,但是不同的是在讀時:

unsigned long seq;

dowhile(read_seqretry(&my_seq,seq));

讀者通過獲取乙個整數順序值而進入臨界區。在退出時,該順序值會和當前值比較,如果不等,就必須重試讀取訪問。

seq鎖對寫者更有利。只要沒有其它寫者,寫鎖總是能夠被成功獲取。掛起的寫者會不斷地使讀者迴圈,直到不再有任何寫者持有鎖為止。seq鎖通常不能用於保護包含指標的資料結構,因為在寫者修改資料結構指標的同時,讀者可能會追隨乙個無效的指標。

7. rcu(讀取-複製-更新)

讀取-複製-更新(read-copy-update, rcu)也是一種高階的互斥機制,在正確的條件下,也卡獲得高的效能。它是針對經常發生讀取而很少寫入的情形做了優化。被保護的資源應該通過指標訪問,而對這些資源的引用必須僅有原子**擁有。在需要修改資料結構時,寫入執行緒先複製,然後修改副本,之後用新的版本替代相關指標。當確信老的版本沒有其他引用時,就釋放老的版本。

使用例項有網路路由表和核心中的starmode射頻ip驅動程式。使用rcu的**應包含。

在讀取端,**使用rcu保護的資料結構時,必須引用資料結構的**包括在rcu_read_lock和rcu_read_unlock呼叫之間:

struct data_buff * my_buff;

rcu_read_lock();

do_something_with(my_buff);

rcu_read_unlock();

函式rcu_read_lock呼叫非常快,它會禁止核心搶占,但不會等待任何東西。用來檢驗讀取"鎖"的**必須是原子的。在呼叫rcu_read_unlock之後就不該對手保護的資料結構有任何引用。

修改受rcu保護的資料結構的**必須通過分配乙個struct rcu_head資料結構來獲取清除用的**函式。修改完資源之後,應該做如下呼叫:

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

在可以安全釋放該資源時,給定的func會被呼叫,func通常就是呼叫kfree。

8. 禁止搶占

我們知道,乙個自旋鎖被持有,核心便不能進行搶占。但是,某些情況下並不需要自旋鎖,但是仍然需要關閉核心搶占。比如說,每個處理器上的資料。如果資料對於每個處理器是唯一的,那麼,這樣的資料可能就不需要使用鎖來保護,因為資料只能被乙個處理器訪問。如果自旋鎖沒有被持有,核心又是搶占的,那麼乙個新排程的任務就可能訪問乙個變數。

為了解決這個問題,可以通過preempt_disable()來禁止核心搶占。這是乙個可以巢狀呼叫的函式,可以呼叫任意次。每次呼叫都必須有乙個相應的preempt_enable()呼叫。當最後一次preempt_enable()被呼叫後,核心搶占才重新啟用。例如:

preempt_disable();

/*訪問當預處理器的唯一資料*/

preempt_enable();

搶占計數存放著被持有鎖的數量和preempt_disable()函式呼叫的次數,如果計數是0,那麼核心就進行搶占,如果為1或更大的值,那麼核心就不會進行搶占。

9. 順序和屏障

當處理器和硬體互動時,時常需要確保乙個給定的讀操作發生在其他讀或寫操作之前。在多處理器上,可能需要按寫資料的順序讀資料。但是編譯器和處理器為了提高效率,可能對讀和寫重新排序。但是,處理器提供了機器指令來確保順序,指示編譯器不要對給定點周圍的指令序列進行重新排序。這些確保順序的指令稱為屏障(barrier)。

如果前後的指令沒有直接的資料依賴關係,就會被處理器重新排序,但是像下面的例子,處理器和編譯器就不會對指令重新排序:

a = 1;

b = a;

函式rmb()提供了乙個"讀"記憶體屏障,它確保跨越rmb()的載入動作不會發生重排序。也就是說,在rmb()之前的載入操作不會被重新排在該呼叫之後。

函式wmb()提供了乙個"寫"記憶體屏障,與rmb()區別是它僅僅針對儲存而非載入。

函式mb()既提供了讀屏障也提供了寫屏障。

看看下面的例子,其中a的初始值是1,b的初始值是2:

執行緒1執行緒2

a = 3;                        -

mb();                        _

b = 4;                        c = b;

-                            rmb();

-                            d = a;

如果不使用記憶體屏障,在某些處理器上,c可能接受了b的新值(4),而d接受了a原來的值(1)。

巨集smp_rmb()、smp_wmb()、smp_mb()、smp_read_barrier_depends()提供了乙個有用的優化。在smp核心中它們被定義成常用的記憶體屏障,而在單處理器核心中,它們被定義成編譯器的屏障。方法barrier()可以防止編譯器垮屏障對載入或儲存操作進行優化。編譯器不會重新組織儲存或載入操作而防止改變c**的效果和現有資料的依賴關係。前面討論的記憶體屏障可以完成編譯器屏障的功能,但是編譯器屏障要比記憶體屏障輕量得多(它實際上是輕快的)。

核心同步機制 優化屏障和記憶體屏障

編譯器編譯源 時,會將源 進行優化,將源 的指令進行重排序,以適合於cpu的並行執行。然而,核心同步必須避免指令重新排序,優化屏障 optimization barrier 避免編譯器的重排序優化操作,保證編譯程式時在優化屏障之前的指令不會在優化屏障之後執行。linux用巨集barrier實現優化屏...

併發庫之同步屏障

讓一組執行緒達到乙個同步點時被阻塞,直到最後乙個執行緒達到同步點,這時候屏障才會放行,所有被屏障攔截的執行緒才會繼續執行。構造方法 public cyclicbarrier int parties public cyclicbarrier int parties runnable r 構造引數表示屏...

核心同步機制之自旋鎖 讀 寫鎖

自旋鎖 spin lock 是用來在多處理器環境中工作的一種特殊的鎖。如果核心控制路徑發現自旋鎖 開著 就獲取鎖並繼續自己的執行。相反,如果核心控制路徑發現由執行在另乙個cpu上的核心控制路徑 鎖著 就在一直迴圈等待,反覆執行一條緊湊的迴圈指令,直到鎖被釋放。自旋鎖與互斥鎖有點類似,只是自旋鎖不會引...