Linux 併發與競爭

Linux 系統是個多任務操作系統,會存在多個任務同時訪問同一片內存區域,這些任務可能會相互覆蓋這段內存中的數據,造成內存數據混亂。針對這個問題必須要做處理,嚴重的話可能會導致系統崩潰。

Linux 系統併發產生的主要原因:
  • 多線程併發訪問,Linux 是多任務(線程)的系統,所以多線程訪問是最基本的原因。
  • 搶佔式併發訪問,從 2.6 版本內核開始,Linux 內核支持搶佔,也就是說調度程序可以在任意時刻搶佔正在運行的線程,從而運行其他的線程。
  • 中斷程序併發訪問,硬件中斷。
  • SMP(多核)核間併發訪問,現在 ARM 架構的多核 SOC 很常見,多核 CPU 存在覈間併發訪問。
併發訪問帶來的問題就是競爭,操作系統有道臨界區這個概念,所謂的臨界區就是共享數據段,對於臨界區必須保證一次只有一個線程訪問,也就是要保證臨界區是原子訪問。多個線程同時操作臨界區就表示存在競爭

 1、原子操作

  • 原子操作是指不會被線程調度機制打斷的操作,獨立不可分割的操作。
  • 原子操作一旦開始,就一直運行到結束。
  • 原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序不可以被打亂,也不可以被切割而只執行其中的一部分。

圖A                          

如圖,使用原子操作後就能將 三行彙編指令作爲一個整體運行,不被打斷。

原子操作 API 函數:

函數 描述
ATOMIC_INIT(int i)
定義原子變量的時候對其初始化
int atomic_read(atomic_t *v)
讀取 v 的值,並且返回
void atomic_set(atomic_t *v, int i)
v 寫入 i
void atomic_add(int i, atomic_t *v)
v 加上 i
void atomic_sub(int i, atomic_t *v)
v 減去 i
void atomic_inc(atomic_t *v)
v 1,也就是自增
void atomic_dec(atomic_t *v)
v 1,也就是自減
int atomic_dec_return(atomic_t *v)
v 1,並且返回 v 的值
int atomic_inc_return(atomic_t *v)
v 1,並且返回 v 的值
int atomic_sub_and_test(int i, atomic_t *v)
v i,如果結果爲 0 就返回真,否則返回假
int atomic_dec_and_test(atomic_t *v)
v 1,如果結果爲 0 就返回真,否則返回假
int atomic_inc_and_test(atomic_t *v)
v 1,如果結果爲 0 就返回真,否則返回假
int atomic_add_negative(int i, atomic_t *v)
v i,如果結果爲負就返回真,否則返回假

原子位操作 API 函數 :

函數 描述
void set_bit(int nr, void *p)
p 地址的第 nr 位置 1
void clear_bit(int nr,void *p)
p 地址的第 nr 位清零
void change_bit(int nr, void *p)
p 地址的第 nr 位進行翻轉
int test_bit(int nr, void *p)
獲取 p 地址的第 nr 位的值
int test_and_set_bit(int nr, void *p)
p 地址的第 nr 位置 1,並且返回 nr 位原來的值
int test_and_clear_bit(int nr, void *p)
p 地址的第 nr 位清零,並且返回 nr 位原來的值
int test_and_change_bit(int nr, void *p)
p 地址的第 nr 位翻轉,並且返回 nr 位原來的值

 

2、自旋鎖

  • 當一個線程要訪問某個共享資源的時候首先要先獲取相應的鎖,鎖只能被一個線程持有,只要此線程不釋放持有的鎖,那麼其他的線程就不能獲取此鎖。
  • 中斷裏面可以使用自旋鎖,但是在中斷裏面使用自旋鎖的時候,在獲取鎖之前一定要先禁止本地中斷(即本 CPU 中斷,對於多核 SOC 來說會有多個 CPU ),否則可能導致鎖死現象的發生。
  • 缺點:等待自旋鎖的線程會一直處於自旋狀態,浪費處理器時間,降低系統性能,所以自旋鎖的持有時間不能太長。
  • 自旋鎖適用於短時期的輕量級加鎖,如果臨界區比較大,運行時間比較長的話要選擇其他的併發處理方式,比如信號量和互斥體
  • 自旋鎖保護的臨界區內不能調用任何可能導致線程休眠的 API 函數,否則的話可能導致死鎖。
  • 不能遞歸申請自旋鎖,因爲一旦通過遞歸的方式申請一個你正在持有的鎖,那麼你就必須“自旋”,等待鎖被釋放,然而你正處於“自旋”狀態,根本沒法釋放鎖。
