[Z] Linux 內核同步機制

Linux內核同步機制,挺複雜的一個東西,經常使用的有自旋鎖,信號量,互斥體,原子操做,順序鎖,RCU,內存屏障等。這裏就說說它們的特色和基本用法。html

自旋鎖 :通用的 和讀寫的linux

特色:
1. 處理的時間很短。
2. 嘗試獲取鎖時,不能睡眠,可是有trylock接口能夠直接退出。
3. 多用在中斷中。
4. 任什麼時候候只有一個保持者可以訪問臨界區。
5. 能夠被中斷打斷的(硬件和軟件的)
6. 獲取自旋鎖後首先就是關閉了搶佔安全

spin_lock使用接口:數據結構

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   void spin_lock_init(spinlock_t *lock); //init
   void spin_lock(spinlock_t *lock); // 獲取鎖
  void spin_unlock(spinlock_t *lock); //釋放鎖
   其餘變體
typedef struct spinlock {
     union {
         struct raw_spinlock rlock;
 
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
         struct {
             u8 __padding[LOCK_PADSIZE];
             struct lockdep_map dep_map;
         };
#endif
     };
} spinlock_t;

Rwlock: 讀寫自旋鎖基本特色和通用自旋鎖同樣,可是有時候多線程頻繁讀取臨界區若是同時只能一個那麼效率會很低,它的特色就是在讀的時候獲取讀鎖,能夠同時有N個線程同時讀,在寫時須要得到寫鎖(不能有讀和寫鎖)。多線程

在讀操做時,寫操做必須等待;寫操做時,讀操做也須要的等待。這樣雖然避免了數據的不一致,可是某些操做要等待,後面還會出現順序鎖,是對讀寫鎖的優化,把寫的優先級調高了架構

使用接口:併發

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
rwlock_init(lock); //init
  read_lock(lock); //獲取讀鎖
  read_unlock(lock) ;
  write_lock (lock); //獲取寫鎖
  write_unlock(lock);
 
/*
  * include/linux/rwlock_types.h - generic rwlock type definitions
  *                 and initializers
  *
  * portions Copyright 2005, Red Hat, Inc., Ingo Molnar
  * Released under the General Public License (GPL).
  */
typedef struct {
     arch_rwlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
     unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
     unsigned int magic, owner_cpu;
     void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
     struct lockdep_map dep_map;
#endif
} rwlock_t;

而關於自旋鎖的缺點?這裏找到ibm一個文章異步

信號量(semaphore):通用的 和讀寫的優化

相對於自旋鎖,它最大的特色就是容許調用它的線程進入睡眠ui

1
2
3
4
5
6
/* Please don't access any members of this structure directly */
struct semaphore {
     raw_spinlock_t        lock;
     unsigned int        count;
     struct list_head    wait_list;
};

void sema_init(struct semaphore *sem, int val); // val值表明了同時多少個線程能夠進入臨界區,通常爲1 即做爲互斥體使用;固然>1 時,併發操做同一資源會引起什麼呢?
down_interruptible(struct semaphore *sem); // 獲取信號量 ,它是能夠中斷的。
up(struct semaphore *sem); // 釋放信號量,通常配對使用,固然也能夠在別的線程裏釋放它。

讀寫信號量:rwsem 它和讀寫自旋鎖相似 除了線程能夠睡眠

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* the rw-semaphore definition
  * - if activity is 0 then there are no active readers or writers
  * - if activity is +ve then that is the number of active readers
  * - if activity is -1 then there is one active writer
  * - if wait_list is not empty, then there are processes waiting for the semaphore
  */
struct rw_semaphore {
     __s32            activity;
     raw_spinlock_t        wait_lock;
     struct list_head    wait_list;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
     struct lockdep_map dep_map;
#endif
};
 
init_rwsem(sem)    ; // 初始化
down_read( struct rw_semaphore *sem); // 獲取讀信號量
up_read( struct rw_semaphore *sem); //釋放讀信號量
down_write( struct rw_semaphore *sem); //獲取寫信號量
up_write( struct rw_semaphore *sem); // 釋放寫信號量

互斥體(mutex):和count=1的信號量幾乎沒有區別,固然擁有互斥鎖的進程老是儘量的在短期內釋放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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_DEBUG_MUTEXES
     const char         *name;
     void            *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
     struct lockdep_map    dep_map;
#endif
};
 
mutex_init(mutex); // init 互斥鎖
mutex_lock(); //獲取互斥鎖,幾乎都能獲取
mutex_unlock();        //釋放互斥鎖

原子操做(atomic)(和架構相關,就是多條指令至關於一條指令執行,多用於計數)

組要是在smp上有意義,防止多條指令被多cpu執行。也是爲了實現互斥。

順序鎖(sequence)

特色:

和讀寫自旋鎖鎖相似,可是它的寫不會等待。寫的時候持有自旋鎖。首先讀者的代碼應該儘量短且寫者不能頻繁得到鎖,其次被保護的數據結構不包括被寫 修改的指針或被讀間接引用的指針。當要保護的資源很小很簡單,會很頻繁被訪問而且寫入操做不多發生且必須快速時,就能夠用seqlock。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Seqlock:
typedef struct {
     unsigned sequence;
     spinlock_t lock;
} seqlock_t;
 
