競態(tài)與同步(1)

內(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);

參考 wait_event_interruptible 使用方法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末懂更,一起剝皮案震驚了整個濱河市眨业,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌沮协,老刑警劉巖龄捡,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異慷暂,居然都是意外死亡聘殖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門行瑞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奸腺,“玉大人,你說我怎么就攤上這事血久⊥徽眨” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵氧吐,是天一觀的道長讹蘑。 經(jīng)常有香客問我,道長副砍,這世上最難降的妖魔是什么衔肢? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮豁翎,結(jié)果婚禮上角骤,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好邦尊,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布背桐。 她就那樣靜靜地躺著,像睡著了一般蝉揍。 火紅的嫁衣襯著肌膚如雪链峭。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天又沾,我揣著相機與錄音弊仪,去河邊找鬼。 笑死杖刷,一個胖子當(dāng)著我的面吹牛励饵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播滑燃,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼役听,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了表窘?” 一聲冷哼從身側(cè)響起典予,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎乐严,沒想到半個月后瘤袖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡麦备,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年孽椰,在試婚紗的時候發(fā)現(xiàn)自己被綠了昭娩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凛篙。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖栏渺,靈堂內(nèi)的尸體忽然破棺而出呛梆,到底是詐尸還是另有隱情,我是刑警寧澤磕诊,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布填物,位于F島的核電站,受9級特大地震影響霎终,放射性物質(zhì)發(fā)生泄漏滞磺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一莱褒、第九天 我趴在偏房一處隱蔽的房頂上張望击困。 院中可真熱鬧,春花似錦广凸、人聲如沸阅茶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脸哀。三九已至蹦浦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間撞蜂,已是汗流浹背盲镶。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝌诡,地道東北人徒河。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像送漠,于是被迫代替她去往敵國和親顽照。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容