自旋鎖 API 函數:
DEFINE_SPINLOCK(spinlock_t lock)
定義並初始化一個自選變量
int spin_lock_init(spinlock_t *lock)
初始化自旋鎖
void spin_lock(spinlock_t *lock)
獲取指定的自旋鎖,加鎖
void spin_unlock(spinlock_t *lock)
釋放指定的自旋鎖
int spin_trylock(spinlock_t *lock)
嘗試獲取指定的自旋鎖,如果沒有獲取到就返回 0
int spin_is_locked(spinlock_t *lock)
檢查指定的自旋鎖是否被獲取,沒有被獲取就返回非 0,否則返回 0
適用於SMP或支持搶佔的單CPU下線程之間的併發訪問,也就是用於線程與線程之間,被自旋鎖保護的臨界區一定不能調用任何能夠引起睡眠和阻塞的 API 函數,否則的話會可能會導致死鎖現象的發生。

 

中斷自旋鎖 API 函數:

void spin_lock_irq(spinlock_t *lock)
禁止本地中斷,並獲取自旋鎖
void spin_unlock_irq(spinlock_t *lock)
激活本地中斷,並釋放自旋鎖
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)
保存中斷狀態,禁止本地中斷,並獲取自旋鎖
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
將中斷狀態恢復到以前的狀態,並且激活本地中斷,釋放自旋鎖
一般在線程中使用 spin_lock_irqsave/ spin_unlock_irqrestore,在中斷中使用 spin_lock/spin_unlock 。

 

下半部(BH)自旋鎖 API 函數:

void spin_lock_bh(spinlock_t *lock)
關閉下半部,並獲取自旋鎖
void spin_unlock_bh(spinlock_t *lock)
打開下半部,並釋放自旋鎖

 

3、信號量

特點:
  • 信號量可以使等待資源線程進入休眠狀態,因此適用於那些佔用資源比較久的場合。
  • 信號量不能用於中斷中,因爲信號量會引起休眠,中斷不能休眠。
  • 共享資源的持有時間比較短時不適合使用信號量,因爲頻繁的休眠、切換線程引起的開銷要遠大於信號量帶來的優勢。

信號量 API 函數:

DEFINE_SEAMPHORE(name)
定義一個信號量,並且設置信號量的值爲 1
void sema_init(struct semaphore *sem, int val)
初始化信號量 sem,設置信號量值爲 val
void down(struct semaphore *sem)
獲取信號量,因爲會導致休眠,因此不能在中斷中使用
int down_trylock(struct semaphore *sem)
嘗試獲取信號量,如果能獲取到信號量就獲取,並且返回 0。如果不能就返回非 0,並且不會進入休眠
int down_interruptible(struct semaphore *sem)
獲取信號量,和 down 類似,只是使用 down 進入休眠狀態的線程不能被信號打斷。而使用此 函數進入休眠以後是可以被信號打斷的
void up(struct semaphore *sem)
釋放信號量

 

4、互斥體(mutex

注意:
  • mutex 可以導致休眠,因此不能在中斷中使用 mutex中斷中只能使用自旋鎖
  • 和信號量一樣,mutex 保護的臨界區可以調用引起阻塞的 API 函數。
  • 因爲一次只有一個線程可以持有 mutex,因此,必須由 mutex 的持有者釋放 mutex。並且 mutex 不能遞歸上鎖和解鎖。

互斥體API 函數:

DEFINE_MUTEX(name)
定義並初始化一個 mutex 變量
void mutex_init(mutex *lock)
初始化 mutex
void mutex_lock(struct mutex *lock)
獲取 mutex,也就是給 mutex 上鎖。如果獲取不到就進休眠
void mutex_unlock(struct mutex *lock)
釋放 mutex,也就給 mutex 解鎖
int mutex_trylock(struct mutex *lock)
嘗試獲取 mutex,成功返回 1,失敗返回 0
int mutex_is_locked(struct mutex *lock)
判斷 mutex 是否被獲取,是就返回1,否則返回 0
int mutex_lock_interruptible(struct mutex *lock)
使用此函數獲取信號量失敗進入休眠以後可以被信號打斷

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章