1. 程式人生 > >linux裝置驅動-自旋鎖和中斷遮蔽

linux裝置驅動-自旋鎖和中斷遮蔽

自旋鎖

自旋鎖:為防止多處理器併發引入的一種鎖
在核心中,廣泛應用於中斷處理部分

4中自旋鎖:

  • 自旋鎖
  • 讀寫自旋鎖
  • 順序鎖

順序鎖(seqlock)是對讀寫鎖的一種優化,若使用順序鎖,讀執行單元絕不會被寫執行單元阻塞,也就是說,讀執行單元可以在寫執行單元對被順序鎖保護的共享資源進行寫操作時仍然可以繼續讀,而不必等待寫執行單元完成寫操作,寫執行單元也不需要等待所有讀執行單元完成讀操作才去進行寫操作。
但是,寫執行單元與寫執行單元之間仍然是互斥的,即如果有寫執行單元在進行寫操作,其他寫執行單元必須自旋在那裡,直到寫執行單元釋放了順序鎖。

如果讀執行單元在讀操作期間,寫執行單元已經發生了寫操作,那麼,讀執行單元必須重新讀取資料,以便確保得到的資料是完整的。這種鎖在讀寫同時進行的概率比較小時,效能是非常好的,而且它允許讀寫同時進行,因而更大地提高了併發性。

順序鎖有一個限制,它必須要求被保護的共享資源不含有指標,因為寫執行單元可能使得指標失效,但讀執行單元如果正要訪問該指標,將導致Oops

  • RCU (Read-Copy Update,讀-拷貝-更新)

RCU(Read-Copy Update,讀-拷貝-更新),它是基於其原理命名的。RCU 並不是新的鎖機制,它 對於Linux
核心而言是新的。早在20 世紀80 年代就有了這種機制,而在Linux 系統中,開發2.5.43 核心時引入該技術,並正式包含在2.6
核心中。

對於被 RCU 保護的共享資料結構,讀執行單元不需要獲得任何鎖就可以訪問它,不使用原子指令, 而且在除Alpha
的所有架構上也不需要記憶體柵(Memory
Barrier),因此不會導致鎖競爭、記憶體延遲以及流水線停滯。不需要鎖也使得使用更容易,因為死鎖問題就不需要考慮了。

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

RCU 可以看做讀寫鎖的高效能版本,相比讀寫鎖,RCU 的優點在於既允許多個讀執行單元同時訪問
被保護的資料,又允許多個讀執行單元和多個寫執行單元同時訪問被保護的資料。

但是,RCU 不能替代讀寫鎖,因為如果寫比較多時,對讀執行單元的效能提高不能彌補寫執行單元導 致的損失。因為使用RCU
時,寫執行單元之間的同步開銷會比較大,它需要延遲資料結構的釋放,複製
被修改的資料結構,它也必須使用某種鎖機制同步並行的其他寫執行單元的修改操作。

使用場景1:
自旋鎖主要針對 SMP 或單 CPU 單核心支援可搶佔的情況,使臨界區不受別的 CPU 和本 CPU 內的搶佔程序打擾,但是得到鎖的程式碼路徑在執行臨界區的時候,還可能受到中斷和底半部的影響。解決辦法是:

    spin_lock_irq() = spin_lock() + local_irq_disable() /* 關中斷加鎖 */
    spin_unlock_irq() = spin_unlock() + local_irq_enable()

    spin_lock_irqsave() = spin_lock() + local_irq_save()/*關中斷並儲存狀態字*/
    spin_lock_irqrestore() = spin_unlock() + local_irq_restore()/*開中斷並恢復狀態*/

    spin_lock_bh() = spin_lock() + local_bh_disable() /* 關底半部加鎖 */
    spin_unlock_bh() = spin_unlock() + local_bh_enable()

1.3 例子:

使用自旋鎖實現一個裝置只能被一個程序開啟:

int flag = 0;

struct hello_device
{
    char data[128];
    spinlock_t lock;
    struct cdev cdev;
} hello_device;


static int hello_open (struct inode *inode, struct file *file)
{
    spin_lock(&hello_device.lock);
    if ( flag )
    {
        spin_unlock(&hello_device.lock);
        return -EBUSY;
    }
    flag++;
    spin_unlock(&hello_device.lock);

    printk (KERN_INFO "Hey! device opened\n");

    return 0;
}

static int hello_release (struct inode *inode, struct file *file)
{
    spin_lock(&hello_device.lock);
    flag--;
    spin_unlock(&hello_device.lock);
    printk (KERN_INFO "Hmmm... device closed\n");

    return 0;
}

中斷遮蔽

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

local_irq_disable() //遮蔽中斷
...
critical section //臨界區
...
local_irq_enable() //開中斷

與 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()。

#