互斥體概述
信號(hào)量是在并行處理環(huán)境中對(duì)多個(gè)處理器訪問(wèn)某個(gè)公共資源進(jìn)行保護(hù)的機(jī)制,mutex用于互斥操作。信號(hào)量的count初始化為1,down()/up()也可以實(shí)現(xiàn)類似mutex的作用。
mutex的語(yǔ)義相對(duì)于信號(hào)量要簡(jiǎn)單輕便一些,在鎖爭(zhēng)用激烈的測(cè)試場(chǎng)景下,mutex比信號(hào)量執(zhí)行速度更快,可擴(kuò)展性更好,另外mutex數(shù)據(jù)結(jié)構(gòu)的定義比信號(hào)量小。
mutex的優(yōu)點(diǎn)
- mutex和信號(hào)量相比要高效的多:mutex最先實(shí)現(xiàn)自旋等待機(jī)制mutex在睡眠之前嘗試獲取鎖mutex實(shí)現(xiàn)MCS所來(lái)避免多個(gè)CPU爭(zhēng)用鎖而導(dǎo)致CPU高速緩存顛簸現(xiàn)象。
mutex的使用注意事項(xiàng):
- 同一時(shí)刻只有一個(gè)線程可以持有mutex。只有鎖持有者可以解鎖。不能再一個(gè)進(jìn)程中持有mutex,在另外一個(gè)進(jìn)程中釋放他。不允許遞歸地加鎖和解鎖。當(dāng)進(jìn)程持有mutex時(shí),進(jìn)程不可以退出。mutex必須使用官方API來(lái)初始化。mutex可以睡眠,所以不允許在中斷處理程序或者中斷下半部中使用,例如tasklet、定時(shí)器等。
目錄:
/linux/include/linux/mutex.h
/*
?*?Simple,?straightforward?mutexes?with?strict?semantics:
?*
?*?-?only?one?task?can?hold?the?mutex?at?a?time
?*?-?only?the?owner?can?unlock?the?mutex
?*?-?multiple?unlocks?are?not?permitted
?*?-?recursive?locking?is?not?permitted
?*?-?a?mutex?object?must?be?initialized?via?the?API
?*?-?a?mutex?object?must?not?be?initialized?via?memset?or?copying
?*?-?task?may?not?exit?with?mutex?held
?*?-?memory?areas?where?held?locks?reside?must?not?be?freed
?*?-?held?mutexes?must?not?be?reinitialized
?*?-?mutexes?may?not?be?used?in?hardware?or?software?interrupt
?*???contexts?such?as?tasklets?and?timers
?*
?*?These?semantics?are?fully?enforced?when?DEBUG_MUTEXES?is
?*?enabled.?Furthermore,?besides?enforcing?the?above?rules,?the?mutex
?*?debugging?code?also?implements?a?number?of?additional?features
?*?that?make?lock?debugging?easier?and?faster:
?*
?*?-?uses?symbolic?names?of?mutexes,?whenever?they?are?printed?in?debug?output
?*?-?point-of-acquire?tracking,?symbolic?lookup?of?function?names
?*?-?list?of?all?locks?held?in?the?system,?printout?of?them
?*?-?owner?tracking
?*?-?detects?self-recursing?locks?and?prints?out?all?relevant?info
?*?-?detects?multi-task?circular?deadlocks?and?prints?out?all?affected
?*???locks?and?tasks?(and?only?those?tasks)
?*/
struct?mutex?{
??/*?1:?unlocked,?0:?locked,?negative:?locked,?possible?waiters?*/
??atomic_t????count;
??spinlock_t????wait_lock;
??struct?list_head??wait_list;
#if?defined(CONFIG_DEBUG_MUTEXES)?||?defined(CONFIG_SMP)
??struct?task_struct??*owner;
#endif
#ifdef?CONFIG_MUTEX_SPIN_ON_OWNER
??void??????*spin_mlock;??/*?Spinner?MCS?lock?*/
#endif
#ifdef?CONFIG_DEBUG_MUTEXES
??const?char?????*name;
??void??????*magic;
#endif
#ifdef?CONFIG_DEBUG_LOCK_ALLOC
??struct?lockdep_map??dep_map;
#endif
};
作用及訪問(wèn)規(guī)則:
- 互斥鎖主要用于實(shí)現(xiàn)內(nèi)核中的互斥訪問(wèn)功能。內(nèi)核互斥鎖是在原子 API 之上實(shí)現(xiàn)的,但這對(duì)于內(nèi)核用戶是不可見的。對(duì)它的訪問(wèn)必須遵循一些規(guī)則:同一時(shí)間只能有一個(gè)任務(wù)持有互斥鎖,而且只有這個(gè)任務(wù)可以對(duì)互斥鎖進(jìn)行解鎖?;コ怄i不能進(jìn)行遞歸鎖定或解鎖。一個(gè)互斥鎖對(duì)象必須通過(guò)其API初始化,而不能使用memset或復(fù)制初始化。一個(gè)任務(wù)在持有互斥鎖的時(shí)候是不能結(jié)束的?;コ怄i所使用的內(nèi)存區(qū)域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。并且互斥鎖不能用于中斷上下文?;コ怄i比當(dāng)前的內(nèi)核信號(hào)量選項(xiàng)更快,并且更加緊湊。
互斥體的使用
初始化
靜態(tài)定義如下:
DEFINE_MUTEX(name);
動(dòng)態(tài)初始化mutex,如下:
mutex_init(&mutex);
具體實(shí)現(xiàn)如下:
#define?mutex_init(mutex)?
do?{???????
?static?struct?lock_class_key?__key;??
???????
?__mutex_init((mutex),?#mutex,?&__key);??
}?while?(0)
void
__mutex_init(struct?mutex?*lock,?const?char?*name,?struct?lock_class_key?*key)
{
?atomic_set(&lock->count,?1);
?spin_lock_init(&lock->wait_lock);
?INIT_LIST_HEAD(&lock->wait_list);
?mutex_clear_owner(lock);
#ifdef?CONFIG_MUTEX_SPIN_ON_OWNER
?lock->spin_mlock?=?NULL;
#endif
?debug_mutex_init(lock,?name,?key);
}
申請(qǐng)互斥鎖
mutex操作列表如下:
方法 | 描述 |
---|---|
mutex_lock(struct mutex*) | 為指定的mutex上鎖,如果不可用則睡眠 |
mutex_unlock(struct mutex*) | 為指定的mutex解鎖 |
mutex_trylock(struct mutex*) | 視圖獲取指定的mutex,如果成功則返回1;否則鎖被獲取,返回值是0 |
mutex_is_lock(struct mutex*) | 如果鎖已被征用,則返回1;否則返回0 |
mutex的簡(jiǎn)潔性和高效性源自于相比使用信號(hào)量更多的受限性。它不同于信號(hào)量,因?yàn)閙utex僅僅實(shí)現(xiàn)了Dijkstra設(shè)計(jì)初衷中的最基本的行為。因此mutex的使用場(chǎng)景相對(duì)而言更嚴(yán)格。
(1)代碼:linux/kernel/mutex.c
void?inline?fastcall?__sched?mutex_lock(struct?mutex?*lock);???
?//獲取互斥鎖。
實(shí)際上是先給count做自減操作,然后使用本身的自旋鎖進(jìn)入臨界區(qū)操作。首先取得count的值,在將count置為-1,判斷如果原來(lái)count的置為1,也即互斥鎖可以獲得,則直接獲取,跳出。否則進(jìn)入循環(huán)反復(fù)測(cè)試互斥鎖的狀態(tài)。在循環(huán)中,也是先取得互斥鎖原來(lái)的狀態(tài),在將其之為-1,判斷如果可以獲取(等于1),則退出循環(huán),否則設(shè)置當(dāng)前進(jìn)程的狀態(tài)為不可中斷狀態(tài),解鎖自身的自旋鎖,進(jìn)入睡眠狀態(tài),待被在調(diào)度喚醒時(shí),再獲得自身的自旋鎖,進(jìn)入新一次的查詢其自身狀態(tài)(該互斥鎖的狀態(tài))的循環(huán)。
(2)具體參見linux/kernel/mutex.c
int?fastcall?__sched?mutex_lock_interruptible(struct?mutex?*lock);
和mutex_lock()一樣,也是獲取互斥鎖。在獲得了互斥鎖或進(jìn)入睡眠直到獲得互斥鎖之后會(huì)返回0。如果在等待獲取鎖的時(shí)候進(jìn)入睡眠狀態(tài)收到一個(gè)信號(hào)(被信號(hào)打斷睡眠),則返回_EINIR。
(3)具體參見linux/kernel/mutex.c
int?fastcall?__sched?mutex_trylock(struct?mutex?*lock);
試圖獲取互斥鎖,如果成功獲取則返回1,否則返回0,不等待。
釋放互斥鎖
具體參見linux/kernel/mutex.c
void?fastcall?mutex_unlock(struct?mutex?*lock);
釋放被當(dāng)前進(jìn)程獲取的互斥鎖。該函數(shù)不能用在中斷上下文中,而且不允許去釋放一個(gè)沒(méi)有上鎖的互斥鎖。
互斥鎖試用注意事項(xiàng)
- 任何時(shí)刻中只有一個(gè)任務(wù)可以持有mutex, 也就是說(shuō),mutex的使用計(jì)數(shù)永遠(yuǎn)是1給mutex鎖者必須負(fù)責(zé)給其再解鎖——你不能在一個(gè)上下文中鎖定一個(gè)mutex,而在另 一個(gè)上下文中給它解鎖。這個(gè)限制使得mutex不適合內(nèi)核同用戶空間復(fù)雜的同步場(chǎng)景。最 常使用的方式是:在同一上下文中上鎖和解鎖。遞歸地上鎖和解鎖是不允許的。也就是說(shuō),你不能遞歸地持有同一個(gè)鎖,同樣你也不能再去解鎖一個(gè)已經(jīng)被解開的mutex當(dāng)持有一個(gè)mutex時(shí) ,進(jìn)程不可以退出mutex不能在中斷或者下半部中使用,即使使用mutex_trylock()也不行mutex只能通過(guò)官方API管理:它只能使用上節(jié)中描述的方法初始化,不可被拷貝、手動(dòng) 初始化或者重復(fù)初始化
信號(hào)量和互斥體
互斥體和信號(hào)量很相似,內(nèi)核中兩者共存會(huì)令人混淆。所幸,它們的標(biāo)準(zhǔn)使用方式都有簡(jiǎn)單規(guī)范:除非mutex的某個(gè)約束妨礙你使用,否則相比信號(hào)量要優(yōu)先使用mutex。當(dāng)你寫新代碼時(shí),只有碰到特殊場(chǎng)合(一般是很底層代碼)才會(huì)需要使用信號(hào)量。因此建議
選mutex。如果發(fā)現(xiàn)不能滿足其約束條件,且沒(méi)有其他別的選擇時(shí),再考慮選擇信號(hào)量
自旋鎖和互斥體使用場(chǎng)合
了解何時(shí)使用自旋鎖,何時(shí)使用互斥體(或信號(hào)量)對(duì)編寫優(yōu)良代碼很重要,但是多數(shù)情況下,并不需要太多的考慮,因?yàn)樵谥袛嗌舷挛闹兄荒苁褂米孕i,而在任務(wù)睡眠時(shí)只能使用互斥體。
下面總結(jié)一下各種鎖的需求情況
需求 | 建議的加鎖方法 |
---|---|
低開銷加鎖 | 優(yōu)先使用自旋鎖 |
短期鎖定 | 優(yōu)先使用自旋鎖 |
長(zhǎng)期鎖定 | 優(yōu)先使用互斥體 |
中斷上下文中加鎖 | 使用自旋鎖 |
持有鎖需要睡眠 | 使用互斥體 |
互斥鎖鎖定和解鎖使用實(shí)例
使用方法如下:
1.?struct?mutex?mutex;
2.?mutex_init(&mutex);?/*定義*/
3.?//加鎖
4.?mutex_lock(&mutex);
5.??
6.?//臨界區(qū)
7.?
8.?//解鎖
9.?mutex_unlock(&mutex);
可以看出,互斥體就是一個(gè)簡(jiǎn)化版的信號(hào)量,因?yàn)椴辉傩枰芾砣魏问褂糜?jì)數(shù)。
下面網(wǎng)卡DM9000的驅(qū)動(dòng),其中寫入eeprom的操作試用了mutex機(jī)制:
static?void
dm9000_write_eeprom(board_info_t?*db,?int?offset,?u8?*data)
{
?unsigned?long?flags;
?if?(db->flags?&?DM9000_PLATF_NO_EEPROM)
??return;
?mutex_lock(&db->addr_lock);
?spin_lock_irqsave(&db->lock,?flags);
?iow(db,?DM9000_EPAR,?offset);
?iow(db,?DM9000_EPDRH,?data[1]);
?iow(db,?DM9000_EPDRL,?data[0]);
?iow(db,?DM9000_EPCR,?EPCR_WEP?|?EPCR_ERPRW);
?spin_unlock_irqrestore(&db->lock,?flags);
?dm9000_wait_eeprom(db);
?mdelay(1);?/*?wait?at?least?150uS?to?clear?*/
?spin_lock_irqsave(&db->lock,?flags);
?iow(db,?DM9000_EPCR,?0);
?spin_unlock_irqrestore(&db->lock,?flags);
?mutex_unlock(&db->addr_lock);
}
可以看到每次驅(qū)動(dòng)向eeprom寫入數(shù)據(jù)(訪問(wèn)臨界資源),都需要首先獲得該資源對(duì)應(yīng)的互斥鎖db->addr_lock,并且使用完畢必須釋放該鎖。