Linux設備驅動開發詳解-Note(14)--- Linux 設備驅動中的併發控制(1)

Linux 設備驅動中的併發控制(1)

成於堅持,敗於止步

併發與競態

併發(concurrency)指的是多個執行單元同時、並行被執行,而併發的執行單元對共享資源(硬件資源和軟件上的全局變量、靜態變量等)的訪問則很容易導致競態(race conditions)。例如,對於 globalmem 設備,假設一個執行單元 A 對其寫入 3000
個字符“a”,而另一個執行單元 B 對其寫入 4000 個字符“b”,第三個執行單元 C 讀取 globalmem 的所有字符。如果執行單元 A、B 的寫操作如圖 7.1 所示的順序執行,執行單元 C 的讀操作不會有問題。但是,如果執行單元 A、B 如圖 7.2 所示的順序執行,而執行單元 C 又“不合時宜”地讀,則會讀出 3000 個“b”。

比圖 7.2 更復雜、更混亂的併發大量地存在於設備驅動中,只要併發的多個執行單元存在對共享資源的訪問,競態就可能發生。在 Linux 內核中,主要的競態發生於如下幾種情況。

1.對稱多處理器(SMP)的多個 CPU

SMP 是一種緊耦合、共享存儲的系統模型,其體系結構如圖 7.3 所示,它的特點是多個 CPU 使用共同的系統總線,因此可訪問共同的外設和儲存器。

2.單 CPU 內進程與搶佔它的進程

Linux 2.6 內核支持搶佔調度,一個進程在內核執行的時候可能被另一高優先級進程打斷,進程與搶佔它的進程訪問共享資源的情況類似於 SMP 的多 個 CPU。

3.中斷(硬中斷、軟中斷、Tasklet、底半部)與進程之間

中斷可以打斷正在執行的進程,如果中斷處理程序訪問進程正在訪問的資源,則競態也會發生。 此外,中斷也有可能被新的更高優先級的中斷打斷,因此,多箇中斷之間本身也可能引起併發而導致競態。

上述併發的發生情況除了 SMP 是真正的並行以外,其他的都是“宏觀並行、微觀串行”的,但其引發的實質問題和 SMP 相似。 解決競態問題的途徑是保證對共享資源的互斥訪問,所謂互斥訪問是指一個執行單元在訪問共享資源的時候,其他的執行單元被禁止訪問。 訪問共享資源的代碼區域稱爲臨界區(critical sections),臨界區需要以某種互斥機制加以保護。中斷屏蔽、原子操作、自旋鎖和信號量等是 Linux 設備驅動中可採用的互斥途徑,下面將進行一一講解。

中斷屏蔽

在單 CPU 範圍內避免競態的一種簡單方法是在進入臨界區之前屏蔽系統的中斷。CPU 一般都具備屏蔽中斷和打開中斷的功能,這項功能可以保證正在執行的內核執行路徑不被中斷處理程序所搶佔,防止某些競態條件的發生。具體而言,中斷屏蔽將使得中斷與進程之間的併發不再發生,而且,由於 Linux 內核的進程調度等操作都依賴中斷來實現,內核搶佔進程之間的併發也就得以避免了。

中斷屏蔽的使用方法爲:

local_irq_disable() //屏蔽中斷

critical section //臨界區

local_irq_enable() //開中斷

由於 Linux 系統的異步 I/O、進程調度等很多重要操作都依賴於中斷,中斷對於內核的運行非常重要,在屏蔽中斷期間所有的中斷都無法得到處理,因此長時間屏蔽中斷是很危險的,有可能造成數據丟失甚至系統崩潰。這就要求在屏蔽了中斷之後,當前的內核執行路徑應當儘快地執行完臨界區的代碼。

local_irq_disable()和 local_irq_enable()都只能禁止和使能本 CPU 內的中斷,因此,並不能解決 SMP 多 CPU 引發的競態。因此,單獨使用中斷屏蔽通常不是一種值得推薦的避免競態的方法,它適宜與自旋鎖聯合使用。

