linux核心 訊號量與自旋鎖、延時函式比較
在驅動程式中,當多個執行緒同時訪問相同的資源時(驅動程式中的全域性變數是一種典型的共享資源),可能會引發"競態",因此我們必須對共享資源進行併發控制。Linux核心中解決併發控制的最常用方法是自旋鎖與訊號量(絕大多數時候作為互斥鎖使用)。
自旋鎖與訊號量"類似而不類",類似說的是它們功能上的相似性,"不類"指代它們在本質和實現機理上完全不一樣,不屬於一類。
自旋鎖不會引起呼叫者睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者就一直迴圈檢視是否該自旋鎖的保持者已經釋放了鎖,"自旋"就是"在原地打轉"。而訊號量則引起呼叫者睡眠,它把程序從執行佇列上拖出去,除非獲得鎖。這就是它們的
但是,無論是訊號量,還是自旋鎖,在任何時刻,最多隻能有一個保持者,即在任何時刻最多隻能有一個執行單元獲得鎖。這就是它們的"類似"。
鑑於自旋鎖與訊號量的上述特點,一般而言,自旋鎖適合於保持時間非常短的情況,它可以在任何上下文使用;訊號量適合於保持時間較長的情況,會只能在程序上下文使用。如果被保護的共享資源只在程序上下文訪問,則可以以訊號量來保護該共享資源,如果對共享資源的訪問時間非常短,自旋鎖也是好的選擇。但是,如 果被保護的共享資源需要在中斷上下文訪問(包括底半部即中斷處理控制代碼和頂半部即軟中斷),就必須使用自旋鎖。
區別總結如下:
1、由於爭用訊號量的程序在等待鎖重新變為可用時會睡眠,所以訊號量適用於鎖會被長時間持有的情況。
2、相反,鎖被短時間持有時,使用訊號量就不太適宜了,因為睡眠引起的耗時可能比鎖被佔用的全部時間還要長。
3、由於執行執行緒在鎖被爭用時會睡眠,所以只能在程序上下文中才能獲取訊號量鎖,因為在中斷上下文中(使用自旋鎖)是不能進行排程的。
4、你可以在持有訊號量時去睡眠(當然你也可能並不需要睡眠),因為當其它程序試圖獲得同一訊號量時不會因此而死鎖,(因為該程序也只是去睡眠而已,而你最終會繼續執行的)。
5、在你佔用訊號量的同時不能佔用自旋鎖,因為在你等待訊號量時可能會睡眠,而在持有自旋鎖時是不允許睡眠的。
6、訊號量鎖保護的臨界區可包含可能引起阻塞的程式碼,而自旋鎖則絕對要避免用來保護包含這樣程式碼的臨界區,因為阻塞意味著要進行程序的切換,如果程序被切換出去後,另一程序企圖獲取本自旋鎖,死鎖就會發生。
7、訊號量不同於自旋鎖,它不會禁止核心搶佔(自旋鎖被持有時,核心不能被搶佔),所以持有訊號量的程式碼可以被搶佔,這意味著訊號量不會對排程的等待時間帶來負面影響。
除了以上介紹的同步機制方法以外,還有BKL(大核心鎖),Seq鎖等。
BKL是一個全域性自旋鎖,使用它主要是為了方便實現從Linux最初的SMP過度到細粒度加鎖機制。
Seq鎖用於讀寫共享資料,實現這樣鎖只要依靠一個序列計數器。
sem就是一個睡眠鎖.如果有一個任務試圖獲得一個已被持有的訊號量時,訊號量會將其推入等待佇列,然後讓其睡眠。這時處理器獲得自由去執行其它程式碼。當持有訊號量的程序將訊號量釋放後,在等待佇列中的一個任務將被喚醒,從而便可以獲得這個訊號量。訊號量一般在用程序上下文中.它是為了防止多程序同時訪問一個共享資源(臨界區).
spin_lock叫自旋鎖.就是當試圖請求一個已經被持有的自旋鎖.這個任務就會一直進行 忙迴圈——旋轉——等待,直到鎖重新可用(它會一直這樣,不釋放CPU,它只能用在短時間加鎖).它是為了防止多個CPU同時訪問一個共享資源(臨界區).它一般用在中斷上下文中,因為中斷上下文不能被中斷,也不能被排程.
自旋鎖對訊號量
需求 建議的加鎖方法
低開銷加鎖 優先使用自旋鎖
短期鎖定 優先使用自旋鎖
長期加鎖 優先使用訊號量
中斷上下文中加鎖 使用自旋鎖
持有鎖是需要睡眠、排程 使用訊號量
程序間的sem.執行緒間的sem與核心中的sem的功能就很類似.
程序間的sem,執行緒間的sem功能是一樣的.只是執行緒的sem,它在同一個程序空間,他的初始化,使用更方便.
程序間的sem,就是程序間通訊的一部分,使用semget,semop等系統呼叫來完成.
核心中的sem 被鎖定,就等於被呼叫的程序佔有了這個sem.其它程序就只能進行睡眠佇列.這與程序間的sem基本一致.
區別 |
Spin_lock |
semaphore |
保護的物件 |
一段程式碼 |
一個裝置(必要性不強), 一個變數, 一段程式碼 |
保護區可被搶佔 |
不可以(會被中斷打斷) |
可以。 |
可允許在保護物件(程式碼)中休眠 |
不可以 |
可以。但最好不這樣。 |
保護區能否被中斷打斷 |
可以,這樣容易引發死鎖。 最好是關了中斷再使用此鎖。 因為有可能中斷處理例程也需要得到同一個鎖。 |
可以。 |
其它功能 |
可完成同步,有傳達資訊的能力。 |
|
試圖佔用鎖不成功後,程序的表現 |
不放開CPU,自己自旋。 |
進入一個等待佇列。 |
釋放鎖後,還有其它程序等待時,核心如何處理 |
哪個程序得到執行的權力,它就得到了鎖。 |
從等待佇列中選一個出來佔用此sem. |
核心對使用者的要求 |
被保護的程式碼執行時間要短,是原子的, 不能主動的休眠。 不能呼叫有可以休眠的核心函式。 |
|
風險 |
發生死鎖 |
|
不允許鎖的持有者二次請求同一個鎖。 |
不允許鎖的持有者二次請求同一個鎖。 |
訊號量在生產者與消費者模式中可以進行同步。
當sem的down和UP分別出現在對立函式中(讀,寫函式),其實這就是在傳達一種資訊。表示當前是否有資料可讀的資訊。
read_somthing()
{
down(裝置) 佔用了此裝置 此時沒有其它人都使用此裝置上的所有操作(函式)
if(有資料)
{
讀完它。
()
}
else
{
up(裝置)
down(有資料的sem)sem=1表示有資料,為0表示無資料。
}
}
write_somthing()
{
down(裝置) 佔用了此裝置 此時沒有其它人都使用此裝置上的所有操作(函式)
if(有資料)
{
不寫。
up(裝置)
return
}
else
{
寫入資料
up(有資料的sem)sem=1表示有資料,為0表示無資料。
up(裝置)
return;
}
}
總結:
訊號量適用於長時間片段,可能會睡眠(掛起排程) 所以只能用在程序上下文,不能用在中斷上下文。
自旋鎖使用於短時間片段 不會睡眠(掛起排程)抱著cpu不放, 用在中斷上下文 但是必須先關閉本地中斷,否則很可能因為
獲取不到自旋鎖又抱著cpu不讓別人持有而釋放自旋鎖從而陷入死鎖。
訊號量可以用的前提下儘量用訊號量,萬不得已(中斷中)使用自旋鎖,短時間片段比較適合自旋鎖,排程(程序之間的切換)本身
佔用時間,自旋等待的時間很短時就沒必要排程了,這時選用自旋鎖死抱cpu不放比較好。
時間函式
忙等待(一直消耗cpu)
ndelay、udelay、mdelay
unsigned long delay = jiffies + 100;
while(time_before(jiffies,delay));
睡著等待(不會一直消耗cpu)
msleep()、msleep_interruptible()、ssleep()、interruptible_sleep_on_timeout();