linux的同步与互斥
臨界區(qū):(critical?region)
所謂臨界區(qū)就是訪問和操作共享數(shù)據(jù)的代碼段。
并發(fā)有偽并發(fā)(單處理器)和真并發(fā)(多處理器)之分,但是都會(huì)造成競爭條件。
同步:(synchronization)
避免并發(fā)(多個(gè)執(zhí)行線程并發(fā)訪問同一個(gè)資源)和防止競爭條件(兩個(gè)執(zhí)行線程處于同一臨界區(qū))被稱為同步。
用戶空間之所以需要同步,是因?yàn)橛脩舫绦驎?huì)被調(diào)度程序搶占和重新調(diào)度。
造成并發(fā)執(zhí)行的原因:
1?中斷
2?軟中斷和tasklet
3?內(nèi)核搶占——任務(wù)的優(yōu)先級
4?睡眠及用戶空間的同步
5?SMP
在編寫代碼的開始階段就應(yīng)該設(shè)計(jì)恰當(dāng)?shù)逆i
我們要給數(shù)據(jù)而不是代碼加鎖。大多數(shù)內(nèi)核數(shù)據(jù)結(jié)構(gòu)都需要加鎖!而局部自動(dòng)變量,動(dòng)態(tài)分配的數(shù)據(jù)結(jié)構(gòu)不需要加鎖。
Linux信號量的實(shí)現(xiàn)
0.概念:
信號量:?一個(gè)信號量本質(zhì)上是一個(gè)整數(shù)值,?它和一對函數(shù)聯(lián)合使用,?這一對函數(shù)通常成為P和V,?也就是我們所說的P/V操作.
·?當(dāng)一個(gè)進(jìn)程希望進(jìn)入臨界區(qū)時(shí),?對臨界區(qū)的信號量執(zhí)行P操作:
·?如果信號量的值大于0,?則該值會(huì)減1,?而進(jìn)程可以繼續(xù);
·?如果信號量的值等于0(或更小),?進(jìn)程必須等待直到其他人釋放該信號量.
·?當(dāng)一個(gè)進(jìn)程完成對臨界區(qū)的操作時(shí),?對臨界區(qū)信號量執(zhí)行V操作:
?將信號量的值加1,?并在必要時(shí)喚醒等待的進(jìn)程.
·?在這種模式下,?一個(gè)信號量有時(shí)也稱為一個(gè)"互斥體"(mutex),?它是互斥的簡稱.
規(guī)則:
·?當(dāng)信號量用于互斥時(shí)(即避免多個(gè)進(jìn)程同時(shí)操作一個(gè)臨界區(qū)),?信號量的值應(yīng)該初始化為1.
·?信號量在任何時(shí)刻只能由單個(gè)進(jìn)程或線程擁有.
1.?定義:
頭文件:?<asm/semaphore.h>
數(shù)據(jù)類型:?struct?semaphore
直接創(chuàng)建:
?void?sema_init(struct?semaphore?*sem,?int?val);?/*?其中val是信號量的初始值?*/
輔助宏:
?DECLARE_MUTEX(name);?????????????????/*?把一個(gè)稱為name的信號量變量初始化為1?*/
DECLARE_MUTEX_LOCKED(name);?/*?把一個(gè)稱為name的信號量變量初始化為0?*/
動(dòng)態(tài)分配:
/*?用于運(yùn)行時(shí)的初始化?*/
void?init_MUTEX(struct?semaphore?*sem);
void?init_MUTEX_LOCKED(struct?semaphore?*sem);
在Linux世界中,?P函數(shù)被稱為down,?指的是該函數(shù)減小了信號量的值,?它也許會(huì)將調(diào)用者置于休眠狀態(tài),?然后等待信號量變得可用,?之后再授予調(diào)用者對被保護(hù)資源的訪問權(quán)限.?down函數(shù)有三個(gè)版本:
void?down(struct?semaphore?*sem);?????/*?減小信號量的值,?并在必要時(shí)一直等待。無法獲得信號量時(shí),會(huì)導(dǎo)致調(diào)用進(jìn)程休眠*/
作為通常規(guī)則,?我們不應(yīng)該使用非中斷版本.?非中斷操作是建立不可殺進(jìn)程的好方法(ps輸出中的"D?state")
void?down_interruptible(struct?semephore?*sem);??/*?可中斷版本,?常用。?無法獲得信號量時(shí),會(huì)導(dǎo)致調(diào)用進(jìn)程休眠*/
可中斷版本幾乎是我們始終要使用的版本。
使用該函數(shù),?如果得到,返回0;如果操作被中斷,?該函數(shù)會(huì)返回非0值(-EINTR),?而調(diào)用者不會(huì)擁有該信號量.
因此對該函數(shù)的正確使用需要始終檢查返回值,?并做出相應(yīng)的響應(yīng).
void?down_trylock(struct?semaphore?*sem);
/*?永遠(yuǎn)不會(huì)休眠,?如信號量在調(diào)用時(shí)不可獲得,?立即返回非0值?*/?
后來的內(nèi)核又增加了:
int?down_killable(struct?semaphore?*sem);
/*無法獲得信號量時(shí),會(huì)導(dǎo)致調(diào)用進(jìn)程休眠*,操作可被fatal信號中斷,?函數(shù)會(huì)返回非0值(-EINTR),?而調(diào)用者不會(huì)擁有該信號量.?*/
int?down_timeout(struct?semaphore?*sem,?long?jiffies);
/*在指定時(shí)間內(nèi)獲取該信號量,會(huì)導(dǎo)致調(diào)用進(jìn)程休眠,無法獲取時(shí)返回-ETIME*/
當(dāng)一個(gè)線程成功調(diào)用down函數(shù)的某個(gè)版本之后,?就稱為該線程擁有了該信號量,?可以訪問被該信號量保護(hù)的臨界區(qū).?當(dāng)互斥操作完成后,?必須釋放該信號量.
Linux的V函數(shù)是up:
/*?調(diào)用up之后,?調(diào)用者不再擁有該信號量?*/
void?up(struct?semaphore?*sem);
2.?舉例:
我們擁有一個(gè)共享數(shù)據(jù)結(jié)構(gòu):
struct?st_data
{
????char?name[32];
????char?data[128];
????int?data_len;
};
這個(gè)數(shù)據(jù)結(jié)構(gòu)被多個(gè)進(jìn)程同時(shí)訪問.
為了避免這些進(jìn)程在訪問該結(jié)構(gòu)時(shí)產(chǎn)生競態(tài),?我們在該結(jié)構(gòu)的底部為其加上信號量:
struct?st_data
{
????char?name[32];??????????????/*?name?*/
????char?data[128];??????????????/*?data?*/
????int?data_len;??????????????????/*?data?length?*/
????struct?semaphore?sem;?/*?semaphore?*/
};
信號量在使用前必須進(jìn)行初始化,?而且是在共享數(shù)據(jù)其他部分可用前初始化.?因此,?我們在其他數(shù)據(jù)賦值之前調(diào)用init_MUTEX,?否則會(huì)建立一個(gè)競態(tài),?即在信號量準(zhǔn)備好之前,?有代碼可能會(huì)訪問它們.
st_data?data;
init_MUTEX(&data->sem);
setup_data(&data);?/*?初始化數(shù)據(jù)?*/
...
接下來,?我們必須仔細(xì)檢查代碼,?確保在不擁有該信號量的時(shí)候不會(huì)訪問data數(shù)據(jù)結(jié)構(gòu).?例如,?在data_write的開始處加入:
if?(down_interruptible(&data->sem))???
?????return?-ERESTARTSYS;
這是檢查down_interruptible的返回值,?如果返回非0值,?說明操作被中斷.?這種情況下,?通常要做的工作是返回-ERESTARTSYS.?在得到這個(gè)返回值后,?內(nèi)核會(huì)從頭重新啟動(dòng)該調(diào)用,?或者將該錯(cuò)誤返回給用戶.
如果我們返回-ERESTARTSYS,?則必須首先撤銷已經(jīng)做出的修改,?這樣,?系統(tǒng)調(diào)用才可正確重試.?如果無法撤銷這些操作,?則應(yīng)該返回-EINTR,?表明中斷.
不管data_write能否完成其他工作,?它都必須釋放信號量:
out:
????up(&data->sem);
????return?retval;
在data_write中有幾個(gè)地方可能會(huì)產(chǎn)生錯(cuò)誤,?包括內(nèi)存分配失敗等.?在這些情況下,?代碼會(huì)執(zhí)行goto?out,?確保正確的完成信號量的釋放工作.
讀取/寫入信號量
信號量對所有調(diào)用者執(zhí)行互斥操作,?而不管線程想做什么(讀/寫).?正如這樣,?我們可以把任務(wù)劃分為兩種類型:?讀取和寫入.?多個(gè)并發(fā)的讀取應(yīng)該是被允許的,?因?yàn)橹蛔x任務(wù)可并行完成他們的工作,?這樣做可以大大提高性能.
如此,?便有了Linux內(nèi)核提供的一種特殊信號量類型,?稱為"rwsem"(reader/writer?semaphore).?雖然在驅(qū)動(dòng)程序中使用rwsem的機(jī)會(huì)相對比較少,?但偶爾也比較有用.
頭文件:?<linux/rwsem.h>
數(shù)據(jù)類型:?struct?rw_semaphore
一個(gè)rwsem對象必須用一下函數(shù)顯式地初始化:
void?init_rwsem(struct?rw_semaphore?*sem);
對受保護(hù)資源的只讀訪問,?可和其他讀取者并發(fā)地訪問.?可用的接口有:
void?down_read(struct?rw_semaphore?*sem);/*?可能會(huì)將調(diào)用進(jìn)程置于不可中斷的休眠?*/
int?down_read_trylock(struct?rw_semaphore?*sem);/*?不會(huì)阻塞等待,授予訪問時(shí)返回非0,?其他情況返回0??注意他的返回值用法和其它函數(shù)的不同*/
void?up_read(struct?rw_semaphore?*sem);?/*?釋放rw_sem對象?*/
針對寫入者的接口類似于讀取者:
void?down_write(struct?rw_semaphore?*sem);
int?down_write_trylock(struct?rw_semaphore?*sem);
void?up_write(struct?rw_semaphore?*sem);
這3個(gè)函數(shù)與讀取者的對應(yīng)函數(shù)行為相同,?他們提供的是寫入訪問.
/*?當(dāng)某個(gè)快速改變獲得寫入者鎖,
??*其后有更長時(shí)間的只讀訪問的時(shí)候,
??*我們可以調(diào)用該函數(shù)來允許其他讀取者訪問?*/
void?downgrade_write(struct?rw_semaphore?*sem);
讀取/寫入信號量特點(diǎn):
·?一個(gè)rwsem可以允許一個(gè)寫入者或無限多個(gè)讀取者擁有該信號量.
·?寫入者具有更高的優(yōu)先級,?某個(gè)寫入者試圖進(jìn)入臨界區(qū),?在其完成工作之前,?不會(huì)允許讀取者獲得訪問.
·?如果大量寫入者競爭該信號量,?會(huì)導(dǎo)致讀取者"餓死".
·?最好在很少需要寫訪問且寫入者只會(huì)短期擁有信號量什使用rwsem.
Completion(完成量)
有時(shí),?由于信號量輕松up操作之后,?會(huì)產(chǎn)生過長時(shí)間的操作,?為了避免信號量執(zhí)行down操作導(dǎo)致的長時(shí)間阻塞,?內(nèi)核提供了一組completion(完成)接口解決這種問題.
Completion是一種輕量級的機(jī)制,?它允許一個(gè)線程告訴另一個(gè)線程某個(gè)工作已經(jīng)完成.
頭文件:?<linux/completion.h>
數(shù)據(jù)類型:?struct?completion
一個(gè)completion對象可以通過兩種方法創(chuàng)建和初始化:
DECLARE_COMPLETION(my_completion);
或者,?如果必須動(dòng)態(tài),?則使用:
struct?completion?my_completion;
/*?...?*/
init_completion(&my_completion);
對completion操作的接口有:
等待completion的操作可以用:
/*?執(zhí)行一個(gè)非中斷等待?*/
void?wait_for_completion(struct?completion?*c);
實(shí)際的completion事件調(diào)用可以用:
/*?只會(huì)喚醒一個(gè)等待線程?*/
void?complete(struct?completion?*c);
/*?喚醒所有等待線程?*/
void?complete_all(struct?completion?*c);
但在大多數(shù)情況下,?只會(huì)有一個(gè)等待者,?此時(shí)兩個(gè)函數(shù)效果相同.
一個(gè)completion通常是一個(gè)單次(one-shot)設(shè)備,?它只會(huì)被使用一次然后被丟棄.觸發(fā)事件明確時(shí),?如果沒有使用complete_all,?可以重復(fù)使用一個(gè)completion結(jié)構(gòu).如果使用了complete_all,?則必須在重復(fù)使用該結(jié)構(gòu)之前重新初始化它.
可以用下面的宏進(jìn)行重新初始化Completion
INIT_COMPLETION(struct?completion?*c)?;
Completion機(jī)制的典型是模塊退出時(shí)的內(nèi)核線程終止.?這時(shí),?驅(qū)動(dòng)程序內(nèi)部由一個(gè)while(1)循環(huán)完成,?當(dāng)內(nèi)核準(zhǔn)備清楚該模塊時(shí),?exit函數(shù)會(huì)令線程退出并等待completion.?此時(shí),?內(nèi)核可調(diào)用一個(gè)特殊函數(shù):
void?complete_and_exit(struct?completion?*c,?long?retval);
/*?其中retval是exit的返回值,?可用于錯(cuò)誤代碼檢查?*/
linux中自旋鎖(spinlock)的實(shí)現(xiàn)
信號量對互斥來講是非常有用的,?但它并不是內(nèi)核提供的唯一工具.
大多數(shù)鎖定通過稱為"自旋鎖"的機(jī)制實(shí)現(xiàn).?和信號量不同,?自旋鎖可以在不能休眠的代碼中使用,?比如:?中斷處理例程.?在正確使用的情況下,?自旋鎖通常可以提供比信號量更高的性能,?但它也有一組不同的使用限制.
1.?概念:
自旋鎖在概念上非常簡單,?一個(gè)自旋鎖就是一個(gè)互斥設(shè)備,?它只有兩個(gè)值:?"鎖定"和"解鎖".?它通常實(shí)現(xiàn)為某個(gè)整數(shù)值中的單個(gè)位.
·?希望獲得某特定鎖的代碼,?測試相關(guān)的位.?(原子操作)
·?如果鎖可用,?則"鎖定"位被設(shè)置,?而代碼繼續(xù)進(jìn)入臨界區(qū).
·?相反,?如果鎖被其他人獲得,?則代碼進(jìn)入忙循環(huán)并重復(fù)檢查這個(gè)鎖,?直到該鎖可用為止.
這個(gè)循環(huán)就是自旋鎖的"自旋"部分.?當(dāng)存在自旋鎖時(shí),?等待執(zhí)行忙循環(huán)的處理器做不了任何有用的工作.
核心原則:任何擁有自旋鎖的代碼都必須是原子的。它不能休眠。
另外一個(gè)重要原則:自旋鎖必須在可能的最短時(shí)間內(nèi)擁有。
在擁有鎖時(shí),要注意調(diào)用的每一個(gè)函數(shù)是否會(huì)引起睡眠。是否會(huì)有中斷服務(wù)程序獲得相同的鎖(需禁止本地CPU中斷)
2.?自旋鎖基本API:
頭文件:?<linux/spinlock.h>
數(shù)據(jù)類型:?spinlock_t
初始化,?以下兩種方法:
??????靜態(tài)(編譯時(shí)):?spinlock_t?my_lock?=?SPIN_LOCK_UNLOCKED;
??????動(dòng)態(tài)(運(yùn)行時(shí)):?void?spin_lock_init(spinlock_t?*lock);
獲得鎖:?void?spin_lock(spinlock_t?*lock);
釋放鎖:?void?spin_unlock(spinlock_t?*lock);?
3.?自旋鎖其他API:
鎖定一個(gè)自旋鎖的函數(shù)實(shí)際有4個(gè):
void?spin_lock(spinlock_t?*lock);?????/*?基本函數(shù)?*/
void?spin_lock_irqsave(spinlock_t?*lock,?unsigned?long?flags);/*?會(huì)在獲得自旋鎖之前禁止本地中斷,?而先前的中斷狀態(tài)保存在flags中?*/
void?spin_lock_irq(spinlock_t?*lock);/*?同上,?但無跟蹤標(biāo)志?*/
void?spin_lock_bh(spinlock_t?*lock);/*?在獲得鎖之前禁止軟件中斷?*/
對應(yīng)的,?也有4個(gè)unlock函數(shù):
void?spin_unlock(spinlock_t?*lock);
/*?這里面的flags必須和傳遞給spinlock_lock_irqsave的是同一個(gè)變量,
?*?還必須在同一函數(shù)中調(diào)用,?否則代碼可能在某些架構(gòu)上出現(xiàn)問題?*/
void?spin_unlock_irqstore(spinlock_t?*lock,?unsigned?long?flags);
void?spin_unlock_irq(spinlock_t?*lock);
void?spin_unlock_bh(spinlock_t?*lock);
同時(shí),?還有2個(gè)非阻塞的自旋鎖操作:
int?spin_trylock(spinlock_t?*lock);??????/*成功獲得返回非0,否則返回0*/
int?spin_trylock_bh(spinlock_t?*lock);?/*成功獲得返回非0,否則返回0*/
對禁止中斷的情況,?沒有對應(yīng)的"try"版本.
注意:《linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》9.2節(jié)?自旋鎖?中論述:
自旋鎖更多的是為多處理器提供防止并發(fā)訪問所需要的保護(hù)機(jī)制。
在單處理器機(jī)器上,編譯的時(shí)候并不會(huì)加入自旋鎖。它僅僅被當(dāng)作一個(gè)設(shè)置內(nèi)核搶占機(jī)制是否被啟用的開關(guān)。如果禁止內(nèi)核搶占,那么在編譯時(shí)自旋鎖會(huì)被完全剔除出內(nèi)核。
觀察內(nèi)核代碼發(fā)現(xiàn):在自旋鎖與CONFIG_SMP和CONFIG_PREEMPT兩個(gè)配置選項(xiàng)有很大的關(guān)聯(lián)。
而在自旋鎖的各個(gè)操作函數(shù)中都會(huì)調(diào)用preempt_disable()來禁止搶占(若CONFIG_PREEMPT選項(xiàng)關(guān)閉,則preempt_disable為空操作)
內(nèi)核搶占代碼使用自旋鎖作為非搶占區(qū)域的標(biāo)記。
Linux?2.4.x及以前的版本都是非搶占式內(nèi)核方式,如果編譯成單處理器系統(tǒng),在同一時(shí)間只有一個(gè)進(jìn)程在執(zhí)行,除非它自己放棄,不然只有通過"中斷"才能中斷其執(zhí)行。因此,在單處理器非搶占式內(nèi)核中,如果需要修改某個(gè)重要的數(shù)據(jù)結(jié)構(gòu),或者執(zhí)行某些關(guān)鍵代碼,只需要禁止中斷。
但是在對稱多處理器,僅僅禁止某個(gè)CPU的中斷是不夠的,當(dāng)然我們也可以將所有CPU的中斷都禁止,但這樣做開銷很大,整個(gè)系統(tǒng)的性能會(huì)明顯下降。
此外,即使在單處理器上,如果內(nèi)核是搶占式的,也可能出現(xiàn)不同進(jìn)程上下文同時(shí)進(jìn)入臨界區(qū)的情況。為此,Linux內(nèi)核中提供了"自旋鎖(spinlock)"的同步機(jī)制。
?自旋鎖和信號量的比較
| 需求 | 建議的加鎖方法 |
| 低開銷加鎖 | 優(yōu)先使用自旋鎖 |
| 短期加鎖 | 優(yōu)先使用自旋鎖 |
| 長期加鎖 | 優(yōu)先使用信號量 |
| 中斷上下文加鎖 | 使用自旋鎖 |
| 持有鎖需要睡眠 | 使用信號量 |
讀取/寫入自旋鎖
頭文件:?<linux/spinlock.h>
數(shù)據(jù)類型:?rwlock_t
初始化,?以下兩種方法:
rwlock_t?my_lock?=?RW_LOCK_UNLOCKED;????/*Static?way*/
rwlock_t?my_lock;
rw_lock_init(&my_lock);??/*Dynamic?way*/
讀取相關(guān)的自旋鎖的函數(shù):
void?read_lock(rwlock_t?*lock);
void?read_lock_irqsave(rwlock_t?*lock,?unsigned?long?flags);
void?read_lock_irq(rwlock_t?*lock);/*?同上,?但無跟蹤標(biāo)志?*/
void?read_lock_bh(rwlock_t?*lock);/*?在獲得鎖之前禁止軟件中斷?*/
void?read_unlock(rwlock_t?*lock);
voidread_unlock_irqrestore(rwlock_t?*lock,?unsigned?long?flags);
voidread_unlock_irq(rwlock_t?*lock);
voidread_unlock_bh(rwlock_t?*lock);
沒有read_trylock函數(shù)可用。
寫入相關(guān)的自旋鎖的函數(shù):
void?write_lock(rwlock_t?*lock);
voidwrite_lock_irqsave(rwlock_t?*lock,?unsigned?long?flags);
voidwrite_lock_irq(rwlock_t?*lock);/*?同上,?但無跟蹤標(biāo)志?*/
voidwrite_lock_bh(rwlock_t?*lock);/*?在獲得鎖之前禁止軟件中斷?*/
voidwrite_unlock(rwlock_t?*lock);
voidwrite_unlock_irqrestore(rwlock_t?*lock,?unsigned?long?flags);
voidwrite_unlock_irq(rwlock_t?*lock);
void?write_unlock_bh(rwlock_t?*lock);
排隊(duì)自旋鎖(FIFO?Ticket?Spinlock)
排隊(duì)自旋鎖(FIFO?Ticket?Spinlock)是?Linux?內(nèi)核?2.6.25?版本中引入的一種新型自旋鎖,它解決了傳統(tǒng)自旋鎖由于無序競爭導(dǎo)致的“公平性”問題。
參考:Linux?內(nèi)核的排隊(duì)自旋鎖(FIFO?Ticket?Spinlock)
內(nèi)核處理方式我的理解如下:
將自旋鎖的計(jì)數(shù)整數(shù)分成兩個(gè)域:Next域和Owner域,兩個(gè)域的位數(shù)相同。分別保存未來鎖申請者和當(dāng)前鎖持有者的票據(jù)序號(Ticket?Number),初始時(shí)Next?=?Owner?=?0。
當(dāng)申請者申請排隊(duì)自旋鎖時(shí),它會(huì)獲得一個(gè)票據(jù)號,是當(dāng)前Next值,然后Next增1;然后比較申請者的票據(jù)號和Owner值,若相等,則申請者可以獲得該鎖,否則忙等待;
當(dāng)申請者處理完后釋放排隊(duì)自旋鎖時(shí),會(huì)使該鎖的Owner值增1。等待的下一個(gè)申請者會(huì)發(fā)現(xiàn)這一變化,從忙等待狀態(tài)中退出,并獲得該鎖。
線程將嚴(yán)格地按照申請順序拿到Next票據(jù)號,然后隨著Owner的增加,票據(jù)號?=?Owner的申請者依次獲取排隊(duì)自旋鎖,從而完全解決了“不公平”問題。
排序自旋鎖和銀行的拿號排隊(duì)的原理是相同的。
死鎖
產(chǎn)生死鎖的原因:資源競爭以及進(jìn)程推進(jìn)順序非法
產(chǎn)生死鎖的4個(gè)必要條件:
·?互斥條件
·?請求保持條件
·?不可剝奪條件
·?環(huán)路條件
死鎖的預(yù)防:預(yù)先靜態(tài)分配法?和?資源有序分配法
免鎖方法
1、循環(huán)緩沖區(qū)——免鎖算法
在設(shè)備驅(qū)動(dòng)程序中使用相當(dāng)普遍,特別是網(wǎng)絡(luò)適配器(處理交換數(shù)據(jù))
一個(gè)生產(chǎn)者將數(shù)據(jù)放入數(shù)組的結(jié)尾,消費(fèi)者從數(shù)組的另一端移走數(shù)據(jù),當(dāng)?shù)竭_(dá)數(shù)組尾部時(shí),生產(chǎn)者繞回到數(shù)組頭部。沒有多個(gè)生產(chǎn)者或消費(fèi)者的情況下,循環(huán)緩沖區(qū)不需要加鎖。
內(nèi)核中有一個(gè)通用的循環(huán)緩沖區(qū)實(shí)現(xiàn),參閱<linux/kfifo.h>
參考:Linux設(shè)備驅(qū)動(dòng)程序?qū)W習(xí)(3-補(bǔ))-Linux中的循環(huán)緩沖區(qū)
2、原子變量
有時(shí),共享的資源可能恰好是個(gè)簡單的整數(shù)值。
內(nèi)核提供了一種原子的整數(shù)類型,稱為:atomic_t
定義在:<asm/atomic.h>
這是一個(gè)與CPU架構(gòu)有關(guān)的變量。
atomic_t數(shù)據(jù)項(xiàng)必須只能通過下面的函數(shù)來訪問
初始化:
void?atomic_set(atomic_t?*v,?int?i);
atomic_t?v?=?ATOMIC_INIT(0);
讀取變量值:
int?atomic_read(atomic_t?*v);
增減操作:(注意不返回操作后的結(jié)果)
void?atomic_add(int?i,?atomic_t?*v);??/*?v=+i?*/
void?atomic_sub(int?i,?atomic_t?*v);?/*?v=-i?*/
void?atomic_inc(atomic_t?*v);???/*?v++?*/
void?atomic_dec(atomic_t?*v);??/*?v--?*/
附加操作:
操作后并測試原子值,若為0,則返回true,否則返回false
int?atomic_inc_and_test(atomic_t?*v);??
int?atomic_dec_and_test(atomic_t?*v);
int?atomic_sub_and_test(int?i,?atomic_t?*v);
操作后并測試原子值,若為負(fù),則返回true,否則返回false
int?atomic_add_negative(int?i,?atomic_t?*v);
操作后并返回原子值給調(diào)用者
int?atomic_add_return(int?i,?atomic_t?*v);
int?atomic_sub_return(int?i,?atomic_t?*v);
int?atomic_inc_return(atomic_t?*v);
int?atomic_dec_return(atomic_t?*v);
3、位操作
內(nèi)核提供了一組可原子地修改和測試單個(gè)位的函數(shù)。
原子位操作只要硬件允許,就可以使用單個(gè)機(jī)器指令來執(zhí)行,并且不需要禁止中斷。所以位操作也是依賴于具體的架構(gòu),在<asm/bitops.h>中聲明
nr通常定義為int型或unsigned?long;addr通常是unsigned?long*?或?void*
void?set_bit(nr,?void?*addr);???/*設(shè)置addr指向的數(shù)據(jù)項(xiàng)的第nr位*/
void?clear_bit(nr,?void?*addr);?/*清除addr指向的數(shù)據(jù)項(xiàng)的第nr位*/
void?change_bit(nr,?void?*addr);/*切換addr指向的數(shù)據(jù)項(xiàng)的第nr位*/
test_bit(nr,?void?*addr);/*返回指定位的當(dāng)前值,唯一不必原子方式實(shí)現(xiàn)的位操作函數(shù)*/
int?test_and_set_bit(nr,?void?*addr);??/*設(shè)定指定位,并返回指定位的先前值*/
int?test_and_clear_bit(nr,?void?*addr);/*清除指定位,并返回指定位的先前值*/
int?test_and_change_bit(nr,?void?*addr);/*切換指定位,并返回指定位的先前值*/
在新代碼中應(yīng)該優(yōu)先使用自旋鎖,因?yàn)樽孕i已經(jīng)很好的調(diào)試過,并且易讀性比位操作更好。
4、順序鎖(seqlock)
當(dāng)要保護(hù)的資源很小、很簡單、會(huì)頻繁被訪問而且寫入訪問很少發(fā)生且必須快速時(shí),就可以使用seqlock。
seqlock通常不能用于保護(hù)包含有指針的數(shù)據(jù)結(jié)構(gòu)。
seqlock在<linux/seqlock.h>中定義。
seqlock_t?lock1?=?SEQLOCK_UNLOCKED;
seqlock_t?lock2;
seqlock_init(&lock2);
seqlock允許讀取者對資源的自由訪問,但需要讀取者檢查是否和寫入著發(fā)生沖突,沖突發(fā)生時(shí),需要重試訪問資源。讀取者的代碼如下:
unsigned?int?seq;
do?{
???????seq?=?read_seqbegin(&the_lock);????/*?獲得一個(gè)(無符號的)整數(shù)順序值seq*/
??????/*?Do?what?you?need?to?do?*/
}?while?read_seqretry(&the_lock,?seq);??/*退出時(shí),seq和當(dāng)前值比較,若不等則重試*/
在中斷處理例程中,使用IRQ安全版本:
unsigned?int?read_seqbegin_irqsave(seqlock_t?*lock,?unsigned?long?flags);
int?read_seqretry_irqrestore(seqlock_t?*lock,?unsigned?int?seq,?unsigned?long?flags);
寫入者在進(jìn)入由seqlock保護(hù)的臨界區(qū)時(shí)獲得一個(gè)互斥鎖。(寫入鎖使用自旋鎖實(shí)現(xiàn))
void?write_seqlock(seqlock_t?*lock);
釋放鎖:
void?write_sequnlock(seqlock_t?*lock);
常用變種:
void?write_seqlock_irqsave(seqlock_t?*lock,?unsigned?long?flags);
void?write_seqlock_irq(seqlock_t?*lock);
void?write_seqlock_bh(seqlock_t?*lock);
void?write_sequnlock_irqrestore(seqlock_t?*lock,?unsigned?long?flags);
void?write_sequnlock_irq(seqlock_t?*lock);
void?write_sequnlock_bh(seqlock_t?*lock);
5、讀取-復(fù)制-更新(read-copy-update,RCU)
一種高級的互斥機(jī)制,它針對經(jīng)常發(fā)生讀取而很少寫入的情形的優(yōu)化。被保護(hù)的資源應(yīng)該通過指針訪問,而對這些資源的訪問應(yīng)該通過指針進(jìn)行,而對這些資源的引用必須僅有原子代碼擁有。在需要修改該數(shù)據(jù)結(jié)構(gòu)時(shí),寫入線程首先復(fù)制,然后修改副本,子后用新的版本替代相關(guān)指針。當(dāng)內(nèi)核確信老的版本上沒有其他引用時(shí),可以釋放老的版本。
參見RCU的白皮書
(http://www.rdrop.com/users/paulmck/rclock/intro/rclock_intro.html).
以及內(nèi)核頭文件?<linux/rcupdate.h>
應(yīng)用實(shí)例參考網(wǎng)絡(luò)路由表
參考文獻(xiàn):
1.Linux?Device?Driver?3rd
2.Linux?Kernel?Develoment?2nd
3.Linux內(nèi)核的同步機(jī)制
總結(jié)
以上是生活随笔為你收集整理的linux的同步与互斥的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 云计算的关键技术
- 下一篇: 设计模式在C语言中的应用--读nginx