與 local_irq_disable()不同的是,local_irq_save(flags)除了進行禁止中斷的操作以外,還保存目前 CPU 的中斷位信息,local_irq_restore(flags)進行的是與 local_irq_save(flags)相反的操作。

如果只是想禁止中斷的底半部,應使用 local_bh_disable(),使能被 local_bh_disable()禁止的底半部應該調用 local_bh_enable()。

原子操作

原子操作指的是在執行過程中不會被別的代碼路徑所中斷的操作。 Linux 內核提供了一系列函數來實現內核中的原子操作,這些函數又分爲兩類,分別針對位和整型變量進行原子操作。它們的共同點是在任何情況下操作都是原子的,內核代碼可以安全地調用它們而不被打斷。位和整型變量原子操作都依賴底層CPU 的原子操作來實現,因此所有這些函數都與 CPU 架構密切相關。

整型原子操作

1.設置原子變量的值

void atomic_set(atomic_t *v, int i); //設置原子變量的值爲 i

atomic_t v = ATOMIC_INIT(0); //定義原子變量 v 並初始化爲 0

2.獲取原子變量的值

atomic_read(atomic_t *v); //返回原子變量的值

3.原子變量加/減

void atomic_add(int i, atomic_t *v); //原子變量增加 i

void atomic_sub(int i, atomic_t *v); //原子變量減少 i

4.原子變量自增/自減

void atomic_inc(atomic_t *v); //原子變量增加 1

void atomic_dec(atomic_t *v); //原子變量減少 1

5.操作並測試

int atomic_inc_and_test(atomic_t *v);

int atomic_dec_and_test(atomic_t *v);

int atomic_sub_and_test(int i, atomic_t *v);

上述操作對原子變量執行自增、自減和減操作後(注意沒有加)測試其是否爲 0,爲 0 則返回 true,否則返回 false。

6.操作並返回

int atomic_add_return(int i, atomic_t *v);

int atomic_sub_return(int i, atomic_t *v);

int atomic_inc_return(atomic_t *v);

int atomic_dec_return(atomic_t *v);

上述操作對原子變量進行加/減和自增/自減操作,並返回新的值。

位原子操作

1.設置位

void set_bit(nr, void *addr); 上述操作設置 addr 地址的第 nr 位,所謂設置位即將位寫爲 1。

2.清除位

void clear_bit(nr, void *addr); 上述操作清除 addr 地址的第 nr 位,所謂清除位即將位寫爲 0。

3.改變位

void change_bit(nr, void *addr); 上述操作對 addr 地址的第 nr 位進行反置。

4.測試位

test_bit(nr, void *addr); 上述操作返回 addr 地址的第 nr 位。

5.測試並操作位

int test_and_set_bit(nr, void *addr);

int test_and_clear_bit(nr, void *addr);

int test_and_change_bit(nr, void *addr);

上述 test_and_xxx_bit(nr, void *addr)操作等同於執行 test_bit (nr, void *addr) 後再執行 xxx_bit(nr, void *addr)。

下面代碼給出了原子變量的使用實例,它用於使設備最多隻能被一個進程打開。

1 static atomic_t xxx_available = ATOMIC_INIT(1); /定義原子變量/
2
3 static int xxx_open(struct inode *inode, struct file filp)
4 {
5 …
6 if (!atomic_dec_and_test(&xxx_available))
7 {
8 atomic_inc(&xxx_available);
9 return - EBUSY; /已經打開/
10 }
11 …
12 return 0; /
成功 */
13 }
14
15 static int xxx_release(struct inode *inode, struct file filp)
16 {
17 atomic_inc(&xxx_available); /
釋放設備 */
18 return 0;
19 }
自旋鎖 
自旋鎖的使用

