摘要:多處理機系統正在變得越來越普通。盡管大多數用戶空間代碼仍將完美地運行,而且有些情況下不需要增加額外的代碼就能利用SMP特性的優勢,但是內核空間代碼必須編寫成具備“SMP意識”且是“SMP安全的”。以下幾段文字解釋如何去做。 多處理機系統正在變得越來越普通。盡管大多數用戶空間代碼仍將完美地運行,而且有些情況下不需要增加額外的代碼就能利用SMP特性的優勢,但是內核空間代碼必須編寫成具備“SMP意識”且是“SMP安全的”。以下幾段文字解釋如何去做。 問題 當有多個CPU時,同樣的代碼可能同時在兩個或多個CPU上執行。這在如下所示用於初始化某個圖像設備的例程中可能會出問題。 void init_hardware(void) { outb(0x1, hardware_base + 0x30); outb(0x2, hardware_base + 0x30); outb(0x3, hardware_base + 0x30); outb(0x4, hardware_base + 0x30); } 假設該硬件依賴於寄存器0x30按順序依次被設為0、1、2、3來初始化,那麼要是有另一個CPU來參乎的話,事情就會搞糟。想象有兩個CPU的情形,它們都在執行這個例程,不過2號CPU進入得稍慢點: CPU 1 CPU 2 0x30 = 1 0x30 = 2 0x30 = 1 0x30 = 3 0x30 = 2 0x30 = 4 0x30 = 3 0x30 = 4 這會發生什麼情況呢?從我們設想的硬件設備看來,它在寄存器0x30上收到的字節按順序為:1、2、1、3、2、4、3、4。 啊!原本好好的事第二個CPU一來就搞得一團糟了也。所幸的是,我們有防止這類事情發生的辦法。 自旋鎖小歷史 2.0.x版本的Linux內核通過給整個內核引入一個全局變量來防止多於一個CPU會造成的問題。這意味著任何時刻只有一個CPU能夠執行來自內核空間的代碼。這樣盡管能工作,但是當系統開始以多於2個的CPU出現時,擴展性能就不怎麼好。 2.1.x版本的內核系列加入了粒度更細的SMP支持。這意味著不再依賴於以前作為全局變量出現的“大鎖”,而是每個沒有SMP意識的例程現在都需要各自的自旋鎖。文件asm/spinlock.h中定義了若干類型的自旋鎖。 有了局部化的自旋鎖後,不止一個CPU同時執行內核空間代碼就變得可能了。 簡單的自旋鎖 理解自旋鎖的最簡單方法是把它作為一個變量看待,該變量把一個例程或者標記為“我當前在另一個CPU上運行,請稍等一會”,或者標記為“我當前不在運行”。如果1號CPU首先進入該例程,它就獲取該自旋鎖。當2號CPU試圖進入同一個例程時,該自旋鎖告訴它自己已為1號CPU所持有,需等到1號CPU釋放自己後才能進入。 spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin_lock (&my_spinlock); ... critical section ... spin_unlock (&my_spinlock); 中斷 設想我們的硬件的驅動程序還有一個中斷處理程序。該處理程序需要修改某些由我們的驅動程序定義的全局變量。這會造成混亂。我們如何解決呢? 保護某個數據結構,使它免遭中斷之修改的最初方法是全局地禁止中斷。在已知只有自己的中斷才會修改自己的驅動程序變量時,這麼做效率很低。所幸的是,我們現在有更好的辦法了。我們只是在使用共享變量期間禁止中斷,此後重新使能。 實現這種辦法的函數有三個: disable_irq() enable_irq() disable_irq_nosync() 這三個函數都取一個中斷號作為參數。注意,禁止一個中斷的時間太長會導致難以追蹤程序缺陷,丟失數據,甚至更壞。 disable_irq函數的非同步版本允許所指定的IRQ處理程序繼續運行,前提是它已經在運行,普通的disable_irq則所指定的IRQ處理程序不在如何CPU上運行。 如果需要在中斷處理程序中修改自旋鎖,那就不能使用普通的spin_lock()和spin_unlock(),而應該保存中斷狀態。這可通過給這兩個函數添加_irqsave後綴很容易地做到: spinlock_t my_spinlock = SPIN_LOCK_UNLOCKED; unsigned long flags; spin_lock_irqsave(&my_spinlock, flags); ... critical section ... spin_unlock_irqrestore (&my_spinlock, flags);