那些情況該使用它們spin_lock到spin_lock_irqsave【轉】


Spinlock的目的是用來同步SMP中會被多個CPU同時存取的變量。在Linux中,普通的spinlock由於不帶額外的語義,是用起來反而要非常小心。
在Linux kernel中執行的代碼大體分normal和interrupt context兩種。tasklet/softirq可以歸為normal因為他們可以進入等待;nested interrupt是interrupt context的一種特殊情況,當然也是interrupt context。Normal級別可以被interrupt搶斷,interrupt會被另一個interrupt搶斷,但不會被normal中斷。各個 interrupt之間沒有優先級關系,只要有可能,每個interrupt都會被其他interrupt中斷。
我們先考慮單CPU的情況。在這樣情況下,不管在什么執行級別,我們只要簡單地把CPU的中斷關掉就可以達到獨占處理的目的。從這個角度來說,spinlock的實現簡單地令人乍舌:cli/sti。只要這樣,我們就關閉了preemption帶來的復雜之門。
單CPU的情況很簡單,多CPU就不那么簡單了。單純地關掉當前CPU的中斷並不會給我們帶來好運。當我們的代碼存取一個shared variable時,另一顆CPU隨時會把數據改得面目全非。我們需要有手段通知它(或它們,你知道我的意思)——spinlock正為此設。這個例子是我們的第一次嘗試:

extern spinlock_t lock;
// ...
spin_lock(&lock);
// do something
spin_unlock(&lock);
他能正常工作嗎?答案是有可能。在某些情況下,這段代碼可以正常工作,但想一想會不會發生這樣的事:
// in normal run level
extern spinlock_t lock;
// ...
spin_lock(&lock);
// do something
                                                                           // interrupted by IRQ ...

                                                                          // in IRQ
                                                                         extern spinlock_t lock;
                                                                         spin_lock(&lock);
喔,我們在normal級別下獲得了一個spinlock,正當我們想做什么的時候,我們被interrupt打斷了,CPU轉而執行interrupt level的代碼,它也想獲得這個lock,於是“死鎖”發生了!解決方法很簡單,看看我們第二次嘗試:

extern spinlock_t lock;
// ...
cli; // disable interrupt on current CPU
spin_lock(&lock);
// do something
spin_unlock(&lock);
sti; // enable interrupt on current CPU
在獲得spinlock之前,我們先把當前CPU的中斷禁止掉,然后獲得一個lock;在釋放lock之后再把中斷打開。這樣,我們就防止了死鎖。事實上,Linux提供了一個更為快捷的方式來實現這個功能:

extern spinlock_t lock;
// ...
spin_lock_irq(&lock);
// do something
spin_unlock_irq(&lock);
如果沒有nested interrupt,所有這一切都很好。加上nested interrupt,我們再來看看這個例子:

// code 1
extern spinlock_t lock1;
// ...
spin_lock_irq(&lock);
// do something
spin_unlock_irq(&lock);
 

// code 2
extern spinlock_t lock2;
// ...
spin_lock_irq(&lock2);
// do something
spin_unlock_irq(&lock2);
Code 1和code 2都可運行在interrupt下,我們很容易就可以想到這樣的運行次序():
 

Code 1                                                                        Code 2

extern spinlock_t lock1;

// ...

spin_lock_irq(&lock1); 

                                                                                   extern spinlock_t lock2;

// ...
spin_lock_irq(&lock1);

// do something 

                                                                                   spin_unlock_irq(&lock2);

                                                                                   // do something

spin_unlock_irq(&lock1); 
問題是在第二個spin_unlock_irq后這個CPU的中斷已經被打開,“死鎖”的問題又會回到我們身邊!
解決方法是我們在每次關閉中斷前紀錄當前中斷的狀態,然后恢復它而不是直接把中斷打開。
unsigned long flags;
local_irq_save(flags);
spin_lock(&lock);
// do something
spin_unlock(&lock);
local_irq_restore(flags);
Linux同樣提供了更為簡便的方式:
unsigned long flags;
spin_lock_irqsave(&lock, flags);
// do something

spin_unlock_irqrestore(&lock, flags);

 

總結:

如果被保護的共享資源只在進程上下文訪問和軟中斷上下文訪問,那么當在進程上下文訪問共享資源時,可能被軟中斷打斷,從而可能進入軟中斷上下文來對被保護的共享資源訪問,因此對於這種情況,對共享資源的訪問必須使用spin_lock_bh和spin_unlock_bh來保護。

  當然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和spin_unlock_irqrestore也可以,它們失效了本地硬中斷,失效硬中斷隱式地也失效了軟中斷。但是使用spin_lock_bh和spin_unlock_bh是最恰當的,它比其他兩個快。
  如果被保護的共享資源只在進程上下文和tasklet或timer上下文訪問,那么應該使用與上面情況相同的獲得和釋放鎖的宏,因為tasklet和timer是用軟中斷實現的。
  如果被保護的共享資源只在一個tasklet或timer上下文訪問,那么不需要任何自旋鎖保護,因為同一個tasklet或timer只能在一個CPU上運行,即使是在SMP環境下也是如此。實際上tasklet在調用tasklet_schedule標記其需要被調度時已經把該tasklet綁定到當前CPU,因此同一個tasklet決不可能同時在其他CPU上運行。
  timer也是在其被使用add_timer添加到timer隊列中時已經被幫定到當前CPU,所以同一個timer絕不可能運行在其他CPU上。當然同一個tasklet有兩個實例同時運行在同一個CPU就更不可能了。
  如果被保護的共享資源只在兩個或多個tasklet或timer上下文訪問,那么對共享資源的訪問僅需要用spin_lock和spin_unlock來保護,不必使用_bh版本,因為當tasklet或timer運行時,不可能有其他tasklet或timer在當前CPU上運行。
 如果被保護的共享資源只在一個軟中斷(tasklet和timer除外)上下文訪問,那么這個共享資源需要用spin_lock和spin_unlock來保護,因為同樣的軟中斷可以同時在不同的CPU上運行。
  如果被保護的共享資源在兩個或多個軟中斷上下文訪問,那么這個共享資源當然更需要用spin_lock和spin_unlock來保護,不同的軟中斷能夠同時在不同的CPU上運行。
  如果被保護的共享資源在軟中斷(包括tasklet和timer)或進程上下文和硬中斷上下文訪問,那么在軟中斷或進程上下文訪問期間,可能被硬中斷打斷,從而進入硬中斷上下文對共享資源進行訪問,因此,在進程或軟中斷上下文需要使用spin_lock_irq和spin_unlock_irq來保護對共享資源的訪問。
  而在中斷處理句柄中使用什么版本,需依情況而定,如果只有一個中斷處理句柄訪問該共享資源,那么在中斷處理句柄中僅需要spin_lock和spin_unlock來保護對共享資源的訪問就可以了。
  因為在執行中斷處理句柄期間,不可能被同一CPU上的軟中斷或進程打斷。但是如果有不同的中斷處理句柄訪問該共享資源,那么需要在中斷處理句柄中使用spin_lock_irq和spin_unlock_irq來保護對共享資源的訪問。
  在使用spin_lock_irq和spin_unlock_irq的情況下,完全可以用spin_lock_irqsave和spin_unlock_irqrestore取代,那具體應該使用哪一個也需要依情況而定,如果可以確信在對共享資源訪問前中斷是使能的,那么使用spin_lock_irq更好一些。
  因為它比spin_lock_irqsave要快一些,但是如果你不能確定是否中斷使能,那么使用spin_lock_irqsave和spin_unlock_irqrestore更好,因為它將恢復訪問共享資源前的中斷標志而不是直接使能中斷。
  當然,有些情況下需要在訪問共享資源時必須中斷失效,而訪問完后必須中斷使能,這樣的情形使用spin_lock_irq和spin_unlock_irq最好。

  spin_lock用於阻止在不同CPU上的執行單元對共享資源的同時訪問以及不同進程上下文互相搶占導致的對共享資源的非同步訪問,而中斷失效和軟中斷失效卻是為了阻止在同一CPU上軟中斷或中斷對共享資源的非同步訪問


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM