Linux驅動中併發問題的相關函數

前言

目前我們接觸到的複雜點的系統都是SMP(對稱多處理器)和preempt(搶佔式)的。這篇文章我們會講解好幾種情況下,處理驅動併發問題的函數應該怎麼使用,避免大材小用或者用得不巧當造成沒有效果。

正文

1、進程和中斷上下文,單CPU,非搶佔內核

這種情況比較簡單,因爲是非搶佔的,所以同一個CPU上的兩個進程之間不用相互顧忌對方,而且又是單CPU的,又不用怕其它CPU的中斷來打擾,所以只需要提防是否有本CPU的中斷過來就行,我們可以使用下面的函數。

local_irq_disable();  /*disable interrupts in local CPU*/
/*do something*/
local_irq_enable(); /*enable interrupts in local CPU*/

但是上面的函數會有問題,因爲執行local_irq_enable後會重新使能系統中斷,而不是恢復之前的中斷狀態,我們舉個例子說明一下。

進程上下文
local_irq_disable();
/*critical section*/

/*在關閉中斷過程中,再次關閉中斷*/
local_irq_disable();
local_irq_enable();  /* 這時候本地中斷已經又開啓了,第一個關閉中斷的操作已經無效*/

local_irq_enable(); /*這個已經無關緊要,前面已經全部打開了*/

所以爲了解決這種的問題,kernel爲我們提供了另外一組函數:

unsigned long flags;
local_irq_save(flags);
/*critical section*/
local_irq_restore(flags);

這兩個宏相對於local_irq_disable和local_irq_enable最大的區別在於,local_irq_save會在關閉中斷前,將處理器當前的標誌位保持在一個unsigned long flags中,在調用local_irq_restore時,在將保存的flags恢復到處理器的FLAGS寄存器中。這樣做是爲了防止在一個關閉中斷的環境中因爲調用local_irq_disable和local_irq_enable破壞之前的中斷響應狀態。如果調用鏈中有多個函數需要禁止中斷, 應該使用local_irq_save。

後面啊在2.3小節,我們同樣能看到類似的問題,看一下那個例子的說明,我們就應該明白爲什麼在開閉中斷前要保存中斷狀態,開啓中斷不是全部放開而是恢復之前的狀態了。

2、進程和中斷上下文,SMP,搶佔內核

這種場景是遇到的最複雜的場景了,我們先一步步來,由淺入深的分析。

2.1、

如果我們只考慮SMP情況下,在CPU0上執行的進程A,不希望運行在CPU1的進程B修改自己的數據的話,可以使用下面的函數:

extern spinlock_t lock;
// ...
spin_lock(&lock);
// critical section
spin_unlock(&lock);

在大部分場景下,這麼執行是正確的,但是我們考慮如果,在進程A執行的時候有中斷髮生呢?

2.2、

進程上下文                              硬件中斷
/* in normal run level */
extern spinlock_t lock;
spin_lock(&lock);
/* critical section */
                                       /* interrupted by IRQ */
                                       extern spinlock_t lock;
                                       spin_lock(&lock);

在一個進程的上下文,發生了硬件中斷,而且在中斷程序中也申請了同樣的lock,這時候就發生了死鎖,爲了解決這種問題,可以使用下面的函數

extern spinlock_t lock;
spin_lock_irq(&lock); /* 先關閉當前CPU中斷再申請鎖,避免發生中斷產生上述的死鎖問題 */
/* critical section */
spin_unlock_irq(&lock);

現在的系統是做的越來越複雜,上面的場景還不能保證完全沒有問題,我們看一下下面的另外一種場景。

2.3、

進程上下文                       硬件中斷
spin_lock_irq(&lock1);
spin_lock_irq(&lock2);
spin_unlock_irq(&lock2);
                                spin_lock(&lock1)   /*因爲lock1被佔用了,所以無法獲取自旋鎖而不停等待,導致了死鎖的情況*/
                                spin_unlock(&lock1)
spin_unlock_irq(&lock1); 

因爲spin_unlock_irq是將本地的中斷都開啓,所以執行完spin_unlock_irq(&lock2)後,硬件又可以申請中斷了,但是這時候lock1還處於臨界區,所以如果再次申請lock1的話,就會發生死鎖了。爲了解決這個問題,可以使用下面的變體函數:

unsigned long flags;
spin_lock_irqsave(&lock, flags);
/* do something */
spin_unlock_irqrestore(&lock, flags);     

參考鏈接

https://blog.csdn.net/wesleyluo/article/details/8807919

https://www.byteisland.com/%E8%87%AA%E6%97%8B%E9%94%81-spin_lock%E3%80%81-spin_lock_irq-%E4%BB%A5%E5%8F%8A-spin_lock_irqsave-%E7%9A%84%E5%8C%BA%E5%88%AB/
 

 

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