自旋鎖(spin lock)是一種對臨界資源進行互斥手訪問的典型手段,其名稱來源於它的工作方式。爲了獲得一個自旋鎖,在某 CPU 上運行的代碼需先執行一個原子操作,該操作測試並設置(test-and-set)某個內存變量,由於它是原子操作,所以在該操作完成之前其他執行單元不可能訪問這個內存變量。 如果測試結果表明鎖已經空閒,則程序獲得這個自旋鎖並繼續執行;如果測試結果表明鎖仍被佔用,程序將在一個小的循環內重複這個“測試並設置”操作,即進行所謂的“自旋”,通俗地說就是“在原地打轉”。當自旋鎖的持有者通過重置該變量釋放這個自旋鎖後,某個等待的“測試並設置”操作向其調用者報告鎖已釋放。

理解自旋鎖最簡單的方法是把它作爲一個變量看待,該變量把一個臨界區或者標記爲“我當前在運行,請稍等一會”或者標記爲“我當前不在運行,可以被使用”。如果 A 執行單元首先進入例程,它將持有自旋鎖;當 B 執行單元試圖進入同一個例程時,將獲知自旋鎖已被持有,需等到 A 執行單元釋放後才能進入。

Linux 系統中與自旋鎖相關的操作主要有如下 4 種。
1.定義自旋鎖

spinlock_t spin;

2.初始化自旋鎖

spin_lock_init(lock) 該宏用於動態初始化自旋鎖 lock

3.獲得自旋鎖

spin_lock(lock)

該宏用於獲得自旋鎖 lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那裏,直到該自旋鎖的保持者釋放;

spin_trylock(lock)

該宏嘗試獲得自旋鎖 lock,如果能立即獲得鎖,它獲得鎖並返回真,否則立即返回假,實際上不再“在原地打轉”;

4.釋放自旋鎖

spin_unlock(lock)

該宏釋放自旋鎖 lock,它與 spin_trylock 或 spin_lock 配對使用。

自旋鎖一般這樣被使用,如下所示:

//定義一個自旋鎖
spinlock_t lock;
spin_lock_init(&lock);

spin_lock (&lock) ; //獲取自旋鎖,保護臨界區
…//臨界區
spin_unlock (&lock) ; //解鎖
自旋鎖主要針對 SMP 或單 CPU 但內核可搶佔的情況,對於單 CPU 和內核不支持搶佔的系統,自旋鎖退化爲空操作。在單CPU內核可搶佔的系統中,自旋鎖持有期間內核的搶佔將被禁止。由於內核可搶佔的單 CPU 系統的行爲實際很類似於 SMP系統,因此,在這樣的單 CPU 系統中使用自旋鎖仍十分必要。 儘管用了自旋鎖可以保證臨界區不受別的 CPU 和本 CPU 內的搶佔進程打擾,但是得到鎖的代碼路徑在執行臨界區的時候還可能受到中斷和底半部(BH)的影響。爲了防止這種影響,就需要用到自旋鎖的衍生。spin_lock()/spin_unlock()是自旋鎖機制的基礎,它們和關中斷 local_irq_ disable()/開中斷 local_irq_enable()、關底半部local_bh_disable()/開底半部 local_bh_enable()、關中斷並保存狀態字 local_irq_save()/開中斷並恢復狀態 local_irq_restore()結合就形成了整套自旋鎖機制,關係如下所示: 
spin_lock_irq() = spin_lock() + local_irq_disable()

spin_unlock_irq() = spin_unlock() + local_irq_enable()

spin_lock_irqsave() = spin_unlock() + local_irq_save()

spin_unlock_irqrestore() = spin_unlock() + local_irq_restore()

spin_lock_bh() = spin_lock() + local_bh_disable()

spin_unlock_bh() = spin_unlock() + local_bh_enable()

驅動工程師應謹慎使用自旋鎖,而且在使用中還要特別注意如下幾個問題。

1、自旋鎖實際上是忙等鎖,當鎖不可用時,CPU 一直循環執行“測試並設置”該鎖直到可用而取得該鎖,CPU 在等待自旋鎖時不做任何有用的工作,僅僅是等待。因此,只有在佔用鎖的時間極短的情況下,使用自旋鎖纔是合理的。當臨界區很大或有共享設備的時候,需要較長時間佔用鎖,使用自旋鎖會降低系統的性能。

