1. 程式人生 > >關於自旋鎖spinlock的一些理解

關於自旋鎖spinlock的一些理解

    這裡並不是講spinlock的一般用法,只是提出自己曾經的一些疑問點,也給出自己的解答。
    在《LINUX核心設計與實現》《LINUX裝置驅動程式》裡都提過,在非搶佔式的單處理器系統上的自旋鎖被優化為不做任何事情。我最初的疑問是即使是單處理器,沒有搶佔能力,但時間片用完,持有鎖的一方終會交出鎖,等待鎖的一方不會永遠自旋下去。但在《linux裝置驅動程式》裡面明確說到,“如果非搶佔式的單處理器系統進入某個鎖上的自旋狀態,則會永遠自旋下去;也就是說,沒有任何其他執行緒能夠獲得CPU來釋放這個鎖”。
    為了說明問題,還是有必要先說明一下在SMP(多處理器)或搶佔式下就需要真正的自旋鎖。
    一、SMP下
    CPU A對公共資源執行一些操作,在這個過程中隨時CPU B也可以對公共資源執行操作。
    如下面的程式碼片斷:
    if (NULL == buf)
        buf = kmalloc(size, GFP_KERNEL);
    當CPU A,CPU B都執行了if (NULL == buf),發現buf的確為NULL,那麼都會進行記憶體分配,但是隻有一個可以被buf記錄(最後執行賦值那個)。這樣會造成另一部記憶體無法回到系統。
    所以SMP上,需要鎖實現。
    二、搶佔式
    在2.4及以前的linux核心均不是搶佔式核心,是在2.6正式引入的。按以前的程序排程,如果一個程序在不耗光自己時間片前,不主動退出是不會交出控制權的,很明顯這樣一些實時性要求很高的程序無法得到滿足,所以需要搶佔。
    如下圖所示,程序A即使被中斷打斷了,後面還是會回到程序A,但程序B可能更需要馬上執行,如使用者敲了一個字元。


    如果是搶佔式,在中斷後程序排程器可以排程更高優先順序的程序執行,如下所示:
   

    從上面可以看出,程序A可能隨時會被B所搶佔,即是在單處理器上也會造成SMP同樣的併發問題,所以需要鎖實現。
    有了上面的介紹後,我們回到原來的問題:在非搶佔式的單處理器系統上的自旋鎖被優化為不做任何事情。
    
    
   

    上面是linux2.6.10的非SMP下spinlock的核心程式碼,雖然非搶佔也呼叫了preempt_disable();(禁用核心搶佔),但進去後會發現非搶佔定義為#define preempt_disable()        do { } while (0)
     spinlock最初就是為多處理器系統設計的。這裡有一條很重要的原則,上面兩本書都沒有提到過,那就是不能在可能交出CPU控制權的程式碼上執行,被排程,休眠都不可以。
    核心從2.6開始就支援核心搶佔,對於非核心搶佔系統,核心程式碼可以一直執行,直到完成,也就是說當程序處於核心態時,是不能被搶佔的(當然,運行於核心態的程序可以主動放棄CPU,比如,在系統呼叫服務例程中,由於核心程式碼由於等待資源而放棄CPU,這種情況叫做計劃性程序切換(planned process switch))。但是,對於由非同步事件(比如中斷)引起的程序切換,搶佔式核心與非搶佔式是有區別的,對於前者叫做強制性程序切換(forced process switch)。 那麼單處理器非搶佔核心下,核心程序是可以不主動交出CPU的。

    在中斷使用spinlock,如果其他中斷可能使用你的保護區域,那麼可以禁止本地中斷,即同一CPU的中斷。那為什麼不用禁止其他CPU的中斷呢,假使另一CPU嘗試持有鎖而自旋,但由於是不同CPU,所以持有鎖的CPU總會執行完,所以不會永遠自旋下去。但是本地中斷打斷後自旋,那麼將無法恢復,因為持有鎖的程式碼永遠沒有機會釋放鎖。