手把手教Linux驅動7-內核互斥鎖
互斥體概述
信號量是在并行處理環境中對多個處理器訪問某個公共資源進行保護的機制,mutex用于互斥操作。信號量的count初始化為1,down()/up()也可以實現類似mutex的作用。
mutex的語義相對于信號量要簡單輕便一些,在鎖爭用激烈的測試場景下,mutex比信號量執行速度更快,可擴展性更好,另外mutex數據結構的定義比信號量小。
mutex的優點
- mutex和信號量相比要高效的多:
- mutex最先實現自旋等待機制
- mutex在睡眠之前嘗試獲取鎖
- mutex實現MCS所來避免多個CPU爭用鎖而導致CPU高速緩存顛簸現象。
mutex的使用注意事項:
- 同一時刻只有一個線程可以持有mutex。
- 只有鎖持有者可以解鎖。不能再一個進程中持有mutex,在另外一個進程中釋放他。
- 不允許遞歸地加鎖和解鎖。
- 當進程持有mutex時,進程不可以退出。
- mutex必須使用官方API來初始化。
- mutex可以睡眠,所以不允許在中斷處理程序或者中斷下半部中使用,例如tasklet、定時器等。
目錄:
- /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
- };
作用及訪問規則:
- 互斥鎖主要用于實現內核中的互斥訪問功能。內核互斥鎖是在原子 API 之上實現的,但這對于內核用戶是不可見的。
- 對它的訪問必須遵循一些規則:同一時間只能有一個任務持有互斥鎖,而且只有這個任務可以對互斥鎖進行解鎖。互斥鎖不能進行遞歸鎖定或解鎖。一個互斥鎖對象必須通過其API初始化,而不能使用memset或復制初始化。一個任務在持有互斥鎖的時候是不能結束的。互斥鎖所使用的內存區域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。并且互斥鎖不能用于中斷上下文。
- 互斥鎖比當前的內核信號量選項更快,并且更加緊湊。
互斥體的使用
初始化
靜態定義如下:
- DEFINE_MUTEX(name);
動態初始化mutex,如下:
- mutex_init(&mutex);
具體實現如下:
- #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的簡潔性和高效性源自于相比使用信號量更多的受限性。它不同于信號量,因為mutex僅僅實現了Dijkstra設計初衷中的最基本的行為。因此mutex的使用場景相對而言更嚴格。
(1)代碼:linux/kernel/mutex.c
- void inline fastcall __sched mutex_lock(struct mutex *lock);
- //獲取互斥鎖。
實際上是先給count做自減操作,然后使用本身的自旋鎖進入臨界區操作。首先取得count的值,在將count置為-1,判斷如果原來count的置為1,也即互斥鎖可以獲得,則直接獲取,跳出。否則進入循環反復測試互斥鎖的狀態。在循環中,也是先取得互斥鎖原來的狀態,在將其之為-1,判斷如果可以獲取(等于1),則退出循環,否則設置當前進程的狀態為不可中斷狀態,解鎖自身的自旋鎖,進入睡眠狀態,待被在調度喚醒時,再獲得自身的自旋鎖,進入新一次的查詢其自身狀態(該互斥鎖的狀態)的循環。
(2)具體參見linux/kernel/mutex.c
- int fastcall __sched mutex_lock_interruptible(struct mutex *lock);
和mutex_lock()一樣,也是獲取互斥鎖。在獲得了互斥鎖或進入睡眠直到獲得互斥鎖之后會返回0。如果在等待獲取鎖的時候進入睡眠狀態收到一個信號(被信號打斷睡眠),則返回_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);
釋放被當前進程獲取的互斥鎖。該函數不能用在中斷上下文中,而且不允許去釋放一個沒有上鎖的互斥鎖。
互斥鎖試用注意事項
- 任何時刻中只有一個任務可以持有mutex, 也就是說,mutex的使用計數永遠是1
- 給mutex鎖者必須負責給其再解鎖——你不能在一個上下文中鎖定一個mutex,而在另 一個上下文中給它解鎖。這個限制使得mutex不適合內核同用戶空間復雜的同步場景。最 常使用的方式是:在同一上下文中上鎖和解鎖。
- 遞歸地上鎖和解鎖是不允許的。也就是說,你不能遞歸地持有同一個鎖,同樣你也不能再去解鎖一個已經被解開的mutex
- 當持有一個mutex時 ,進程不可以退出
- mutex不能在中斷或者下半部中使用,即使使用mutex_trylock()也不行
- mutex只能通過官方API管理:它只能使用上節中描述的方法初始化,不可被拷貝、手動 初始化或者重復初始化
信號量和互斥體
互斥體和信號量很相似,內核中兩者共存會令人混淆。所幸,它們的標準使用方式都有簡單規范:除非mutex的某個約束妨礙你使用,否則相比信號量要優先使用mutex。當你寫新代碼時,只有碰到特殊場合(一般是很底層代碼)才會需要使用信號量。因此建議 選mutex。如果發現不能滿足其約束條件,且沒有其他別的選擇時,再考慮選擇信號量
自旋鎖和互斥體使用場合
了解何時使用自旋鎖,何時使用互斥體(或信號量)對編寫優良代碼很重要,但是多數情況下,并不需要太多的考慮,因為在中斷上下文中只能使用自旋鎖,而在任務睡眠時只能使用互斥體。
下面總結一下各種鎖的需求情況
需求 | 建議的加鎖方法 |
---|---|
低開銷加鎖 | 優先使用自旋鎖 |
短期鎖定 | 優先使用自旋鎖 |
長期鎖定 | 優先使用互斥體 |
中斷上下文中加鎖 | 使用自旋鎖 |
持有鎖需要睡眠 | 使用互斥體 |
互斥鎖鎖定和解鎖使用實例
使用方法如下:
- 1. struct mutex mutex;
- 2. mutex_init(&mutex); /*定義*/
- 3. //加鎖
- 4. mutex_lock(&mutex);
- 5.
- 6. //臨界區
- 7.
- 8. //解鎖
- 9. mutex_unlock(&mutex);
可以看出,互斥體就是一個簡化版的信號量,因為不再需要管理任何使用計數。
下面網卡DM9000的驅動,其中寫入eeprom的操作試用了mutex機制:
- 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);
- }
可以看到每次驅動向eeprom寫入數據(訪問臨界資源),都需要首先獲得該資源對應的互斥鎖db->addr_lock,并且使用完畢必須釋放該鎖。
本文轉載自微信公眾號「一口Linux」,可以通過以下二維碼關注。轉載本文請聯系一口Linux公眾號。