2、自旋鎖可能導致系統死鎖。引發這個問題最常見的情況是遞歸使用一個自旋鎖,即如果一個已經擁有某個自旋鎖的 CPU 想第二次獲得這個自旋鎖,則該 CPU 將死鎖。此外,如果進程獲得自旋鎖之後再阻塞,也有可能導致死鎖的發生。copy_from_user()、copy_to_user()和 kmalloc()等函數都有可能引起阻塞,因此在自旋鎖的佔用期間不能調用這些函數。

下面代碼給出了自旋鎖的使用實例,它被用於實現使得設備只能被最多一個進程打開。

1 int xxx_count = 0;/定義文件打開次數計數/
2
3 static int xxx_open(struct inode *inode, struct file filp)
4 {
5 …
6 spinlock(&xxx_lock);
7 if (xxx_count)/已經打開/
8 {
9 spin_unlock(&xxx_lock);
10 return - EBUSY;
11 }
12 xxx_count++;/增加使用計數/
13 spin_unlock(&xxx_lock);
14 …
15 return 0; /
成功 */
16 }
17
18 static int xxx_release(struct inode *inode, struct file *filp)
19 {
20 …
21 spinlock(&xxx_lock);
22 xxx_count–; /減少使用計數/
23 spin_unlock(&xxx_lock);
24
25 return 0;
26 }
讀寫自旋鎖

自旋鎖不關心鎖定的臨界區究竟進行怎樣的操作,不管是讀還是寫,它都一視同仁。即便多個執行單元同時讀取臨界資源也會被鎖住。實際上,對共享資源併發訪問時,多個執行單元同時讀取它是不會有問題的,自旋鎖的衍生鎖讀寫自旋鎖(rwlock)可允許讀的併發。 讀寫自旋鎖是一種比自旋鎖粒度更小的鎖機制,它保留了“自旋”的概念,但是在寫操作方面,只能最多有一個寫進程,在讀操作方面,同時可以有多個讀執行單元。當然,讀和寫也不能同時進行。 讀寫自旋鎖涉及的操作如下所示。

1.定義和初始化讀寫自旋鎖

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* 靜態初始化 */

rwlock_t my_rwlock;

rwlock_init(&my_rwlock); /* 動態初始化 */

2.讀鎖定

void read_lock(rwlock_t *lock);

void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_lock_irq(rwlock_t *lock);

void read_lock_bh(rwlock_t *lock);

3.讀解鎖

void read_unlock(rwlock_t *lock);

void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void read_unlock_irq(rwlock_t *lock);

void read_unlock_bh(rwlock_t *lock);

在對共享資源進行讀取之前,應該先調用讀鎖定函數,完成之後應調用讀解鎖函數。

read_lock_irqsave()、read_lock_irq()和 read_lock_bh()分別是 read_lock()分別與local_irq_save() 、 local_irq_disable() 和 local_bh_disable() 的 組合,讀解鎖函數read_unlock_irqrestore()、read_unlock_ irq()、read_unlock_bh()的情況與此類似。

4.寫鎖定

void write_lock(rwlock_t *lock);

void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock);

void write_lock_bh(rwlock_t *lock);

int write_trylock(rwlock_t *lock);

5.寫解鎖

void write_unlock(rwlock_t *lock);

void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);

void write_unlock_irq(rwlock_t *lock);

void write_unlock_bh(rwlock_t *lock);

write_lock_irqsave() 、 write_lock_irq() 、 write_lock_bh() 分 別 是 write_lock() 與local_irq_save() 、 local_irq_disable() 和 local_bh_disable() 的組合,寫解鎖函數write_unlock_irqrestore()、write_unlock_irq()、write_unlock_bh()的情況與此類似。

在對共享資源進行讀取之前,應該先調用寫鎖定函數,完成之後應調用寫解鎖函數。和 spin_trylock()一樣,write_trylock()也只是嘗試獲取讀寫自旋鎖,不管成功失敗,都會立即返回。 讀寫自旋鎖一般這樣被使用,如下所示:

