溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Linux驅(qū)動中內(nèi)核互斥鎖

發(fā)布時(shí)間:2021-11-02 09:16:06 來源:億速云 閱讀:134 作者:柒染 欄目:系統(tǒng)運(yùn)維

如何理解Linux驅(qū)動中內(nèi)核互斥鎖,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

 

互斥體概述

信號量是在并行處理環(huán)境中對多個(gè)處理器訪問某個(gè)公共資源進(jìn)行保護(hù)的機(jī)制,mutex用于互斥操作。信號量的count初始化為1,down()/up()也可以實(shí)現(xiàn)類似mutex的作用。

mutex的語義相對于信號量要簡單輕便一些,在鎖爭用激烈的測試場景下,mutex比信號量執(zhí)行速度更快,可擴(kuò)展性更好,另外mutex數(shù)據(jù)結(jié)構(gòu)的定義比信號量小。

mutex的優(yōu)點(diǎn)

  1. mutex和信號量相比要高效的多:

  2. mutex最先實(shí)現(xiàn)自旋等待機(jī)制

  3. mutex在睡眠之前嘗試獲取鎖

  4. mutex實(shí)現(xiàn)MCS所來避免多個(gè)CPU爭用鎖而導(dǎo)致CPU高速緩存顛簸現(xiàn)象。

mutex的使用注意事項(xiàng):

  1. 同一時(shí)刻只有一個(gè)線程可以持有mutex。

  2. 只有鎖持有者可以解鎖。不能再一個(gè)進(jìn)程中持有mutex,在另外一個(gè)進(jìn)程中釋放他。

  3. 不允許遞歸地加鎖和解鎖。

  4. 當(dāng)進(jìn)程持有mutex時(shí),進(jìn)程不可以退出。

  5. mutex必須使用官方API來初始化。

  6. 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 };

作用及訪問規(guī)則:

  1. 互斥鎖主要用于實(shí)現(xiàn)內(nèi)核中的互斥訪問功能。內(nèi)核互斥鎖是在原子 API 之上實(shí)現(xiàn)的,但這對于內(nèi)核用戶是不可見的。

  2. 對它的訪問必須遵循一些規(guī)則:同一時(shí)間只能有一個(gè)任務(wù)持有互斥鎖,而且只有這個(gè)任務(wù)可以對互斥鎖進(jìn)行解鎖?;コ怄i不能進(jìn)行遞歸鎖定或解鎖。一個(gè)互斥鎖對象必須通過其API初始化,而不能使用memset或復(fù)制初始化。一個(gè)任務(wù)在持有互斥鎖的時(shí)候是不能結(jié)束的。互斥鎖所使用的內(nèi)存區(qū)域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。并且互斥鎖不能用于中斷上下文。

  3. 互斥鎖比當(dāng)前的內(nèi)核信號量選項(xiàng)更快,并且更加緊湊。

互斥體的使用

初始化

靜態(tài)定義如下:

DEFINE_MUTEX(name);

動態(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); }

申請互斥鎖

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的簡潔性和高效性源自于相比使用信號量更多的受限性。它不同于信號量,因?yàn)閙utex僅僅實(shí)現(xiàn)了Dijkstra設(shè)計(jì)初衷中的最基本的行為。因此mutex的使用場景相對而言更嚴(yán)格。

(1)代碼:linux/kernel/mutex.c

void inline fastcall __sched mutex_lock(struct mutex *lock);     //獲取互斥鎖。

實(shí)際上是先給count做自減操作,然后使用本身的自旋鎖進(jìn)入臨界區(qū)操作。首先取得count的值,在將count置為-1,判斷如果原來count的置為1,也即互斥鎖可以獲得,則直接獲取,跳出。否則進(jìn)入循環(huán)反復(fù)測試互斥鎖的狀態(tài)。在循環(huán)中,也是先取得互斥鎖原來的狀態(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)入睡眠直到獲得互斥鎖之后會返回0。如果在等待獲取鎖的時(shí)候進(jìn)入睡眠狀態(tài)收到一個(gè)信號(被信號打斷睡眠),則返回_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è)沒有上鎖的互斥鎖。

互斥鎖試用注意事項(xiàng)

  1. 任何時(shí)刻中只有一個(gè)任務(wù)可以持有mutex, 也就是說,mutex的使用計(jì)數(shù)永遠(yuǎn)是1

  2. 給mutex鎖者必須負(fù)責(zé)給其再解鎖——你不能在一個(gè)上下文中鎖定一個(gè)mutex,而在另  一個(gè)上下文中給它解鎖。這個(gè)限制使得mutex不適合內(nèi)核同用戶空間復(fù)雜的同步場景。最 常使用的方式是:在同一上下文中上鎖和解鎖。

  3. 遞歸地上鎖和解鎖是不允許的。也就是說,你不能遞歸地持有同一個(gè)鎖,同樣你也不能再去解鎖一個(gè)已經(jīng)被解開的mutex

  4. 當(dāng)持有一個(gè)mutex時(shí) ,進(jìn)程不可以退出

  5. mutex不能在中斷或者下半部中使用,即使使用mutex_trylock()也不行

  6. mutex只能通過官方API管理:它只能使用上節(jié)中描述的方法初始化,不可被拷貝、手動 初始化或者重復(fù)初始化

信號量和互斥體

互斥體和信號量很相似,內(nèi)核中兩者共存會令人混淆。所幸,它們的標(biāo)準(zhǔn)使用方式都有簡單規(guī)范:除非mutex的某個(gè)約束妨礙你使用,否則相比信號量要優(yōu)先使用mutex。當(dāng)你寫新代碼時(shí),只有碰到特殊場合(一般是很底層代碼)才會需要使用信號量。因此建議  選mutex。如果發(fā)現(xiàn)不能滿足其約束條件,且沒有其他別的選擇時(shí),再考慮選擇信號量

自旋鎖和互斥體使用場合

了解何時(shí)使用自旋鎖,何時(shí)使用互斥體(或信號量)對編寫優(yōu)良代碼很重要,但是多數(shù)情況下,并不需要太多的考慮,因?yàn)樵谥袛嗌舷挛闹兄荒苁褂米孕i,而在任務(wù)睡眠時(shí)只能使用互斥體。

下面總結(jié)一下各種鎖的需求情況

需求建議的加鎖方法
低開銷加鎖優(yōu)先使用自旋鎖
短期鎖定優(yōu)先使用自旋鎖
長期鎖定優(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è)簡化版的信號量,因?yàn)椴辉傩枰芾砣魏问褂糜?jì)數(shù)。

下面網(wǎng)卡DM9000的驅(qū)動,其中寫入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ū)動向eeprom寫入數(shù)據(jù)(訪問臨界資源),都需要首先獲得該資源對應(yīng)的互斥鎖db->addr_lock,并且使用完畢必須釋放該鎖。

看完上述內(nèi)容,你們掌握如何理解Linux驅(qū)動中內(nèi)核互斥鎖的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI