【嵌入式Linux學習七步曲之第五篇 Linux內核及驅動編程】全面解析Linux內核的同步與互斥機制--同步篇


 

全面解析Linux內核的同步與互斥機制--同步篇

Sailor_forever sailing_9806@163.com 轉載請注明

http://blog.csdn.net/sailor_8318/archive/2008/06/30/2599357.aspx

 

【摘要】本文分析了內核的同步及互斥的幾種機制:原子運算符(atomic operator)、自旋鎖Spinlock、等待隊列Waitqueue、事件Eventcompletion、信號量Semaphore及其優化版互斥鎖,詳細分析了其實現流程。EventSemaphore本質上都是基於Waitqueue和自旋鎖實現的。本文還探討了每種機制最適合應用到哪些地方,以及如何構建安全高效的內核及驅動代碼。

 

【關鍵詞】原子操作;SpinlockWaitqueuecompletionEventSemaphore

 

---------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------

1      休眠與同步

一個驅動當它無法立刻滿足請求應當如何響應? 一個對 read 的調用可能當沒有數據時到來, 而以后會期待更多的數據。或者一個進程可能試圖寫, 但是你的設備沒有准備好接受數據, 因為你的輸出緩沖滿了。調用進程往往不關心這種問題; 程序員只希望調用 read write 並且使調用返回, 在必要的工作已完成后. 這樣, 在這樣的情形中。驅動應當(缺省地)阻塞進程, 使它進入睡眠直到請求可繼續。

 

進程被置為休眠,意味着它被標識為處於一個特殊的狀態並且從調度器的運行隊列中移走。直到發生某些事情改變了那個狀態,否則這個進程將不被任何 CPU調度運行。

 

安全地進入休眠的兩條規則:

1)       永遠不要在原子上下文中進入休眠。

當驅動在持有一個自旋鎖、seqlock或者 RCU 鎖時不能睡眠;

關閉中斷也不能睡眠;

持有一個信號量時休眠是合法的,但你應當仔細查看代碼:如果代碼在持有一個信號量時睡眠,任何其他的等待這個信號量的線程也會休眠。因此發生在持有信號量時的休眠必須短暫,而且決不能阻塞那個將最終喚醒你的進程。

2)       當進程被喚醒,重新檢查其所需資源。

它並不知道休眠了多長時間以及休眠時發生什么;也不知道是否另有進程也在休眠等待同一事件,且那個進程可能在它之前醒來並獲取了所等待的資源。所以不能對喚醒后的系統狀態做任何的假設,並必須重新檢查等待條件來確保正確的響應。

 

除非確信其他進程會在其他地方喚醒休眠的進程,否則也不能睡眠。使進程可被找到意味着:需要維護一個稱為等待隊列的數據結構。它是一個進程鏈表,其中飽含了等待某個特定事件的所有進程。在 Linux 中, 一個等待隊列由一個wait_queue_head_t 結構體來管理。

 

2      休眠的基礎

2.1   wait_queue系列數據結構

2.1.1      wait_queue_head_t

/include/linux/wait.h

struct __wait_queue_head {

        spinlock_t lock;

        struct list_head task_list;

};

typedef struct __wait_queue_head  wait_queue_head_t;

它包含一個自旋鎖和一個鏈表。這個鏈表是一個等待隊列入口。

 

關於自定義結構體的風格,若需要提供別名,則原始類型前面加”__”或者“tag_”,表示其為內部數據類型,對外是不可見的。typedef之后的類型為了和原始類型分開一般會在后面添加_t”,表示是typedef的,對外使用

 

#define __WAIT_QUEUE_HEAD_INITIALIZER(name) {                           /

        .lock         = __SPIN_LOCK_UNLOCKED(name.lock)              /

        .task_list   = { &(name).task_list &(name).task_list } /

}

因為Linux內核對於鏈表的遍歷方式的問題,通常一個雙向循環鏈表中有一個頭節點,其與其他節點的結構不一樣,並且通常無有效信息。此處的等待隊列頭有兩個域:

1)       操作循環鏈表的互斥鎖;

2)       嵌入到等待隊列頭中的鏈表頭。

為了用“.”域的形式初始化成員不能采用單獨的初始化鎖和鏈表頭部的宏,但可以采用聲明一個結構體類型的宏,如__SPIN_LOCK_UNLOCKED(name.lock).task_list的初始化應該采用LIST_HEAD_INIT宏的,這樣提高了可移植性。定義等待隊列頭部的同時,初始化了其成員,尤其是鏈表頭的初始化是添加后續等待隊列的前提。

 

