Linux 系統是個多任務操作系統,會存在多個任務同時訪問同一片內存區域,這些任務可能會相互覆蓋這段內存中的數據,造成內存數據混亂。針對這個問題必須要做處理,嚴重的話可能會導致系統崩潰。
- 多線程併發訪問,Linux 是多任務(線程)的系統,所以多線程訪問是最基本的原因。
- 搶佔式併發訪問,從 2.6 版本內核開始,Linux 內核支持搶佔,也就是說調度程序可以在任意時刻搶佔正在運行的線程,從而運行其他的線程。
- 中斷程序併發訪問,硬件中斷。
- SMP(多核)核間併發訪問,現在 ARM 架構的多核 SOC 很常見,多核 CPU 存在覈間併發訪問。
1、原子操作
- 原子操作是指不會被線程調度機制打斷的操作,獨立不可分割的操作。
- 原子操作一旦開始,就一直運行到結束。
- 原子操作可以是一個步驟,也可以是多個操作步驟,但是其順序不可以被打亂,也不可以被切割而只執行其中的一部分。
如圖,使用原子操作後就能將 三行彙編指令作爲一個整體運行,不被打斷。
原子操作 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 函數,否則的話可能導致死鎖。
- 不能遞歸申請自旋鎖,因爲一旦通過遞歸的方式申請一個你正在持有的鎖,那麼你就必須“自旋”,等待鎖被釋放,然而你正處於“自旋”狀態,根本沒法釋放鎖。
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
|
中斷自旋鎖 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)
|
將中斷狀態恢復到以前的狀態,並且激活本地中斷,釋放自旋鎖
|
下半部(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)
|
使用此函數獲取信號量失敗進入休眠以後可以被信號打斷
|