seqlock_init(x) / DEFINE_SEQLOCK(x) // init
write_seqlock(seqlock_t *sl) ; // 獲取寫鎖
write_sequnlock(seqlock_t *sl);
read_seqbegin 和 read_seqretry 結合使用 //讀時若是有寫鎖,則循環等待直到鎖釋放.
應用實例drivers/md/md.c
retry:
             seq = read_seqbegin(&bb->lock);
 
             memset (bbp, 0xff, PAGE_SIZE);
 
             for (i = 0 ; i < bb->count ; i++) {
                 u64 internal_bb = p[i];
                 u64 store_bb = ((BB_OFFSET(internal_bb) << 10)
                         | BB_LEN(internal_bb));
                 bbp[i] = cpu_to_le64(store_bb);
             }
             bb->changed = 0;
             if (read_seqretry(&bb->lock, seq))
                 goto retry;

RCU:read-copy-update

在linux提供的全部內核互斥設施當中屬於一種免鎖機制。Rcu無需考慮讀和寫的互斥問題。

它其實是rwlock的一種優化。讀取者沒必要關心寫入者。因此RCU可讓多個讀取者與寫入者同時工做。寫入者的操做比例在10%以上,須要考慮其餘互斥方法。而且必需要以指針的方式來訪問被保護資源。

Rcu_read_lock //僅僅是關閉搶佔
Rcu_read_unlock //打開搶佔
Rcu_assign_pointer(ptr,new_ptr)
//等待隊列:它並非一種互斥機制。它輔助comletion。
//它主要用來實現進程的睡眠等待。
//操做接口:wait/ wake_up

1
2
3
4
5
struct __wait_queue_head {
          spinlock_t lock;
          struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

完成接口(completion) :該機制被用來在多個執行路徑間做同步使用,即協調多個執行路徑的執行順序。若是沒有完成體,則睡眠在wait_list上。這裏usb 在提交urb時會用到。

若是驅動程序要在執行後面操做以前等待某個過程的完成,它能夠調用wait_for_completion,以要完成的事件爲參數:

Completion機制是線程間通訊的一種輕量級機制:容許一個線程告訴另外一個線程工做已經完成

1
2
3
4
5
6
7
8
9
10
11
12
struct completion {
     unsigned int done;
     wait_queue_head_t wait;
};
 
接口:
DECLARE_COMPLETION(x) // 靜態定義completion
init_completion( struct completion *x); // 動態init
INIT_COMPLETION(x); // 初始化一個已經使用過的completion
Wait_for_completion( struct completion *x);
complete( struct completion *); //done +1,喚醒等待的一個。
Complete_all // 喚醒全部的,通常不會用。

內存屏障

內存屏障主要有:讀屏障、寫屏障、通用屏障、優化屏障

內存屏障主要解決了兩個問題:單處理器下的亂序問題和多處理器下的內存同步問題

編譯器優化以保證程序上下文因果關係爲前提。

以 讀屏障爲例,它用於保證讀操做有序。屏障以前的讀操做必定會先於屏障以後的讀操做完成,寫操做不受影響,同屬於屏障的某一側的讀操做也不受影響。相似的, 寫屏障用於限制寫操做。而通用屏障則對讀寫操做都有做用。而優化屏障則用於限制編譯器的指令重排,不區分讀寫。前三種屏障都隱含了優化屏障的功能。好比:
tmp = ttt; *addr = 5; mb(); val = *data;

有了內存屏障就了確保先設置地址端口,再讀數據端口。而至於設置地址端口與tmp的賦值孰先孰後,屏障則不作干預。有了內存屏障,就能夠在隱式因果關係的場景中,保證因果關係邏輯正確。

在Linux中,優化屏障就是barrier()宏,它展開爲asm volatile(「」:::」memory」)
smp_rmb(); // 讀屏障
smp_wmb(); //寫屏障
smp_mb(); // 通用屏障

Blk:大內核鎖

BKL(大內核鎖)是一個全局自旋鎖,使用它主要是爲了方便實現從Linux最初的SMP過分到細粒度加鎖機制。它終將退出歷史舞臺。

BKL的特性:
持有BKL的任務仍然能夠睡眠 。由於當任務沒法調度時,所加的鎖會自動被拋棄;當任務被調度時,鎖又會被從新得到。固然,並非說,當任務持有BKL時,睡眠是安全的,緊急是能夠這樣作,由於睡眠不會形成任務死鎖。

BKL是一種遞歸鎖。一個進程能夠屢次請求一個鎖,並不會像自旋鎖那麼產生死鎖。BKL能夠在進程上下文中。

BKL是有害的:
在內核中不鼓勵使用BKL。一個執行線程能夠遞歸的請求鎖lock_kernel(),可是釋放鎖時也必須調用一樣次數的unlock_kernel() 操做,在最後一個解鎖操做完成以後,鎖纔會被釋放。BKL在被持有時一樣會禁止內核搶佔。多數狀況下,BKL更像是保護代碼而不是保護數據.

備註:單核不可搶佔內核 惟一的異步事件就是硬件中斷 ,因此想要同步即關閉中斷便可。對於單核可搶佔和多核可搶佔的 ,除了中斷 還有進程調度(即優先級高的進程搶佔cpu資源),而上述全部這些機制都是爲了防止併發。

 

參考書籍《linux內核設計與實現》 ,《深刻linux設備驅動內核機制》等。 參考代碼 linux3.18.3

相關文章
相關標籤/搜索