#define DECLARE_WAIT_QUEUE_HEAD(name) /

        wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

定義一個等待隊列頭同時分配內存並進行初始化。對外的接口。

 

extern void init_waitqueue_head(wait_queue_head_t *q);

void init_waitqueue_head(wait_queue_head_t *q)

{

        spin_lock_init(&q->lock);

        INIT_LIST_HEAD(&q->task_list);

}

動態初始化一個已經分配了內存的wait_queue_head_t結構。當wait_queue_head_t類型成員內嵌到其他結構體中時需要采用此方法,而不能采用DECLARE_WAIT_QUEUE_HEAD全局或在棧中定義初始化一個wait_queue_head_t結構。

 

2.1.2      wait_queue_t

struct __wait_queue {

        unsigned int flags; //在等待隊列上喚醒時是否具備排他性

#define WQ_FLAG_EXCLUSIVE  0x01

        void *private;

        wait_queue_func_t func; //從等待隊列中喚醒進程時執行的統一操作,將進程更改為可運行狀態,具體誰運行將由調度策略決定

        struct list_head task_list; //與該等待隊列對應的鏈表

};

 

/*Macros for declaration and initialisaton of the datatypes*/

#define __WAITQUEUE_INITIALIZER(name tsk) {                               /

        .private     = tsk                                           /

        .func                = default_wake_function                      /

        .task_list   = { NULL NULL } /

}

GNU語法中對於結構體成員賦初值采用了域的形式,如“.private   =”,其好處在於:

a)       可以選擇性的對部分成員賦值。當結構體成員變量較多而大部分無須初始值時,此方法顯得尤為重要,因此減少了不必要的賦值。

b)       賦值順序與數據結構定義中成員的順序無關,因此若結構體成員順序變化,初始化部分不會受到任何影響。

c)       各個域之間用“,”分隔,最后一個無“,”。

 

#define DECLARE_WAITQUEUE(name tsk)                                   /

        wait_queue_t name = __WAITQUEUE_INITIALIZER(name tsk)

全局或者在棧中定義一個wait_queue_t類型變量的同時對其初始化,這保證了系統的可靠性,避免因用戶忘記初始化時導致的問題。通用的初始化宏,tsk為任意指針。分兩步:

1)       內部宏__WAITQUEUE_INITIALIZER初始化相應成員;wq內嵌在別的結構體中時,此宏很重要,提高了可移植性;

2)       提供給外部的接口,定義一個變量,並將第一步的結果賦值給該變量。

 

static inline void init_waitqueue_entry(wait_queue_t *q struct task_struct *p)

{

        q->flags = 0;

        q->private = p;

        q->func = default_wake_function;

}

動態初始化一個等待隊列入口項,將其和當前進程關聯起來,以便喚醒當前進程。

 

2.1.3      數據結構設計規則

今后凡遇到新設計一類結構體,若此類結構體變量必須初始化且有相對集中的操作,則應提供以下兩個操作接口:

a)       定義新建一個結構體變量,並初始化之;

b)       動態初始化一個已經分配內存的該類變量

為了適應在堆棧及全局等任意地方分配的該變量,其應該接收指向該類變量的指針。

 

 

2.2   陳舊sleep_on系列

//初始化一個wait_queue_t

#define     SLEEP_ON_VAR                                  /

        unsigned long flags;                          /

        wait_queue_t wait;                          /

        init_waitqueue_entry(&wait current);

 

//添加到隊列中

#define SLEEP_ON_HEAD                                   /

        spin_lock_irqsave(&q->lockflags);               /

        __add_wait_queue(q &wait);                      /

        spin_unlock(&q->lock);

 

//從隊列中刪除

#define     SLEEP_ON_TAIL                                 /

        spin_lock_irq(&q->lock);                 /

        __remove_wait_queue(q &wait);                        /

        spin_unlock_irqrestore(&q->lock flags);

SLEEP_ON_VARSLEEP_ON_HEADSLEEP_ON_ TAIL總是同時出現,不可分割。上述宏不是函數,只是連續的表達式而已,因為函數就將他們隔離開來了,函數他退出后變量就無意義了。也不能寫成do――while的多語句宏,變量定義離開“{}”后就沒有意義了。是為了編寫代碼更清晰明了,同時避免多寫字,實際上就是代碼封裝復用。

 

void fastcall __sched interruptible_sleep_on(wait_queue_head_t *q)

{

        SLEEP_ON_VAR //注意沒“;”

 

        current->state = TASK_INTERRUPTIBLE;

 

        SLEEP_ON_HEAD

        schedule(); //此處可能出問題

        SLEEP_ON_TAIL

}

EXPORT_SYMBOL(interruptible_sleep_on);

 

添加到隊列和從隊列中刪除由同一個模塊做,符合模塊設計規則,減小了耦合性。

喚醒的wakeup只負責改變進程狀態,進程重新獲得cpu后從隊列中刪除。

 

long fastcall __sched

interruptible_sleep_on_timeout(wait_queue_head_t *q long timeout)

{

        SLEEP_ON_VAR

 

        current->state = TASK_INTERRUPTIBLE;

 

        SLEEP_ON_HEAD

        timeout = schedule_timeout(timeout);

        SLEEP_ON_TAIL

 

        return timeout;

}

EXPORT_SYMBOL(interruptible_sleep_on_timeout);

 

void fastcall __sched sleep_on(wait_queue_head_t *q)

{

        SLEEP_ON_VAR

 

        current->state = TASK_UNINTERRUPTIBLE;

 

        SLEEP_ON_HEAD

        schedule();

        SLEEP_ON_TAIL

}

EXPORT_SYMBOL(sleep_on);

 

long fastcall __sched sleep_on_timeout(wait_queue_head_t *q long timeout)

{

        SLEEP_ON_VAR

 

        current->state = TASK_UNINTERRUPTIBLE;

 

        SLEEP_ON_HEAD

        timeout = schedule_timeout(timeout);

        SLEEP_ON_TAIL

 

        return timeout;

}

 

EXPORT_SYMBOL(sleep_on_timeout);

 

在決定調用sleep_on系列函數到真正調用schedule系列函數期間,若等待的條件為真,若此時繼續schedule相當於丟失了一次喚醒機會。因此sleep_on系列函數會引入競態,導致系統的不安全。

 

另外對於interruptible系列函數,其返回時並不能確定是因為資源可用返回還是遇到了signal,因此在程序中用戶需要再次判斷資源是否可用。如:

static ssize_t at91_mcp2510_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)

{

        CanData candata_ret;

 

retry:

        if (mcp2510dev.nCanReadpos !=  mcp2510dev.nCanRevpos) {// 需求的資源

                int count;

                count = MCP2510_RevRead(&candata_ret);

                if (count) copy_to_user(buffer, (char *)&candata_ret, count);

               

                return count;

        } else { //不可用

                if (filp->f_flags & O_NONBLOCK)

                       return -EAGAIN;

                interruptible_sleep_on(&(mcp2510dev.wq));

 

                if (signal_pending(current)) // 遇到信號,返回

                       return -ERESTARTSYS;

 

                goto retry; //重新判斷資源是否可用

        }

 

        DPRINTK("read data size=%d/n", sizeof(candata_ret));

        return sizeof(candata_ret);

}

 

綜合上述兩個因素,sleep_on系列函數應避免在驅動程序中出現,未來的2.7版內核中將刪除此類函數。

 

2.3   等待隊列的接口

2.3.1      初始化等待隊列

#define DEFINE_WAIT(name)                                              /

        wait_queue_t name = {                                           /

                .private     = current,                         /

                .func                = autoremove_wake_function,          /

                .task_list   = LIST_HEAD_INIT((name).task_list),   /

        }

 

#define init_wait(wait)                                                    /

        do {                                                        /

                (wait)->private = current;                         /

                (wait)->func = autoremove_wake_function;             /

                INIT_LIST_HEAD(&(wait)->task_list);                 /

        } while (0)

 

專用的初始化等待隊列函數,將當前進程添加到等待隊列中,注意和通用的接口DECLARE_WAITQUEUEinit_waitqueue_entry區別。同時喚醒處理不一樣,autoremove_wake_functiondefault_wake_function的基礎之上,將當前進程從等待隊列中刪除。

 

2.3.2      添加或從等待隊列中刪除

添加刪除的原始接口

static inline void __add_wait_queue(wait_queue_head_t *head, wait_queue_t *new)

{

        list_add(&new->task_list, &head->task_list);

}

 

static inline void __add_wait_queue_tail(wait_queue_head_t *head,

                                               wait_queue_t *new)

{

        list_add_tail(&new->task_list, &head->task_list);

}

 

static inline void __remove_wait_queue(wait_queue_head_t *head,

                                                      wait_queue_t *old)

{

        list_del(&old->task_list);

}

等待隊列是公用資源,但上述接口沒有加任何保護措施,適用於已經獲得鎖的情況下使用。

 

帶鎖並設置屬性的添加刪除

void fastcall add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

{

        unsigned long flags;

 

        wait->flags &= ~WQ_FLAG_EXCLUSIVE;

        spin_lock_irqsave(&q->lock, flags);

        __add_wait_queue(q, wait);

        spin_unlock_irqrestore(&q->lock, flags);

}

EXPORT_SYMBOL(add_wait_queue);

 

void fastcall add_wait_queue_exclusive(wait_queue_head_t *q, wait_queue_t *wait)

{

        unsigned long flags;

 

        wait->flags |= WQ_FLAG_EXCLUSIVE;

        spin_lock_irqsave(&q->lock, flags);

        __add_wait_queue_tail(q, wait);

        spin_unlock_irqrestore(&q->lock, flags);

}

EXPORT_SYMBOL(add_wait_queue_exclusive);

 

void fastcall remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

{

        unsigned long flags;

 

        spin_lock_irqsave(&q->lock, flags);

        __remove_wait_queue(q, wait);

        spin_unlock_irqrestore(&q->lock, flags);

}

EXPORT_SYMBOL(remove_wait_queue);

 

此三對函數的特點是調用同名的內部函數,同時添加一些保障安全特性的代碼。WQ_FLAG_EXCLUSIVE屬性的進程添加到隊尾,而非WQ_FLAG_EXCLUSIVE從隊頭添加。這樣可以保證每次都能喚醒所有的非WQ_FLAG_EXCLUSIVE進程。

 

無鎖但設置屬性的添加刪除

static inline void add_wait_queue_exclusive_locked(wait_queue_head_t *q,

                                                  wait_queue_t * wait)

{

        wait->flags |= WQ_FLAG_EXCLUSIVE;

        __add_wait_queue_tail(q,  wait);

}

 

/* Must be called with the spinlock in the wait_queue_head_t held.*/

static inline void remove_wait_queue_locked(wait_queue_head_t *q,

                                           wait_queue_t * wait)

{

        __remove_wait_queue(q,  wait);

}

Locked系列適用於在已經獲得鎖的情況下調用,通常用於信號量后者complete系列函數中。

 

上述三組函數,__add_wait_queue是內部函數,無任何保護,無任何屬性設置;而另外兩組分別適用於當前是否加鎖的兩種場合,是對外的接口函數。這種根據適用場合不同提供不同的接口函數的方法值得借鑒。

 

2.3.3      prepare_to_waitfinish_wait

/*

 * Used to distinguish between sync and async io wait context:

 * sync i/o typically specifies a NULL wait queue entry or a wait

 * queue entry bound to a task (current task) to wake up.

 * aio specifies a wait queue entry with an async notification

 * callback routine not associated with any task.

 */

#define is_sync_wait(wait)       (!(wait) || ((wait)->private))

同步io等待將喚醒當前進程,異步io等待和當前進程無關,時間到后執行安裝的回調函

 

void fastcall

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

 

        if (is_sync_wait(wait))

                set_current_state(state);

        spin_unlock_irqrestore(&q->lock flags);

}

 

prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)

wait->flags |= WQ_FLAG_EXCLUSIVE;

排他性等待,其余和prepare_to_wait一樣

 

void fastcall finish_wait(wait_queue_head_t *q wait_queue_t *wait)

{

        unsigned long flags;

 

        __set_current_state(TASK_RUNNING); //確保進程狀態為running

 

        //若有等待進程,則將其從等待隊列中刪除

        if (!list_empty_careful(&wait->task_list)) {

                spin_lock_irqsave(&q->lock flags);

                list_del_init(&wait->task_list);

                spin_unlock_irqrestore(&q->lock flags);

        }

}

 

 

3      等待事件event

Linux 內核中最簡單的休眠方式是稱為 wait_event(及其變種),它實現了休眠和進程等待的條件的檢查。形式如下:

