天天看點

深入了解linux互斥鎖(mutex)

鎖機制,可以說是linux整個系統的精髓所在,linux核心都是圍繞着同步在運轉。在多程序和多線程程式設計中,鎖起着極其重要的作用。我這裡說的是互斥鎖,其實是泛指linux中所有的鎖機制。我在這裡不講如果建立鎖,關于鎖的建立,網上代碼很多,我在這裡就不多說了。我要談一談一個讓所有剛剛接觸鎖機制的程式員都很困惑的問題:如何使用以及鎖機制在程式中是如何運作的。

 一、定義:

/linux/include/linux/mutex.h

47struct mutex {

  48        /* 1: unlocked, 0: locked, negative: locked, possible waiters */

  49        atomic_t                count;

  50        spinlock_t              wait_lock;

  51        struct list_head        wait_list;

  52#ifdef CONFIG_DEBUG_MUTEXES

  53        struct thread_info      *owner;

  54        const char              *name;

  55        void                    *magic;

  56#endif

  57#ifdef CONFIG_DEBUG_LOCK_ALLOC

  58        struct lockdep_map      dep_map;

  59#endif

  60};

二、作用及通路規則:

互斥鎖主要用于實作核心中的互斥通路功能。核心互斥鎖是在原子 API 之上實作的,但這對于核心使用者是不可見的。對它的通路必須遵循一些規則:同一時間隻能有一個任務持有互斥鎖,而且隻有這個任務可以對互斥鎖進行解鎖。互斥鎖不能進行遞歸鎖定或解鎖。一個互斥鎖對象必須通過其API初始化,而不能使用memset或複制初始化。一個任務在持有互斥鎖的時候是不能結束的。互斥鎖所使用的記憶體區域是不能被釋放的。使用中的互斥鎖是不能被重新初始化的。并且互斥鎖不能用于中斷上下文。但是互斥鎖比目前的核心信号量選項更快,并且更加緊湊,是以如果它們滿足您的需求,那麼它們将是您明智的選擇。

三、各字段詳解:

1、atomic_t count;

訓示互斥鎖的狀态:

1 沒有上鎖,可以獲得

0 被鎖定,不能獲得

負數 被鎖定,且可能在該鎖上有等待程序

初始化為沒有上鎖。

2、spinlock_t wait_lock;

等待擷取互斥鎖中使用的自旋鎖。在擷取互斥鎖的過程中,操作會在自旋鎖的保護中進行。初始化為為鎖定。

3、struct list_head wait_list;

等待互斥鎖的程序隊列。

四、操作:

1、定義并初始化:

struct mutex mutex;

mutex_init(&mutex);

79# define mutex_init(mutex) \

80do { \

81 static struct lock_class_key __key; \

82 \

83 __mutex_init((mutex), #mutex, &__key); \

84} while (0)

42void

43__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)

44{

45 atomic_set(&lock->count, 1);

46 spin_lock_init(&lock->wait_lock);

47 INIT_LIST_HEAD(&lock->wait_list);

48

49 debug_mutex_init(lock, name, key);

50}

直接定于互斥鎖mutex并初始化為未鎖定,己count為1,wait_lock為未上鎖,等待隊列wait_list為空。

2、擷取互斥鎖:

(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,不等待。

3、釋放互斥鎖:

具體參見linux/kernel/mutex.c

void fastcall mutex_unlock(struct mutex *lock);

釋放被目前程序擷取的互斥鎖。該函數不能用在中斷上下文中,而且不允許去釋放一個沒有上鎖的互斥鎖。

五、使用形式:

mutex_init(&mutex); /*定義*/

...

mutex_lock(&mutex); /*擷取互斥鎖*/

... /*臨界資源*/

mutex_unlock(&mutex); /*釋放互斥鎖*/

為什麼要使用鎖

這個就比較簡單,linux裡面,鎖的種類很多,包括互斥鎖,檔案鎖,讀寫鎖······其實信号量說白了也是一種鎖。使用鎖的目的是達到同步的作用,使共享資源在同一時間内,隻有能有一個程序或者線程對他進行操作。

linux是如何通過鎖來實作對資料的保護和維護的

這個問題是我要将的重點。很多剛剛接觸鎖機制的程式員,都會犯這種錯誤。比如,此時有2個線程,分别是線程A,線程B。A和B共享了資源M。為了同步A和B,使得同一時刻,同意時刻,隻有一個線程對M操作。于是,很自然的會在A中對M資源先lock,等到A對M操作完畢之後,然後做一個操作unlock。B中則因為A加了鎖,B就直接操作M。這個時候,你會發現,B同樣可以操作到M。這個是為什麼呢?

我們利索當然的把檢測鎖的任務交給了作業系統,交給了核心。可以翻看APUE上對于所的講解,其中一部分是這麼寫的:

This mutual-exclusion mechanism works only if we design our threads to follow the same data-access rules. The operating system doesn't serialize access to data for us. If we allow one thread to access a shared resource without first acquiring a lock, then

inconsistencies can occur even though the rest of our threads do acquire the lock before attempting to access the shared resource.

這裡This mutual-exclusion mechanism指的就是鎖機制。說的很清楚,隻有程式員設計線程的時候,都遵循同一種資料通路規則,鎖機制才會起作用。作業系統不會為我們序列化資料通路,也就是說,作業系統不會為我們拟定任何資料通路順序,到底是A在先還是B在先,作業系統不會為我們規定。如果我們允許一個線程在沒有多的鎖(lock)之前,就對共享資料進行通路操作,那麼,即使我們其他的線程都在通路之前試圖去先鎖住資源(擷取鎖),同樣會導緻資料通路不一緻,即多個線程同時在操作共享資源。

    從上面文字可以看出,作業系統不會為我們去檢查,此時是不是有線程已經把資源鎖住了。為了使鎖能夠正常工作,為了保護共享資源,我們隻有在設計線程的時候,所有線程都用同一種方法去通路共享資料,也就是通路資料之前,務必先擷取鎖,然後再操作,操作完之後要解鎖(unlock)。作業系統提供鎖機制,就是提供了一種所有程式員都必須遵循的規範。而不是說我們鎖住資源,其他線程通路共享資源的時候,讓作業系統去為我們檢查資料是否有其他的線程在操作。

繼續閱讀