rwlock_t lock; //定義 rwlock
rwlock_init(&lock); //初始化 rwlock

//讀時獲取鎖
read_lock(&lock);
… //臨界資源
read_unlock(&lock);

//寫時獲取鎖
write_lock_irqsave(&lock, flags);
… //臨界資源
write_unlock_irqrestore(&lock, flags);
順序鎖 
順序鎖(seqlock)是對讀寫鎖的一種優化,若使用順序鎖,讀執行單元絕不會被寫執行單元阻塞,也就是說,讀執行單元可以在寫執行單元對被順序鎖保護的共享資源進行寫操作時仍然可以繼續讀,而不必等待寫執行單元完成寫操作,寫執行單元也不需要等待所有讀執行單元完成讀操作纔去進行寫操作。 但是,寫執行單元與寫執行單元之間仍然是互斥的,即如果有寫執行單元在進行寫操作,其他寫執行單元必須自旋在那裏,直到寫執行單元釋放了順序鎖。 如果讀執行單元在讀操作期間,寫執行單元已經發生了寫操作,那麼,讀執行單元必須重新讀取數據,以便確保得到的數據是完整的。這種鎖在讀寫同時進行的概率比較小時,性能是非常好的,而且它允許讀寫同時進行,因而更大地提高了併發性。

順序鎖有一個限制,它必須要求被保護的共享資源不含有指針,因爲寫執行單元可能使得指針失效,但讀執行單元如果正要訪問該指針,將導致 Oops。 在 Linux 內核中,寫執行單元涉及如下順序鎖操作。

1.獲得順序鎖

void write_seqlock(seqlock_t *sl);

int write_tryseqlock(seqlock_t *sl);

write_seqlock_irqsave(lock, flags)

write_seqlock_irq(lock)

write_seqlock_bh(lock)

其中:

write_seqlock_irqsave() = loal_irq_save() + write_seqlock()

write_seqlock_irq() = local_irq_disable() + write_seqlock()

write_seqlock_bh() = local_bh_disable() + write_seqlock()

2.釋放順序鎖

void write_sequnlock(seqlock_t *sl);

write_sequnlock_irqrestore(lock, flags)

write_sequnlock_irq(lock)

write_sequnlock_bh(lock)

其中:

write_sequnlock_irqrestore() = write_sequnlock() + local_irq_restore()

write_sequnlock_irq() = write_sequnlock() + local_irq_enable()

write_sequnlock_bh() = write_sequnlock() + local_bh_enable()

寫執行單元使用順序鎖的模式如下:

write_seqlock(&seqlock_a);

…//寫操作代碼塊

write_sequnlock(&seqlock_a);

因此,對寫執行單元而言,它的使用與 spinlock 相同。

讀執行單元涉及如下順序鎖操作。

1.讀開始

unsigned read_seqbegin(const seqlock_t *sl);

read_seqbegin_irqsave(lock, flags)

讀執行單元在對被順序鎖 s1 保護的共享資源進行訪問前需要調用該函數,該函數僅返回順序鎖 s1 的當前順序號。其中:

read_seqbegin_irqsave() = local_irq_save() + read_seqbegin()

2.重讀

int read_seqretry(const seqlock_t *sl, unsigned iv);

read_seqretry_irqrestore(lock, iv, flags)

讀執行單元在訪問完被順序鎖 s1 保護的共享資源後需要調用該函數來檢查,在讀訪問期間是否有寫操作。如果有寫操作,讀執行單元就需要重新進行讀操作。其中:

read_seqretry_irqrestore() = read_seqretry() + local_irq_restore()

讀執行單元使用順序鎖的模式如下:
do {

seqnum = read_seqbegin(&seqlock_a);

//讀操作代碼塊

} while (read_seqretry(&seqlock_a, seqnum));

讀-拷貝-更新

