做者:劉洪濤,華清遠見嵌入式學院高級講師,ARM公司受權ATC講師。html
關於自旋鎖用法介紹的文章,已經有不少,但有些細節的地方點的還不夠透。我這裏就把我我的認爲你們容易有疑問的地方拿出來討論一下。linux
1、自旋鎖(spinlock)簡介 安全
自旋鎖在同一時刻只能被最多一個內核任務持有,因此一個時刻只有一個線程容許存在於臨界區中。這點能夠應用在多處理機器、或運行在單處理器上的搶佔式內核中須要的鎖定服務。併發
2、信號量簡介函數
這裏也介紹下信號量的概念,由於它的用法和自旋鎖有類似的地方。post
Linux中的信號量是一種睡眠鎖。若是有一個任務試圖得到一個已被持有的信號量時,信號量會將其推入等待隊列,而後讓其睡眠。這時處理器得到自由去執行其它代碼。當持有信號量的進程將信號量釋放後,在等待隊列中的一個任務將被喚醒,從而即可以得到這個信號量。ui
3、自旋鎖和信號量對比atom
在不少地方自旋鎖和信號量能夠選擇任何一個使用,但也有一些地方只能選擇某一種。下面對比一些二者的用法。spa
表1-1自旋鎖和信號量對比線程
應用場合 |
信號量or自旋鎖 |
低開銷加鎖(臨界區執行時間較快) |
優先選擇自旋鎖 |
低開銷加鎖(臨界區執行時間較長) |
優先選擇信號量 |
臨界區可能包含引發睡眠的代碼 |
不能選自旋鎖,能夠選擇信號量 |
臨界區位於非進程上下文時,此時不能睡眠 |
優先選擇自旋鎖,即便選擇信號量也只能用down_trylock非阻塞的方式 |
4、自旋鎖與linux內核進程調度關係
咱們討論下表1-1中的第3種狀況(其它幾種狀況比較好理解),若是臨界區可能包含引發睡眠的代碼則不能使用自旋鎖,不然可能引發死鎖。
那麼爲何信號量保護的代碼能夠睡眠而自旋鎖就不能呢?
先看下自旋鎖的實現方法吧,自旋鎖的基本形式以下:
spin_lock(&mr_lock);
//臨界區
spin_unlock(&mr_lock);
跟蹤一下spin_lock(&mr_lock)的實現
#define spin_lock(lock) _spin_lock(lock)
#define _spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) \
do { preempt_disable(); __acquire(lock); (void)(lock); } while (0)
注意到「preempt_disable()」,這個調用 的功能是「關搶佔」(在spin_unlock中會從新開啓搶佔功能)。從中能夠看出,使用自旋鎖保護的區域是工做在非搶佔的狀態;即便獲取不到鎖,在 「自旋」狀態也是禁止搶佔的。瞭解到這,我想我們應該可以理解爲什麼自旋鎖保護的代碼不能睡眠了。試想一下,若是在自旋鎖保護的代碼中間睡眠,此時發生進程 調度,則可能另一個進程會再次調用spinlock保護的這段代碼。而咱們如今知道了即便在獲取不到鎖的「自旋」狀態,也是禁止搶佔的,而「自旋」又是 動態的,不會再睡眠了,也就是說在這個處理器上不會再有進程調度發生了,那麼死鎖天然就發生了。
我們能夠總結下自旋鎖的特色:
● 單處理器非搶佔內核下:自旋鎖會在編譯時被忽略;
● 單處理器搶佔內核下:自旋鎖僅僅看成一個設置內核搶佔的開關;
● 多處理器下:此時才能徹底發揮出自旋鎖的做用,自旋鎖在內核中主要用來防止多處理器中併發訪問臨界區,防止內核搶佔形成的競爭。
5、linux搶佔發生的時間
最後在瞭解下linux搶佔發生的時間,搶佔分爲用戶搶佔和內核搶佔。
用戶搶佔在如下狀況下產生:
● 從系統調用返回用戶空間
● 從中斷處理程序返回用戶空間
內核搶佔會發生在:
● 當從中斷處理程序返回內核空間的時候,且當時內核具備可搶佔性;
● 當內核代碼再一次具備可搶佔性的時候。(如:spin_unlock時)
● 若是內核中的任務顯式的調用schedule()
● 若是內核中的任務阻塞。
基本的進程調度就是發生在時鐘中斷後,而且發現進程的時間 片已經使用完了,則發生進程搶佔。一般咱們會利用中斷處理程序返回內核空間的時候能夠進行內核搶佔這個特性來提升一些I/O操做的實時性,如:當I/O事 件發生的是時候,對應的中斷處理程序被激活,當它發現有進程在等待這個I/O事件的時候,它會激活等待進程,而且設置當前正在執行進程的 need_resched標誌,這樣在中斷處理程序返回的時候,調度程序被激活,原來在等待I/O事件的進程(極可能)得到執行權,從而保證了對I/O事 件的相對快速響應(毫秒級)。能夠看出,在I/O事件發生的時候,I/O事件的處理進程會搶佔當前進程,系統的響應速度與調度時間片的長度無關。
本章已介紹了不少符號給併發的管理. 最重要的這些在此總結:
-
#include <asm/semaphore.h> //定義信號量和其上操做的包含文件.
-
信號量的定義和初始化
(1)靜態定義及初始化
-
DECLARE_MUTEX(name);
-
DECLARE_MUTEX_LOCKED(name);
-
(2)動態定義及初始化
struct semaphore sem
-
void init_MUTEX(struct semaphore *sem);
-
-
void init_MUTEX_LOCKED(struct semaphore *sem);
信號量的獲取和釋放
-
void down(struct semaphore *sem);
-
-
int down_interruptible(struct semaphore *sem);
-
-
int down_trylock(struct semaphore *sem);
-
-
void up(struct semaphore *sem);
-
down 使調用進程進入不可中斷的睡眠; down_interruptible, 相反, 能夠被信號打斷,通常狀況下都應該使用這個操做. down_trylock 不睡眠; 相反, 它馬上返回若是旗標不可用. 得到信號量並完成相關操做後必須使用 up 來釋放他.
-
-
struct rw_semaphore;
-
-
init_rwsem(struct rw_semaphore *sem);
-
旗標的讀者/寫者版本和初始化它的函數.
-
void down_read(struct rw_semaphore *sem);
-
-
int down_read_trylock(struct rw_semaphore *sem);
-
-
void up_read(struct rw_semaphore *sem);
-
得到和釋放對讀者/寫者旗標的讀存取的函數.
-
void down_write(struct rw_semaphore *sem);
-
-
int down_write_trylock(struct rw_semaphore *sem);
-
-
void up_write(struct rw_semaphore *sem);
-
-
void downgrade_write(struct rw_semaphore *sem);
-
管理對讀者/寫者旗標寫存取的函數.
-
#include <linux/completion.h>
-
-
DECLARE_COMPLETION(name);
-
-
init_completion(struct completion *c);
-
-
INIT_COMPLETION(struct completion c);
-
描述 Linux completion 機制的包含文件, 已經初始化 completion 的正常方法. INIT_COMPLETION 應當只用來從新初始化一個以前已經使用過的 completion.
-
void wait_for_completion(struct completion *c);
-
等待一個 completion 事件發出.
-
void complete(struct completion *c);
-
-
void complete_all(struct completion *c);
-
發出一個 completion 事件. completion 喚醒, 最多, 一個等待着的線程, 而 complete_all 喚醒所有等待者.
-
void complete_and_exit(struct completion *c, long retval);
-
經過調用 complete 來發出一個 completion 事件, 而且爲當前線程調用 exit.
自旋鎖
-
#include <linux/spinlock.h>
-
-
spinlock_t lock = SPIN_LOCK_UNLOCKED;
-
-
spin_lock_init(spinlock_t *lock);
-
定義自旋鎖接口的包含文件, 以及初始化鎖的 2 個方法.
-
void spin_lock(spinlock_t *lock);
-
-
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
-
-
void spin_lock_irq(spinlock_t *lock);
-
-
void spin_lock_bh(spinlock_t *lock);
-
加鎖一個自旋鎖的各類方法, 而且, 可能地, 禁止中斷.
-
int spin_trylock(spinlock_t *lock);
-
-
int spin_trylock_bh(spinlock_t *lock);
-
上面函數的非自旋版本; 在獲取鎖失敗時返回 0, 不然非零.
-
void spin_unlock(spinlock_t *lock);
-
-
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
-
-
void spin_unlock_irq(spinlock_t *lock);
-
-
void spin_unlock_bh(spinlock_t *lock);
-
釋放一個自旋鎖的相應方法.
-
rwlock_t lock = RW_LOCK_UNLOCKED
-
-
rwlock_init(rwlock_t *lock);
-
初始化讀者/寫者鎖的 2 個方法.
-
void read_lock(rwlock_t *lock);
-
-
void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
-
-
void read_lock_irq(rwlock_t *lock);
-
-
void read_lock_bh(rwlock_t *lock);
-
得到一個讀者/寫者鎖的讀存取的函數.
-
void read_unlock(rwlock_t *lock);
-
-
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
-
-
void read_unlock_irq(rwlock_t *lock);
-
-
void read_unlock_bh(rwlock_t *lock);
-
釋放一個讀者/寫者自旋鎖的讀存取.
-
void write_lock(rwlock_t *lock);
-
-
void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
-
-
void write_lock_irq(rwlock_t *lock);
-
-
void write_lock_bh(rwlock_t *lock);
-
得到一個讀者/寫者鎖的寫存取的函數.
-
void write_unlock(rwlock_t *lock);
-
-
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
-
-
void write_unlock_irq(rwlock_t *lock);
-
-
void write_unlock_bh(rwlock_t *lock);
-
釋放一個讀者/寫者自旋鎖的寫存取的函數.
-
#include <asm/atomic.h>
-
-
atomic_t v = ATOMIC_INIT(value);
-
-
void atomic_set(atomic_t *v, int i);
-
-
int atomic_read(atomic_t *v);
-
-
void atomic_add(int i, atomic_t *v);
-
-
void atomic_sub(int i, atomic_t *v);
-
-
void atomic_inc(atomic_t *v);
-
-
void atomic_dec(atomic_t *v);
-
-
int atomic_inc_and_test(atomic_t *v);
-
-
int atomic_dec_and_test(atomic_t *v);
-
-
int atomic_sub_and_test(int i, atomic_t *v);
-
-
int atomic_add_negative(int i, atomic_t *v);
-
-
int atomic_add_return(int i, atomic_t *v);
-
-
int atomic_sub_return(int i, atomic_t *v);
-
-
int atomic_inc_return(atomic_t *v);
-
-
int atomic_dec_return(atomic_t *v);
-
原子地存取整數變量. atomic_t 變量必須只經過這些函數存取.
-
#include <asm/bitops.h>
-
-
void set_bit(nr, void *addr);
-
-
void clear_bit(nr, void *addr);
-
-
void change_bit(nr, void *addr);
-
-
test_bit(nr, void *addr);
-
-
int test_and_set_bit(nr, void *addr);
-
-
int test_and_clear_bit(nr, void *addr);
-
-
int test_and_change_bit(nr, void *addr);
-
原子地存取位值; 它們可用作標誌或者鎖變量. 使用這些函數阻止任何與併發存取這個位相關的競爭狀況.
-
#include <linux/seqlock.h>
-
-
seqlock_t lock = SEQLOCK_UNLOCKED;
-
-
seqlock_init(seqlock_t *lock);
-
定義 seqlock 的包含文件, 已經初始化它們的 2 個方法.
-
unsigned int read_seqbegin(seqlock_t *lock);
-
-
unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
-
-
int read_seqretry(seqlock_t *lock, unsigned int seq);
-
-
int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);
-
得到一個 seqlock-保護 的資源的讀權限的函數.
-
void write_seqlock(seqlock_t *lock);
-
-
void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
-
-
void write_seqlock_irq(seqlock_t *lock);
-
-
void write_seqlock_bh(seqlock_t *lock);
-
獲取一個 seqlock-保護的資源的寫權限的函數.
-
void write_sequnlock(seqlock_t *lock);
-
-
void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
-
-
void write_sequnlock_irq(seqlock_t *lock);
-
-
void write_sequnlock_bh(seqlock_t *lock);
-
釋放一個 seqlock-保護的資源的寫權限的函數.
-
#include <linux/rcupdate.h>
-
須要使用讀取-拷貝-更新(RCU)機制的包含文件.
-
void rcu_read_lock;
-
-
void rcu_read_unlock;
-
獲取對由 RCU 保護的資源的原子讀權限的宏定義.
-
void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
-
安排一個回調在全部處理器已經被調度以及一個 RCU-保護的資源可用被安全的釋放以後運行.