wait_event(queue, condition)/*不可中斷休眠,不推薦*/wait_event_interruptible(queue, condition)/*推薦,返回非零值意味着休眠被中斷,且驅動應返回 -ERESTARTSYS*/wait_event_timeout(queue, condition, timeout)wait_event_interruptible_timeout(queue, condition, timeout)/*有限的時間的休眠;若超時,則不管條件為何值返回0*/

 

上述四個宏函數為內核對外的接口,其他的休眠函數應避免使用。因為宏並不是函數,參數所做的任何修改對調用環境仍然有效,所以queue都是“值傳遞”,在宏內部會調用底層函數,采用的指針傳遞。Linux內核中存在大部分這樣的宏,其都在接口上都是值傳遞。

 

3.1   wait_event

認真地看簡單休眠中的 wait_event(queue, condition) wait_event_interruptible(queue, condition) 底層源碼會發現,其實他們只是手工休眠中的函數的組合。因此在驅動程序中應避免使用手動休眠代碼,而應該采用內核已經封裝好的四個wait_event系列函數。

 

/include/linux/wait.h

#define  __wait_event(wqcondition)                /

     do {                           /

       DEFINE_WAIT(__wait);                       /

                                    /

for(;;) {                      /

          prepare_to_wait(&wq&__waitTASK_UNINTERRUPTIBLE);   /

/// 添加到等待隊列中,同時更改進程狀態;若已經加入則不會重復添加 

        if (condition)                        /

           break;                          /

       schedule();   //何時返回呢???                       /

     }                               /

     finish_wait(&wq&__wait);                    /

    } while (0)

// “__”表示內部函數,默認為condition不滿足,添加至等待隊列,調度

注意prepare_to_waitfinish_wait的匹配關系

 

#define  wait_event(wqcondition)                    /

do {                                   /

    if(condition)                              /

       break;                               /

   __wait_event(wqcondition);                    /

 }while (0)   

//對外的接口函數,需要判斷condition,若假則等待;若真則直接退出

  --------------------------------------------------------------------------------------------------------------

等待系列函數架構設計:

 

3.2   wait_event_timeout

#define __wait_event_timeout(wq condition ret)                     /

do {                                                               /

        DEFINE_WAIT(__wait);                                       /

                                                                      /

        for (;;) {                                                  /

               prepare_to_wait(&wq &__wait TASK_UNINTERRUPTIBLE);   /

               if (condition)                                            /

                       break;                                             /

               ret = schedule_timeout(ret);                             /

               if (!ret)                                            /

                       break;              //延時到,退出                              /

        }                                                            /

        finish_wait(&wq &__wait);                                /

} while (0)

 

#define  wait_event_timeout(wqconditiontimeout)      /

({        /

      long   __ret=timeout;     /

    if( !(condition) )             /

      __wait_event_timeout( wqcondition__ret);     /

    __ret;     /

})

 

 

3.3   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_event_interruptible - sleep until a condition gets true

 * @wq: the waitqueue to wait on

 * @condition: a C expression for the event to wait for

 *

 * The process is put to sleep (TASK_INTERRUPTIBLE) until the

 * @condition evaluates to true or a signal is received.

 * The @condition is checked each time the waitqueue @wq is woken up.

 *

 * wake_up() has to be called after changing any variable that could

 * change the result of the wait condition.

 *

 * The function will return -ERESTARTSYS if it was interrupted by a

 * signal and 0 if @condition evaluated to true.

 */

#define wait_event_interruptible(wq, condition)                        /

({                                                                   /

        int __ret = 0;                                                   /

        if (!(condition))                                        /

                __wait_event_interruptible(wq, condition, __ret);      /

        __ret;                                                             /

})

 

3.4   wait_event_interruptible_timeout

#define   __wait_event_interruptible_timeout(wqconditionret)     /

do {                               /

       DEFINE_WAIT(__wait);                     /

                                   /

       for (;;) {                          /

           prepare_to_wait(&wq&__waitTASK_INTERRUPTIBLE);   /  

               if (condition)                   /

                   break;                     /

               if(!signal_pending(current)) {                 /

                   // 當前進程無信號需要處理

ret = schedule_timeout(ret);            /

                   if(!ret)                   /              

                      break; //時間片用完喚醒                     /

                   continue;                 /              .

               }                            /

               ret = _ERESTARTSYS;   //被信號喚醒                  /

               break;                            /

             }                            /

             finish_wait(&wq&__wait);                   /

  } while (0)    

 