RCU(Read-Copy Update,讀-拷貝-更新),它是基於其原理命名的。RCU 並不是新的鎖機制,它對於 Linux 內核而言是新的。早在 20 世紀 80 年代就有了這種機制,而在 Linux 系統中,開發 2.5.43 內核時引入該技術,並正式包含在 2.6 內核中。 對於被 RCU 保護的共享數據結構,讀執行單元不需要獲得任何鎖就可以訪問它,不使用原子指令,而且在除 Alpha 的所有架構上也不需要內存柵(Memory Barrier),因此不會導致鎖競爭、內存延遲以及流水線停滯。不需要鎖也使得使用更容易,因爲死鎖問題就不需要考慮了。

使用 RCU 的寫執行單元在訪問它前需首先複製一個副本,然後對副本進行修改,最後使用一個回調機制在適當的時機把指向原來數據的指針重新指向新的被修改的數據,這個時機就是所有引用該數據的 CPU 都退出對共享數據的操作的時候。讀執行單元沒有任何同步開銷,而寫執行單元的同步開銷則取決於使用的寫執行單元間的同步機制。

RCU 可以看做讀寫鎖的高性能版本,相比讀寫鎖,RCU 的優點在於既允許多個讀執行單元同時訪問被保護的數據,又允許多個讀執行單元和多個寫執行單元同時訪問被保護的數據。 但是,RCU 不能替代讀寫鎖,因爲如果寫比較多時,對讀執行單元的性能提高不能彌補寫執行單元導致的損失。因爲使用 RCU 時,寫執行單元之間的同步開銷會比較大,它需要延遲數據結構的釋放,複製被修改的數據結構,它也必須使用某種鎖機制同步並行的其他寫執行單元的修改操作。

Linux 系統中提供的 RCU 操作如下 4 種。

1.讀鎖定

rcu_read_lock()

rcu_read_lock_bh()

2.讀解鎖

rcu_read_unlock()

rcu_read_unlock_bh()

使用 RCU 進行讀的模式如下:

rcu_read_lock()

…//讀臨界區

rcu_read_unlock()

其中 rcu_read_lock()和 rcu_read_unlock()實質只是禁止和使能內核的搶佔調度,如

下所示:

#define rcu_read_lock() preempt_disable()

#define rcu_read_unlock() preempt_enable()

其變種 rcu_read_lock_bh()、rcu_read_unlock_bh()則定義爲:

#define rcu_read_lock_bh() local_bh_disable()

#define rcu_read_unlock_bh() local_bh_enable()

3.同步 RCU

synchronize_rcu()

該函數由 RCU 寫執行單元調用,它將阻塞寫執行單元,直到所有的讀執行單元已經完成讀執行單元臨界區,寫執行單元纔可以繼續下一步操作。如果有多個 RCU寫執行單元調用該函數,它們將在一個 grace period(即所有的讀執行單元已經完成對臨界區的訪問)之後全部被喚醒。synchronize_rcu()保證所有 CPU 都處理完正在運行的讀執行單元臨界區。

synchronize_kernel()

內核代碼使用該函數來等待所有 CPU 處於可搶佔狀態,目前功能等同於synchronize_rcu(),但現在已經不建議使用,而是使用 synchronize_sched(),該函數用於等待所有 CPU 都處在可搶佔狀態,它能保證正在運行的中斷處理函數處理完畢,但不能保證正在運行的軟中斷處理完畢。

4.掛接回調

void fastcall call_rcu(struct rcu_head *head,

void (*func)(struct rcu_head *rcu));

函數 call_rcu()也由 RCU 寫執行單元調用,它不會使寫執行單元阻塞,因而可以在中斷上下文或軟中斷中使用。該函數將把函數 func 掛接到 RCU 回調函數鏈上,然後立即返回。函數 synchronize_rcu()的實現實際上使用了 call_rcu()函數。

void fastcall call_rcu_bh(struct rcu_head *head,

void (*func)(struct rcu_head *rcu));

call_ruc_bh()函數的功能幾乎與 call_rcu()完全相同,唯一差別就是它把軟中斷的完成也當做經歷一個 quiescent state(靜默狀態),因此如果寫執行單元使用了該函數,

