2014年11月6日 星期四

[轉] 淺析Linux等待隊列

在Linux驅動程序中,可以使用等待隊列(wait queue)來實現阻塞進程的喚醒。等待很早就作為一個基本的功能單位出現在Linux內核中,它以隊列為基礎數據結構,與進程調度機制緊密結合,能夠用於實現內核中的異步事件通知機制。
我們從它的使用範例著手,看看等待隊列是如何實現異步信號功能的。以下代碼節選自kernel/printk.c。
DECLARE_WAIT_QUEUE_HEAD(log_wait); // 初始化等待隊列頭log_wait
static DEFINE_SPINLOCK(logbuf_lock); // 定義自旋鎖logbuf_lock
int do_syslog(int type, char __user *buf, int len)
{
        ... ... ...
        err = wait_event_interruptible(log_wait, (log_start - log_end)); // 等待
        /*
        // 我們先來看看linux/wait.h中定義的wait_event_interruptible()的原型
        #define wait_event_interruptible(wq, condition)
        ({
               int __ret = 0;
                if ( (!condition) )
                        __wait_event_interruptible(wq, condition, __ret);
                __ret;
        })
        // wait_event_interruptible()將等待隊列加入第一個參數wq為等待隊列頭的等待隊列鏈表中,
        // 當condition滿足時,wait_event_interruptible()會立即返回,否則,阻塞等待condition滿足。
        // 與之類似的還有wait_event(),不同點在於wait_event_interruptible()可以被信號喚醒。
        // __wait_event_interruptible()原型如下:
        #define __wait_event_interruptible(wq, condition, __ret)
        do {
                DEFINE_WAIT(__wait); // 定義等待隊列__wait
                for(;;) {
                        prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);
                        // prepare_to_wait()的作用是:將等待隊列__wait加入以wq為等待隊列的等待隊列
                         // 鏈表中,並且將進程狀態設置為TASK_INTERRUPTIBLE
                        if (condition)
                                break; // 如果condition滿足則跳出
                       if (!signal_pending(current)) { // 如果不是被信號喚醒
                                ret = schedule_timeout(ret);
                                if (!ret)
                                        break;
                                continue;
                        }
                          ret = - ERESTARTSYS;
                          break;
                          schedule(); // 放棄CPU,調度其它進程執行
                }
                finish_wait(&wq, &__wait);
                // finish_wait()的作用是,將等待隊列__wait從等待隊列頭wq指向的等待隊列鏈表中移除,
                // 並且將進程狀態設置為TASK_RUNNING
        }while(0)
        */
        ... ... ...
        spin_lock_irq(&lock_wait); // 鎖定
        ... ... ...
        log_start++; // log_start自加,使得(log_start - log_end)這個等待隊列的condition滿足
        ... ... ...
         spin_unlock_irq(&lock_wait); // 解鎖
        ... ... ...
}
void wake_up_klogd(void)
{
        ... ... ...
        wake_up_interruptible(&lock_wait); //喚醒等待隊列
}
void release_console_sem(void)
{
        ... ... ...
        spin_lock_irqsave(&lock_wait); // 鎖定
        wake_klogd = log_start - log_end;
         ... ... ...
        spin_unlock_irqrestore(&lock_wait); // 解鎖
        ... ... ...
        if (wake_klogd)
                wake_up_klogd(); // 如果wake_klogd非負,則調用wake_up_klogd()來喚醒等待隊列
        ... ... ...
}
我們最後再總結一下等待隊列wait_event_interruptible():


在wait_event_interruptible()中首先判斷conditon是不是已經滿足,如果是則直接返回0,否則調用__wait_event_interruptible(),並用__ret來存放返回值。__wait_event_interruptible()首先將定義並初始化一個wait_queue_t變量__wait,其中數據為當前進程狀態current(struct task_struct),並把__wait加入等待隊列。在無限循環中,__wait_event_interruptible()將本進程置為可中斷的掛起狀態,反覆檢查condition是否成立,如果成立則退出,如果不成立則繼續休眠;條件滿足後,即把本進程運行狀態置為運行態,並把__wait從等待隊列中清除,從而進程能夠調度運行。

掛起的進程並不會自動轉入運行的,因此,還需要一個喚醒動作,這個動作由wake_up_interruptible()完成,它將遍歷作為參數傳入的lock_wait等待隊列,將其中所有的元素(通常都是task_struct)置為運行態,從而可被調度。

沒有留言:

張貼留言