Linux 設備驅動中的併發控制

首先我們來了解下爲什麼要對設備驅動中引入併發控制,這個問題簡單的理解就是設備資源只有一個,但是在同時間內,有好多的進程需要訪問它。對設備資源進行併發控制,讓其在同一時間只能被一個進程訪問,這樣就不能造成訪問的紊亂問題,解決了訪問資源的競態。

例如有一段內存空間,存儲了數據,有多個進程能夠對這段資源進行讀寫,當一個進程對資源進行寫操作的時候,來了另一個進程對資源進行讀操作,在沒有寫完這段內存的時候進行讀訪問,這種多個進程對資源的併發訪問就導致了競態的產生。

競態發生的類型:

(1)多核處理器容易發生CPU之間的對內存和外設的併發訪問。

(2)單內核處理的CPU的進程之間的訪問共享資源。

(3)中斷與進程之間的資源訪問。

對訪問的共享資源代碼區域成爲臨界區(critical sections)。那麼如果對這段臨界區的訪問進行保護,而避免產生競態呢?途徑有:

(1) 中斷屏蔽

       中斷屏蔽的使用方法:

  local_irq_disable();  // 屏蔽中斷

     ....

  critical section //  臨界區

    ..... 
  local_irq_enable();  // 開中斷

      場合:保證正在執行的內核路徑不被中斷處理程序所搶佔。是中斷和進程之間能夠產生的競態使用的方法。

      要求:臨界區的代碼應該非常短,時間長會導致其他中斷不能相應,後果很嚴重。

     注意:中斷屏蔽智能禁止和使能本CPU的中斷,多核的CPU不能解決競態,單獨使用時需要和自旋鎖配合使用。

(2)原子操作

    原子操作有位和整數原子操作兩種類型。

原子操作有好幾個函數,實現原子變量的設置、獲取、加減、自加自減等操作。

在這裏以實例的方式介紹:

/*這個例子是使用原子操作來實現設備只能被一個進程打開*/

// 首先定義一個原子變量
static atomic_t xxxx_available ATOMIC_INIT(1);
static int xxx_open(struct inode *inode,struct file*filp)
{     // atomic_dec_and_test() 函數是減去一時候爲零,爲零返回true,否則返回flase.
     // 如果設備沒有打開,測試返回TRUE,取反的話不執行if 語句下面的代碼。返回打開成功
     // 如果已經打開,返回false,取反爲1,執行if 
    if(!atomic_dec_and_test(&xxx_available)){
        atomic_inc(&xxx_available); 把減掉的1加上
        return -EBUSY; // 已經被打開了
    }
    ...
    return 0; 打開成功

}

static int xxx_release(struct inode *inode, struct file *filp)
{
    atomic_inc(&xxx_available); /* 釋放設備 */
    return 0;
}

(3)自旋鎖

“自旋鎖(Spin Lock)是一種典型的對臨界資源進行互斥訪問的手段,其名稱來源於它的工作方式。爲了獲得一個自旋鎖,在某CPU上運行的代碼需先執行一個原子操作,該操作測試並設置(Test-And-Set)某個內存變量。由於它是原子操作,所以在該操作完成之前其他執行單元不可能訪問這個內存變量。如果測試結果表明鎖已經空閒,則程序獲得這個自旋鎖並繼續執行;如果測試結果表明鎖仍被佔用,程序將在一個小的循環內重複這個“測試並設置”操作,即進行所謂的“自旋”,通俗地說就是“在原地打轉””--Linux驅動開發詳解中所說。

自旋鎖的使用:

/*
 1)定義自旋鎖:spinlock_t  lock;
 2) 初始化自旋鎖: spin_lock_init(lock);
 3) 獲取自旋鎖:spin_lock(lock);
               spin_trylock(lock); 這個函數是獲取是如果能立馬獲取的話返回 true ,否則false,是不等待,不原地旋轉。
 4) 釋放自旋鎖: spin_unlock(lock);




*/

使用模板:
/* 定義一個自旋鎖 */
spinlock_t lock;
spin_lock_init(&lock);
spin_lock (&lock) ; /* 獲取自旋鎖,保護臨界區 */
. . ./* 臨界區 */
spin_unlock (&lock) ; /* 解鎖 */

 場合:針對多核和單核的搶佔情況

注意:(1)自旋鎖能夠受到中斷和底半部的影響。因此和中斷有拓展的函數存在,形成整套的自旋鎖機制。這裏就不介紹了。

           (2)自旋鎖是忙等鎖,如果佔用鎖的時間既短暫的話,使用合理,長時間的話等待時間長降低了系統的性能。

           (3)遞歸使用自旋鎖會導致鎖死的情況發生。

           (4)在自旋鎖鎖定期間不能調用可能引起進程調度的函數。例如copy_from_user(), copy_to_user(), kmalloc(), msleep()。

          (5)衍生的讀寫自旋鎖、順序自旋鎖。

(4)信號量

     信號量是實現同步和互斥訪問的重要手段,使用過程有定義信號量、初始化信號量、獲取信號量及釋放信號量。當能獲取到信號量是進入臨界保護區,當不能獲取信號量是進入休眠狀態。由此可見和自旋鎖不同的是信號量不在原地旋轉等待,而是直接進入休眠。

struct semaphore sem;
void sema_init(struct semaphore *sem, int val);
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem);
int down_trylock(struct semaphore * sem);
void up(struct semaphore * sem);

場合:1)用於互斥,是對一段臨界區實現互斥訪問。

            2)用於同步,兩個進程之間的同步, 一個進程釋放信號量後,另一個進程獲取到信號量後再執行。(生產者和消費者問題)

注意:mutex也可以用於互斥,一般常使用信號量來同步,互斥傾向於mutex。

(5)互斥體

     mutex 的使用和信號量基本相同,實現互斥訪問的場合。

不同點: 互斥體是實現進程之間對資源的互斥訪問,如果互斥體搶佔失敗的情況下會進行進程之間的切換,當前的進程進入到睡眠態。而自旋鎖如果訪問失敗的話會在原地等待,自旋鎖釋放,不會進行進程切換,如果訪問臨界區時間短時,自旋鎖不進行進程切換,比較方便。如果在中斷中使用只能選擇自旋鎖。中斷中不能上下文切換,阻塞。

(6)完成量

到最後說下完成量,完成量是當一個進程執行某段代碼完成後,喚醒另一個進程執行的操作。

操作有定義完成量,初始化完成量,等待完成量和喚醒完成量。

使用過程是:

1)先定義,初始化 。。。。

2) 進程1 執行代碼,執行完了 喚醒完成量,complete(&done)。

3)進程2 wait_for_completion(&done),如果等待完成,則執行進程2下面的代碼。

在以後的筆記中會舉個併發控制的設備驅動加深理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章