在進程上下文的讀執行單元必須使用 rcu_read_lock_bh()。 每個 CPU 維護兩個數據結構 rcu_data 和 rcu_bh_data,它們用於保存回調函數,函數 call_rcu()把回調函數註冊到 rcu_data,而 call_rcu_bh()則把回調函數註冊到rcu_bh_data,在每一個數據結構上,回調函數被組成一個鏈表,先註冊的排在前頭,後註冊的排在末尾。

使用 RCU 時,讀執行單元必須提供一個信號給寫執行單元以便寫執行單元能夠確定數據可以被安全地釋放或修改的時機。有一個專門的垃圾收集器來探測讀執行單元的信號,一旦所有的讀執行單元都已經發送信號告知它們都不在使用被 RCU 保護的數據結構,垃圾收集器就調用回調函數完成最後的數據釋放或修改操作。 RCU 還增加了鏈表操作函數的 RCU 版本,如下所示:

static inline void list_add_rcu(struct list_head *new, struct list_head *head);

該函數把鏈表元素 new 插入到 RCU 保護的鏈表 head 的開頭,內存柵保證了在引用這個新插入的鏈表元素之前,新鏈表元素的鏈接指針的修改對所有讀執行單元是可見的。

static inline void list_add_tail_rcu(struct list_head *new, struct list_head *head);

該函數類似於 list_add_rcu(),它將把新的鏈表元素 new 添加到被 RCU 保護的鏈表的末尾。

static inline void list_del_rcu(struct list_head *entry);

該函數從 RCU 保護的鏈表中刪除指定的鏈表元素 entry。

static inline void list_replace_rcu(struct list_head *old, struct list_head *new);

該函數是 RCU 新添加的函數,並不存在非 RCU 版本。它使用新的鏈表元素 new取代舊的鏈表元素 old,內存柵保證在引用新的鏈表元素之前,它對鏈接指針的修正對所有讀執行單元是可見的。

list_for_each_rcu(pos, head)

該宏用於遍歷由 RCU 保護的鏈表 head,只要在讀執行單元臨界區使用該函數,它就可以安全地和其他_rcu 鏈表操作函數併發運行如 list_add_rcu()。

list_for_each_safe_rcu(pos, n, head)

該宏類似於 list_for_each_rcu,不同之處在於它允許安全地刪除當前鏈表元素 pos。

list_for_each_entry_rcu(pos, head, member)

該宏類似於 list_for_each_rcu,不同之處在於它用於遍歷指定類型的數據結構鏈表,當前鏈表元素 pos 爲一個包含 struct list_head 結構的特定的數據結構。

static inline void hlist_del_rcu(struct hlist_node *n)

它從由 RCU 保護的哈希鏈表中移走鏈表元素 n。

static inline void hlist_add_head_rcu(struct hlist_node *n, struct hlist_head *h);

該函數用於把鏈表元素 n 插入到被 RCU 保護的哈希鏈表的開頭,但同時允許

讀執行單元對該哈希鏈表的遍歷。內存柵確保在引用新鏈表元素之前,它對指針的修改對所有讀執行單元可見。

hlist_for_each_rcu(pos, head)

該宏用於遍歷由 RCU 保護的哈希鏈表 head,只要在讀端臨界區使用該函數,它就可以安全地和其他_rcu 哈希鏈表操作函數(如 hlist_add_rcu)併發運行。

hlist_for_each_entry_rcu(tpos, pos, head, member)

類似於 hlist_for_each_rcu(),不同之處在於它用於遍歷指定類型的數據結構哈希鏈表,當前鏈表元素 pos 爲一個包含 struct list_head 結構的特定的數據結構。 目前,RCU 的使用在內核中已經非常普遍,內核中大量原先使用讀寫鎖的代碼被RCU 替換。

就到這裏了,O(∩_∩)O~

我的專欄地址:http://blog.csdn.net/column/details/linux-driver-note.html

待續。。。。

作者:Ela–學海無涯
來源:CSDN
原文:https://blog.csdn.net/xinyuwuxian/article/details/9345955
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章