#define   wait_event_interruptible_timeout(wqconditiontimeout)    /

( {                                 /

    long__ret = timeout;                      /

    if(!(condition))                       /

        __wait_event_interruptible_timeout(wqcondition__ret);  /

    __ret;                               /

 })

 

    wait_event_interruptible_timeout()類架構: 

 

4      喚醒系列wake_up

4.1   wake_up API

慣例:用 wake_up 喚醒 wait_event;用 wake_up_interruptible 喚醒wait_event_interruptible。很少會需要調用wake_up_interruptible 之外的喚醒函數,但為完整起見,這里是整個集合:

wake_up(wait_queue_head_t *queue); wake_up_interruptible(wait_queue_head_t *queue); /*wake_up 喚醒隊列中的每個非獨占等待進程和一個獨占等待進程。wake_up_interruptible 同樣, 但它跳過處於不可中斷休眠的進程。它們在返回之前, 使一個或多個進程被喚醒、被調度(如果它們被從一個原子上下文調用, 這就不會發生).*/


wake_up_nr(wait_queue_head_t *queue, int nr); wake_up_interruptible_nr(wait_queue_head_t *queue, int nr); /*這些函數類似 wake_up, 除了它們能夠喚醒多達 nr 個獨占等待者, 而不只是一個. 注意傳遞 0 被解釋為請求所有的互斥等待者都被喚醒*/


wake_up_all(wait_queue_head_t *queue); wake_up_interruptible_all(wait_queue_head_t *queue); /*這種 wake_up 喚醒所有的進程, 不管它們是否進行獨占等待(可中斷的類型仍然跳過在做不可中斷等待的進程)*/


wake_up_interruptible_sync(wait_queue_head_t *queue); /*一個被喚醒的進程可能搶占當前進程, 並且在 wake_up 返回之前被調度到處理器 但是, 如果你需要不要被調度出處理器時,可以使用 wake_up_interruptible "同步"變體. 這個函數最常用在調用者首先要完成剩下的少量工作,且不希望被調度出處理器時。*/

 

4.2   wake_up 的實現細節

 

/kernel /sched.c

/*

 * The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just

 * wake everything up.  If it's an exclusive wakeup (nr_exclusive == small +ve

 * number) then we wake all the non-exclusive tasks and one exclusive task.

 *

 * There are circumstances in which we can try to wake a task which has already

 * started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns

 * zero in this (rare) case and we handle it by continuing to scan the queue.

 */

static void __wake_up_common(wait_queue_head_t *q unsigned int mode

                            int nr_exclusive int sync void *key)

