2017-06-03
周末閑暇無事,聊聊內核中的wait_event*類函數的具體實現,等待事件必定涉及到某個條件,而這些函數的區別主要是等待后喚醒的方式……直奔主題,上源碼
wait_event_interruptible
#define wait_event_interruptible(wq, condition) \ ({ \ int __ret = 0; \ if (!(condition)) \ __wait_event_interruptible(wq, condition, __ret); \ __ret; \ })
調用該宏首先會先檢查條件,如果條件已經滿足,則不用等了呀,返回吧……,否則調用__wait_event_interruptible
#define __wait_event_interruptible(wq, condition, ret) \ do { \ DEFINE_WAIT(__wait); \ \ for (;;) { \ prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \ if (condition) \ break; \ if (!signal_pending(current)) { \ schedule(); \ continue; \ } \ ret = -ERESTARTSYS; \ break; \ } \ finish_wait(&wq, &__wait); \ } while (0)
首先聲明了一個關聯當前進程的wait對象,然后進入一個for空循環,開始就調用prepare_to_wait,該函數代碼如下,功能就是把wait對象加入到等待隊列並設置當前進程的狀態,注意此刻僅僅是設置了結構體的狀態,並沒有觸發調度。在真正觸發調度之前,需要再次檢查條件是否滿足,如果滿足了,直接break,否則檢查當前進程是否有信號存在,因為該宏設置的等待是可以被信號喚醒的,如果有信號則同樣break。否則就調用schedule進行調度吧!其他種類的額wait函數都是同樣的結構,區別就在於for循環內內部的不同,所以后續主要列舉的是for循環的代碼。
void prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) { unsigned long flags; wait->flags &= ~WQ_FLAG_EXCLUSIVE; spin_lock_irqsave(&q->lock, flags); /*保證只加入一次*/ if (list_empty(&wait->task_list)) __add_wait_queue(q, wait); set_current_state(state); spin_unlock_irqrestore(&q->lock, flags); }
wait_event_interruptible_timeout
該函數和前面類似,但是增加了等待時間,還是簡單看下__wait_event_interruptible_timeout的for循環
for (;;) { \ prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \ if (condition) \ break; \ if (!signal_pending(current)) { \ ret = schedule_timeout(ret); \ if (!ret) \ break; \ continue; \ } \ ret = -ERESTARTSYS; \ break; \ }
在沒有信號的狀態下,是調用了schedule_timeout函數,該函數在調度之前會對當前進程設定一個定時器,並設定process_timeout回調函數用於時間到了就執行該函數,自然該函數就是完成喚醒進程的功能。關於定時器,本文不做介紹。
wait_event_timeout
該函數和上面區別就是沒有了對信號的判斷,並且設置進程狀態為TASK_UNINTERRUPTIBLE,其__wait_event_timeout中for循環如下
for (;;) { \ prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \ if (condition) \ break; \ ret = schedule_timeout(ret); \ if (!ret) \ break; \ }
wait_event
該函數是最簡單的,設置狀態TASK_UNINTERRUPTIBLE,進行條件判斷,如果不符合就調度。沒有其他額外的操作。
內核中的sleep函數分析
static inline void sleep(unsigned sec) { current->state = TASK_INTERRUPTIBLE; schedule_timeout(sec * HZ); }
這里首先設置當前進程狀態為TASK_INTERRUPTIBLE,然后調用了schedule_timeout,參數就是設置的睡眠時間,單位是秒,乘以時鍾頻率HZ(每秒鍾發生時鍾中斷的次數)
signed long __sched schedule_timeout(signed long timeout) { struct timer_list timer; unsigned long expire; switch (timeout) { case MAX_SCHEDULE_TIMEOUT: /* * These two special cases are useful to be comfortable * in the caller. Nothing more. We could take * MAX_SCHEDULE_TIMEOUT from one of the negative value * but I' d like to return a valid offset (>=0) to allow * the caller to do everything it want with the retval. */ schedule(); goto out; default: /* * Another bit of PARANOID. Note that the retval will be * 0 since no piece of kernel is supposed to do a check * for a negative retval of schedule_timeout() (since it * should never happens anyway). You just have the printk() * that will tell you if something is gone wrong and where. */ if (timeout < 0) { printk(KERN_ERR "schedule_timeout: wrong timeout " "value %lx\n", timeout); dump_stack(); current->state = TASK_RUNNING; goto out; } } /*定時器到期時間*/ expire = timeout + jiffies; /*設置定時器*/ setup_timer_on_stack(&timer, process_timeout, (unsigned long)current); __mod_timer(&timer, expire, false, TIMER_NOT_PINNED); /*調度*/ schedule(); /*回來就刪除定時器*/ del_singleshot_timer_sync(&timer); /* Remove the timer from the object tracker */ destroy_timer_on_stack(&timer); timeout = expire - jiffies; /*timeout大於0意味這進程被提前喚醒*/ out: return timeout < 0 ? 0 : timeout; }
這里如果設置的睡眠時間足夠長,就不設置定時器,直接調度。否則如果設置的時間小於0,就打印下棧信息,然后設置狀態會TASK_RUNNING就返回了。正常情況下就設置一個定時器,根據傳入的時間設置到期時間,然后再出發調度,這樣在這段時間過后如果定時器被有被出發就會由時鍾中斷觸發執行,看到定時器的回調函數是process_timeout,其中調用了wake_up_process喚醒了睡眠的進程。創建定時器之后就調用__mod_timer激活定時器。默認情況下是在當前CPU,但是如果當前CPU是空閑的即IDLE狀態,這種情況如果配置了動態時鍾會關閉周期時鍾,這樣會把定時器遷移到一個非空閑的CPU上。因為在動態時鍾下,並不是每個jiffies都會發生中斷,這樣有可能造成延遲。在選定CPU后如果不是當前CPU那么需要對定時器做一些修改,然后調用internal_add_timer把定時器加入到管理向量數組中。從這里看沒有考慮高分辨率定時器呀!!!
說明:在schedule函數中,如果當前進程非運行態,在允許搶占的情況下,極有可能會把task從就緒隊列移除,而在喚醒進程的時候再重新加入到就緒隊列。
以馬內利!
參考:linux3.10.1源碼
