從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 lock;
// ...
spin_lock_irq(&lock);
// do something
spin_unlock_irq(&lock);
 


// code 2
extern spinlock_t lock;
// ...
spin_lock_irq(&lock);
// do something
spin_unlock_irq(&lock);
Code 1和code 2都運行在interrupt context下,由於中斷可以嵌套執行,我們很容易就可以想到這樣的運行次序:
 
Code 1                                   Code 2
extern spinlock_t lock;
// ...
spin_lock_irq(&lock);  
                                                extern spinlock_t lock;
                                                // ...
                                                spin_lock_irq(&lock);
                                                // do something
                                               spin_unlock_irq(&lock);
// do something
spin_unlock_irq(&lock);
問題是在第一個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);
發佈了13 篇原創文章 · 獲贊 16 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章