{

        struct list_head *tmp *next;

 

        list_for_each_safe(tmp next &q->task_list) {

                wait_queue_t *curr = list_entry(tmp wait_queue_t task_list);

                unsigned flags = curr->flags;

 

                if (curr->func(curr mode sync key) &&

                               (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)

                       break;

        }

}

// 循環結束的條件包括list_for_each_safe本身、排他性(flags & WQ_FLAG_EXCLUSIVE)以及喚醒非0個進程,!--nr_exclusive非常巧妙,當傳入0時,!--nr_exclusive的結果總是0if條件不可能成立,就無法break,即表示喚醒所有的進程。對於非WQ_FLAG_EXCLUSIVE進程,由於(flags & WQ_FLAG_EXCLUSIVE)0后,就不計算!--nr_exclusive,因此這個過程可以喚醒所有的非WQ_FLAG_EXCLUSIVE進程。但遇到WQ_FLAG_EXCLUSEVE之后的任意進程無法喚醒。

最終哪個進程運行是由schedule決定的。

 

/**

 * __wake_up - wake up threads blocked on a waitqueue.

 * @q: the waitqueue

 * @mode: which threads

 * @nr_exclusive: how many wake-one or wake-many threads to wake up

 * @key: is directly passed to the wakeup function

 */

void fastcall __wake_up(wait_queue_head_t *q unsigned int mode int nr_exclusive void *key)

{

        unsigned long flags;

 

        spin_lock_irqsave(&q->lock flags);

        __wake_up_common(q mode nr_exclusive 0 key);

//通用的wakeup,不可重入的,需要__wake_up對之進行封裝

        spin_unlock_irqrestore(&q->lock flags);

}

EXPORT_SYMBOL(__wake_up);

根據int nr_exclusive值喚醒對應的進程,只是更改了進程的狀態,具體何時運行由schedule決定。

並沒有將喚醒的進程從等待隊列中刪除,只有當schedule獲得cpu時才從等待隊列中刪除。

 

/include/linux/wait.h

void FASTCALL(__wake_up(wait_queue_head_t *q unsigned int mode int nr void *key));

extern void FASTCALL(__wake_up_locked(wait_queue_head_t *q unsigned int mode));

extern void FASTCALL(__wake_up_sync(wait_queue_head_t *q unsigned int mode int nr));

 

#define wake_up(x)                 __wake_up(x TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE 1 NULL)

#define wake_up_nr(x nr)           __wake_up(x TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE nr NULL)

#define wake_up_all(x)                   __wake_up(x TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE 0 NULL)

#define wake_up_interruptible(x)     __wake_up(x TASK_INTERRUPTIBLE 1 NULL)

#define wake_up_interruptible_nr(x nr)       __wake_up(x TASK_INTERRUPTIBLE nr NULL)

#define wake_up_interruptible_all(x)        __wake_up(x TASK_INTERRUPTIBLE 0 NULL)

各種wakeup通過宏定義的形式本質上就是一個函數__wake_up,但對外的接口不一樣,這樣對外的意義明確,相當於采用了默認參數,而不是在各個wakeup內部調用函數,省掉了函數調用的開銷。在實現代碼復用的同時保證了對外的明確接口,值得借鑒。

 

#define     wake_up_locked(x)          __wake_up_locked((x) TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)

#define wake_up_interruptible_sync(x)   __wake_up_sync((x)TASK_INTERRUPTIBLE 1)

 

5      獨占等待和高級休眠

5.1   獨占等待

當一個進程調用 wake_up 在等待隊列上,所有的在這個隊列上等待的進程被置為可運行的。 這在許多情況下是正確的做法。但有時,可能只有一個被喚醒的進程將成功獲得需要的資源,而其余的將再次休眠。這時如果等待隊列中的進程數目大,這可能嚴重降低系統性能。為此,內核開發者增加了一個獨占等待選項。它與一個正常的睡眠有 2 個重要的不同:

²      當等待隊列入口設置了 WQ_FLAG_EXCLUSEVE 標志,它被添加到等待隊列的尾部;否則,添加到頭部。因為喚醒一個WQ_FLAG_EXCLUSEVE標志的進程后就不再喚醒其他任意類型的進程。添加在尾部可以保證FIFO

²      wake_up 被在一個等待隊列上調用, 它在喚醒第一個有 WQ_FLAG_EXCLUSIVE 標志的進程后停止喚醒。但內核仍然會喚醒所有的非獨占等待進程,因為所有的非WQ_FLAG_EXCLUSIVE進程在隊頭

 

采用獨占等待要滿足以下條件:

²      希望對資源進行有效競爭;

²      當資源可用時,喚醒一個進程就足夠來完全消耗資源;

²      所有使用該資源的進程都應采用統一的獨占等待規則。

 

使一個進程進入獨占等待,可調用:

void prepare_to_wait_exclusive(wait_queue_head_t *queue, wait_queue_t *wait, int state);

 

注意:無法使用通用的wait_event 和它的變體宏函數來進行獨占等待。

此時需要使用休眠的高級特性,利用等待隊列的prepare_to_wait_exclusivefinish_wait接口函數手動編寫相關代碼,

 

5.2   高級休眠的基本步驟:

1)分配和初始化一個 wait_queue_t 結構, 隨后將其添加到正確的等待隊列。

2)設置進程狀態,標記為休眠。TASK_RUNNING 意思是進程能夠運行。有 2 個狀態指示一個進程是在睡眠: TASK_INTERRUPTIBLE TASK_UNTINTERRUPTIBLE2.6 內核的驅動代碼通常不需要直接操作進程狀態。但如果需要這樣做使用的代碼是:

void set_current_state(int new_state);

在老的代碼中, 你常常見到如此的東西:current->state = TASK_INTERRUPTIBLE; 但是象這樣直接改變 current 是不推薦的,當數據結構改變時這樣的代碼將會失效。通過改變 current 狀態,只改變了調度器對待進程的方式,但進程還未讓出處理器。

 

3)最后一步是放棄處理器。 但必須先檢查進入休眠的條件。如果不做檢查會引入競態: 如果決定休眠后,在做上述准備工作到真正調用schedule時,若等待的條件變為真,不對條件重新進行判斷,則你可能錯過喚醒且長時間休眠。因此典型的代碼下:

if (!condition)    schedule();

在調用schedule前,應對條件再次進行檢查。

 

4)更改進程狀態並將進程從等待隊列中刪除。

如果代碼是從 schedule 返回,則進程肯定處於TASK_RUNNING 狀態。 如果不需睡眠而跳過對 schedule 的調用,必須將任務狀態重置為 TASK_RUNNING。無論是否調用過schedule,都需要從等待隊列中去除這個進程,否則它可能被多次喚醒。

 

5.3   手工休眠的具體函數執行流

特殊睡眠要求程序員手動處理所有上面的步驟. 它是一個繁瑣的過程, 包含相當多的易出錯的樣板式的代碼. 程序員如果願意還是可能用那種方式手動睡眠。

1)創建和初始化一個等待隊列。常由宏定義完成: DEFINE_WAIT(my_wait);/*name 是等待隊列入口項的名字. 也可以用2步來做:*/wait_queue_t my_wait;init_wait(&my_wait);/*常用的做法是放一個 DEFINE_WAIT 循環的頂部,來實現休眠。*/

 

2)添加等待隊列入口到隊列,並設置進程狀態: void prepare_to_wait(wait_queue_head_t *queue, wait_queue_t *wait, int state); /*queue wait 分別地是等待隊列頭和進程入口。state 是進程的新狀態TASK_INTERRUPTIBLE(可中斷休眠,推薦)TASK_UNINTERRUPTIBLE(不可中斷休眠,不推薦)*/prepare_to_wait_exclusive

3)在檢查確認仍然需要休眠之后調用 schedule

schedule();

 

4schedule 返回,重新判斷等待條件,若為真則退出,否則繼續schedule

 

5)條件滿足退出后,確保狀態為running,同時將進程從等待隊列中刪除。

void finish_wait(wait_queue_head_t *queue, wait_queue_t *wait);

 

5.4   wait_event_interruptible_exclusive

為了避免手工編寫上述復雜代碼,內核提供了最常見的interruptible類型排他性等待函數。而對於非排他性的可以直接利用等待事件event系列函數。

 

#define __wait_event_interruptible_exclusive(wq, condition, ret)      /

do {                                                                /

        DEFINE_WAIT(__wait);                                       /

                                                                      /

        for (;;) {                                                  /

                prepare_to_wait_exclusive(&wq, &__wait,                     /

                                       TASK_INTERRUPTIBLE);            /

                if (condition)                                            /

                       break;                                             /

                if (!signal_pending(current)) {                          /

                       schedule();                                      /

                       continue;                                 /

                }                                                     /

                ret = -ERESTARTSYS;                                  /

                break;                                                     /

        }                                                            /

        finish_wait(&wq, &__wait);                                    /

} while (0)

 

#define wait_event_interruptible_exclusive(wq, condition)        /

({                                                                   /

        int __ret = 0;                                                   /

        if (!(condition))                                        /

                __wait_event_interruptible_exclusive(wq, condition, __ret);/

        __ret;                                                             /

})

 

6      Completion

completion是一種輕量級的機制,它允許一個線程告訴另一個線程某個工作已經完成。代碼必須包含<linux/completion.h>。使用的代碼如下:

 DECLARE_COMPLETION(my_completion);/* 創建completion(聲明+初始化) */struct completion my_completion;/* 動態聲明completion 結構體*/static inline void init_completion(&my_completion);/*動態初始化completion*/void wait_for_completion(struct completion *c);/* 等待completion */void complete(struct completion *c);/*喚醒一個等待completion的線程*/void complete_all(struct completion *c);/*喚醒所有等待completion的線程*//*如果未使用completion_allcompletion可重復使用;否則必須使用以下函數重新初始化completion*/INIT_COMPLETION(struct completion c);/*快速重新初始化completion*/

 

completion的典型應用是模塊退出時的內核線程終止。在這種模式,某些驅動程序的內部工作有一個內核線程在while(1)循環中完成。當內核准備清除該模塊時,exit函數會告訴該線程退出並等待completion為此內核包含了用於這種線程的一個特殊函數:

void complete_and_exit(struct completion *c, long retval);

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
  © 2014-2022 ITdaan.com 联系我们: