內(nèi)核里處理的競態(tài)主要通過以下方法處理: 信號量(互斥量)种玛、自旋鎖笑窜、讀寫信號量致燥、讀寫自旋鎖、等待隊列排截、完成量嫌蚤。
- 信號量(互斥量)
//初始化信號量,val表示可并發(fā)使用的資源數(shù)量
void sema_init(struct semaphore *sem, int val);
//獲取一個資源断傲,資源數(shù)量減1脱吱,獲取不到則線程休眠
void down (&sem);
//可中斷的down操作,休眠中可被中斷喚醒
int down_interruptible(&sem);
//嘗試獲取认罩,不成功不會阻塞箱蝠,直接返回非0
int down_trylock(&sem);
//釋放信號量
void up(&sem);
上述獲取函數(shù)中,down_interruptible和down_trylock都有返回值垦垂,返回為0表示獲取信號量成功抡锈,非0則說明未獲取到。對 down_interruptible 來說是在等待休眠中被打斷了乔外,對down_trylock則是說明信號量已全被占用床三。
使用這兩種down肯定要始終檢查返回值。
互斥量就是信號量初始化為1的情況杨幼,也是最常用的情況:
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name)
//或者
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
example:
//用信號量保證設(shè)備同時只被一個線程打開
static DECLARE_MUTEX(mutex);
static xxx_open(struct inode *inode, struct file *filp)
{
......
if(0 != down_trylock(&mutex)) {
//返回設(shè)備忙
return -EBUSY;
}
......
//正常返回撇簿,設(shè)備文件打開
return 0;
}
static xxx_release(struct inode *inode, struct file *filp)
{
//close時釋放互斥量
up(&mutex);
return 0;
}
***關(guān)閉設(shè)備時一定要 up !差购! ***
- 自旋鎖
自旋鎖也是一個互斥設(shè)備四瘫,線程獲取到鎖后可使用某個資源,使用完后釋放該鎖欲逃。在未釋放時找蜜,另一線程申請該鎖,則陷入自旋稳析,即忙等待洗做,期間一直對鎖進(jìn)行測試弓叛,一旦可用就鎖定。對SMP及preemptive的單處理器就是如此诚纸,對于non-preemptive的單處理器自旋鎖什么也不需要做撰筷,因為線程執(zhí)行中并不會被搶占,一旦某個線程陷入自旋畦徘,就不會有其他線程來釋放它等待的鎖毕籽。
自旋鎖的實現(xiàn)描述:
do {
preempt_disable(); __acquire(lock); (void)(lock);
}while(0)
即關(guān)閉搶占 -> 請求鎖 -> 鎖定(置位)
//初始化
spinlock_t lock = SPIN_LOCK_UNLOCKED;
//或者
void spinlock_init(spinlock_t *lock);
//請求鎖
void spin_lock(spinlock_t *lock);
//請求鎖、保存中斷狀態(tài)井辆、禁止中斷
void spin_lock_irqsave(&lock, unsigned long flag);
//請求鎖关筒、禁止中斷
void spin_lock_irq(&lock);
//請求鎖、禁止軟中斷杯缺、允許硬件中斷
void spin_lock_bh(&lock)
//---------------------------------------------------
//釋放
void spin_unlock(spinlock_t *lock);
//其他lock都有對應(yīng)的unlock
void spin_unlock_irqrestore(&lock, unsigned long falg);
void spin_unlock_irq(&lock);
void spin_unlock_bh(&iock);
//-------------------------------------------------
//try 版本蒸播,鎖被占用立即返回非0
int spin_trylock(&lock);
int spin_trylock_bh(&lock);
獲取自旋鎖期間的代碼段是禁止休眠的,
這里要注意禁止使用一些可能引起休眠的函數(shù)夺谁,包括copy_to_user, copy_from_user, kmalloc等
- 讀寫信號量
基本機制就是: 允許多個讀取者獲得信號量廉赔,但只允許一個寫入者肉微,且一旦一個寫入者獲取到鎖匾鸥,就會禁止其他讀取/寫入者獲取信號量,即完全鎖定了碉纳。
void init_rwsem(struct rw_semaphore *sem);
//讀
void down_read(&sem);
int dow_read_trylock(&sem);
void up_read(&sem);
//寫
void down_write(&sem);
int down_write_trylock(&sem);
void up_write(&sem);
//一段時間內(nèi)禁止獲取寫入者信號量
void downgrade_write(&sem);
downgrade_write的意義:
讀寫信號量中寫入者優(yōu)先級比讀取者高勿负,且排斥讀取者,這就可能會出現(xiàn)在寫入頻繁時劳曹,讀取者長時間獲取不到鎖奴愉。因此可以在某次寫入者釋放鎖后,調(diào)用downgrade_write可在一段時間內(nèi)禁止寫入铁孵,即將保護(hù)資源變成了只讀锭硼。
- 讀寫自旋鎖
使用和自旋鎖API相似,如下:
void rwlock_init(rwlock_t *lock);
void read_lock(&lock);
void read_lock_irqsave(&lock);
void read_lock_irq(&lock);
void read_lock_bh(&lock);
//釋放鎖參考自旋鎖部分
//寫入鎖多一個try版本,讀取者并沒有try版本
int lock_write_rrylock(&lock);
對自旋鎖似乎并不是很在意讀取者饑餓的情況蜕劝。
- 完成量 & 等待隊列
信號量和自旋鎖都用于處理并發(fā)時的資源保護(hù)檀头,等待隊列和完成量則是用于內(nèi)核線程間的同步岂膳。
完成量可以很好的實現(xiàn)同步澳窑,接口也很簡潔:
//初始化
DECLARE_COMPLETION(cmp);
//或者,動態(tài)的創(chuàng)建
void init_completion(struct completion *cmp);
//等待完成量搅窿,非中斷等待婴削,不可殺
void wait_for_completion(&cmp);
//通知完成量
//喚醒一個等待線程
void complete(&cmp);
//喚醒所有等待線程
void complete_all(&cmp);
等待隊列更加古老廊镜,完成量是在其基礎(chǔ)上封裝的,
喚醒在等待隊列上休眠的線程需要兩個條件唉俗,一個是condition為真嗤朴,另一個是對應(yīng)的wake_up被調(diào)用配椭。
基本原理是線程調(diào)用wait_even_xxx
后,線程被置為TASK_INTERRUPTIBLE(或UNINTERRUPTIBLE)播赁,并被調(diào)度出去颂郎。調(diào)用wake_up就是將這些線程重新放回就緒隊列。線程被再次調(diào)度后容为,首先判斷condition是否為真乓序,如果不是,重復(fù)進(jìn)入等待坎背。
#define wait_event(wq, condition) \
do { \
if (condition) \
break; \
__wait_event(wq, condition); \
} while (0)
//-------------------------------------------------------------------------
#define __wait_event(wq, condition) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
if (condition) \
break; \
schedule(); \
} \
finish_wait(&wq, &__wait); \
} while (0)
使用步驟如下:
//初始化等待隊列頭
DECLARE_WAIT_QUEUE_HEAD(queue_head);
init_waitqueue_head(wait_queue_head_t *queue_head);
//等待事件
wait_event(queue_head, condition)
wait_event_interruptible(queue_head, condition);
wait_event_timeout(queue_head, condition, timeout);
wait_event_interruptible_timeout(queue_head, condition, timeout);
//喚醒
void wake_up(&queue_head);
void wake_up_interruptible(&queue_head);
另一種方法替劈,直接向等待隊列中增加一個任務(wù),控制程序調(diào)度得滤,實際上就是手動控制wait_event的過程陨献,顯然麻煩了。
/*接口*/
//定義一個等待隊列節(jié)點
DECLARE_WAITQUEUE(queue, tsk);
// 添加/移除等待隊列
void fastcall add_wait_queue(&queue_head, &queue);
void fastcall remove_wait_queue(&queue_head, &queue);
/*例子*/
DECLEARE_WAITQUEUE(queue,current); //保存current指針
add_wait_queue(&queue_head, &queue);
while(!conditon)
{
set_current_state(TASK_INTERRUPTIBLE);
if(signal_pending(currrent))
/*處理信號*/
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(q,&wait);