阻塞與非阻塞是設備訪問的兩種方式。驅動程序需要提供阻塞(等待隊列,中斷)和非阻塞方式(輪詢,異步通知)訪問設備。在寫阻塞與非阻塞的驅動程序時,經常用到等待隊列。
一、阻塞與非阻塞
阻塞調用是沒有獲得資源則掛起進程,被掛起的進程進入休眠狀態,調用的函數只有在得到結果之后才返回,進程繼續。
非阻塞是不能進行設備操作時不掛起,或返回,或反復查詢,直到可以進行操作為止,被調用的函數不會阻塞當前進程,而會立刻返回。
對象是否處於阻塞模式和函數是不是阻塞調用有很強的相關性,但並不是一一對應的。阻塞對象上可以有非阻塞的調用方式,我們可以通過一定的API去輪詢 狀態,在適當的時候調用阻塞函數,就可以避免阻塞。而對於非阻塞對象,調用的函數也可以進入阻塞調用。函數select()就是這樣一個例子。
驅動程序常需要這種能力:當應用程序進程read(),write()等系統調用時,若設備的資源不能獲取,而用戶又希望以阻塞的方式訪問設備,驅動程序應該在設備驅動程序的xxx_read(),xxx_write()等操作中將進程阻止到資源可以獲取,以后,應用程序的read(),write()等調用返回,整個過程仍然進行了正確的設備訪問,用戶並沒有感知到。若用戶以非阻塞的方式訪問設備文件,則當設備資源不可獲取時,設備驅動的xxx_read(),xxx_write()等操作應立即返回,read(),write()等喜用調用也隨即被訪問。
阻塞不是低效率,如果設備驅動不阻塞,用戶想獲取設備資源只能不斷查詢,小號CPU資源,阻塞訪問時,不能獲取資源的進程將進入休眠,將CPU資源讓給其他資源。
阻塞的進程會進入休眠狀態,因此,必須確保有一個地方能喚醒休眠的進程。喚醒進程的地方最大可能發生在終端里面,因為硬件資源的獲得往往伴隨着一個終端。
對象是否處於阻塞模式和函數是不是阻塞調用有很強的相關性,但並不是一一對應的。阻塞對象上可以有非阻塞的調用方式,我們可以通過一定的API去輪詢狀 態,在適當的時候調用阻塞函數,就可以避免阻塞。而對於非阻塞對象,調用的函數也可以進入阻塞調用。函數select()就是這樣一個例子。
二、等待隊列
在linux驅動程序中,可使用等待隊列(wait queue)來實現阻塞進程的喚醒,以隊列為基礎數據結構,與進程調度機制緊密結合,用於視線內核的異步事件通知機制,也可用於同步對系統資源的訪問。(信號量在內核中也依賴等待隊列來實現)
在軟件開發中任務經常由於某種條件沒有得到滿足而不得不進入睡眠狀態,然后等待條件得到滿足的時候再繼續運行,進入運行狀態。這種需求需要等待隊列機制的支持。Linux中提供了等待隊列的機制,該機制在內核中應用很廣泛。
等待隊列在linux內核中有着舉足輕重的作用,很多linux驅動都或多或少涉及到了等待隊列。因此,對於linux內核及驅動開發者來說,掌握等待隊 列是必須課之
Linux內核的等待隊列是以雙循環鏈表為基礎數據結構,與進程調度機制緊密結合,能夠用於實現核心的異步事件通知機制。它有兩種數據結構:等待隊列頭 (wait_queue_head_t)和等待隊列項(wait_queue_t)。等待隊列頭和等待隊列項中都包含一個list_head類型的域作為”連接件”。它通過一個雙鏈表和把等待task的頭,和等待的進程列表鏈接起來。下面具體介紹。
1.定義
頭文件:/include/linux/wait.h
struct __wait_queue_head { spinlock_t lock; /* 保護等待隊列的原子鎖 (自旋鎖),在對task_list與操作的過程中,使用該鎖實現對等待隊列的互斥訪問*/ struct list_head task_list; /* 等待隊列,雙向循環鏈表,存放等待的進程 */ }; /*__wait_queue,該結構是對一個等待任務的抽象。每個等待任務都會抽象成一個wait_queue,並且掛載到wait_queue_head上。該結構定義如下:*/ struct __wait_queue { unsigned int flags;#define WQ_FLAG_EXCLUSIVE 0x01
void *private; /* 通常指向當前任務控制塊 */ wait_queue_func_t func; struct list_head task_list; /* 掛入wait_queue_head的掛載點 */ };typedef
struct
__wait_queue wait_queue_t;
/* 任務喚醒操作方法,該方法在內核中提供,通常為auto remove_wake_function */
2.操作:
(1) 定義並初始化"等待隊列頭"
wait_queue_head_t my_queue; init_waitqueue_head(&my_queue); //會將自旋鎖初始化為未鎖,等待隊列初始化為空的雙向循環鏈表。 //宏名用於定義並初始化,相當於"快捷方式" DECLARE_WAIT_QUEUE_HEAD (my_queue);
(2) 定義"等待隊列"
/*定義並初始化一個名為name的等待隊列 ,注意此處是定義一個wait_queue_t類型的變量name,並將其private與設置為tsk*/ DECLARE_WAITQUEUE(name,tsk);
struct __wait_queue { unsigned int flags; #define WQ_FLAG_EXCLUSIVE 0x01 void *private; /* 通常指向當前任務控制塊 */ wait_queue_func_t func; struct list_head task_list; /* 掛入wait_queue_head的掛載點 */ }; typedef struct __wait_queue wait_queue_t;
其中flags域指明該等待的進程是互斥進程還是非互斥進程。其中0是非互斥進程,WQ_FLAG_EXCLUSIVE(0×01)是互斥進程。等待隊列 (wait_queue_t)和等待對列頭(wait_queue_head_t)的區別是等待隊列是等待隊列頭的成員。也就是說等待隊列頭的task_list域鏈接的成員就是等待隊列類型的(wait_queue_t)。
(3) (從等待隊列頭中)添加/刪除等待隊列
//設置等待的進程為非互斥進程,並將其添加進等待隊列頭(q)的隊頭中。 void 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); //下面函數也和add_wait_queue()函數功能基本一樣,只不過它是將等待的進程(wait)設置為互斥進程。 void 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 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);
(4) 等待事件
#define wait_event(wq, condition) do { if (condition) break; __wait_event(wq, condition); } while (0) wait_event(queue,condition);等待以queue為等待隊列頭等待隊列被喚醒,condition必須滿足,否則阻塞 wait_event_interruptible(queue,condition);可被信號打斷 wait_event_timeout(queue,condition,timeout);阻塞等待的超時時間,時間到了,不論condition是否滿足,都要返回 wait_event_interruptible_timeout(queue,condition,timeout)
wait_event()宏:
在等待會列中睡眠直到condition為真。在等待的期間,進程會被置為TASK_UNINTERRUPTIBLE進入睡眠,直到condition變量變為真。每次進程被喚醒的時候都會檢查condition的值.
wait_event_interruptible()函數:
和wait_event()的區別是調用該宏在等待的過程中當前進程會被設置為TASK_INTERRUPTIBLE狀態.在每次被喚醒的時候,首先檢查 condition是否為真,如果為真則返回,否則檢查如果進程是被信號喚醒,會返回-ERESTARTSYS錯誤碼.如果是condition為真,則 返回0.
wait_event_timeout()宏:
也與wait_event()類似.不過如果所給的睡眠時間為負數則立即返回.如果在睡眠期間被喚醒,且condition為真則返回剩余的睡眠時間,否則繼續睡眠直到到達或超過給定的睡眠時間,然后返回0.
wait_event_interruptible_timeout()宏
與wait_event_timeout()類似,不過如果在睡眠期間被信號打斷則返回ERESTARTSYS錯誤碼.
wait_event_interruptible_exclusive()宏
同樣和wait_event_interruptible()一樣,不過該睡眠的進程是一個互斥進程.
(5)喚醒隊列
/* __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 __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); spin_unlock_irqrestore(&q->lock, flags); } EXPORT_SYMBOL(__wake_up); //喚醒等待隊列.可喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERUPTIBLE狀態的進程,和wait_event/wait_event_timeout成對使用 #define wake_up_interruptible(x) __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL) //和wake_up()唯一的區別是它只能喚醒TASK_INTERRUPTIBLE狀態的進程.,與wait_event_interruptible wait_event_interruptible_timeout wait_event_interruptible_exclusive成對使用 #define wake_up_all(x) __wake_up(x, TASK_NORMAL, 0, 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) //這些也基本都和wake_up/wake_up_interruptible一樣
wake_up()與wake_event()或者wait_event_timeout成對使用,
wake_up_intteruptible()與wait_event_intteruptible()或者wait_event_intteruptible_timeout()成對使用。
(6) 在等待隊列上睡眠:
sleep_on()函數
void __sched sleep_on(wait_queue_head_t *q) { sleep_on_common(q, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } static long __sched sleep_on_common(wait_queue_head_t *q, int state, long timeout) { unsigned long flags; wait_queue_t wait; init_waitqueue_entry(&wait, current); __set_current_state(state); spin_lock_irqsave(&q->lock, flags); __add_wait_queue(q, &wait); spin_unlock(&q->lock); timeout = schedule_timeout(timeout); spin_lock_irq(&q->lock); __remove_wait_queue(q, &wait); spin_unlock_irqrestore(&q->lock, flags); return timeout; }
該函數的作用是定義一個等待隊列(wait),並將當前進程添加到等待隊列中(wait),然后將當前進程的狀態置為 TASK_UNINTERRUPTIBLE,並將等待隊列(wait)添加到等待隊列頭(q)中。之后就被掛起直到資源可以獲取,才被從等待隊列頭(q) 中喚醒,從等待隊列頭中移出。在被掛起等待資源期間,該進程不能被信號喚醒。
sleep_on_timeout()函數
long __sched sleep_on_timeout(wait_queue_head_t *q, long timeout) { return sleep_on_common(q, TASK_UNINTERRUPTIBLE, timeout); } EXPORT_SYMBOL(sleep_on_timeout);
與sleep_on()函數的區別在於調用該函數時,如果在指定的時間內(timeout)沒有獲得等待的資源就會返回。實際上是調用 schedule_timeout()函數實現的。值得注意的是如果所給的睡眠時間(timeout)小於0,則不會睡眠。該函數返回的是真正的睡眠時間。
interruptible_sleep_on()函數
void __sched interruptible_sleep_on(wait_queue_head_t *q) { sleep_on_common(q, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } EXPORT_SYMBOL(interruptible_sleep_on);
該函數和sleep_on()函數唯一的區別是將當前進程的狀態置為TASK_INTERRUPTINLE,這意味在睡眠如果該進程收到信號則會被喚醒。
interruptible_sleep_on_timeout()函數:
long __sched interruptible_sleep_on_timeout(wait_queue_head_t *q, long timeout) { return sleep_on_common(q, TASK_INTERRUPTIBLE, timeout); } EXPORT_SYMBOL(interruptible_sleep_on_timeout);
類似於sleep_on_timeout()函數。進程在睡眠中可能在等待的時間沒有到達就被信號打斷而被喚醒,也可能是等待的時間到達而被喚醒。
以上四個函數都是讓進程在等待隊列上睡眠,不過是小有詫異而已。在實際用的過程中,根據需要選擇合適的函數使用就是了。例如在對軟驅數據的讀寫中,如果設 備沒有就緒則調用sleep_on()函數睡眠直到數據可讀(可寫),在打開串口的時候,如果串口端口處於關閉狀態則調用 interruptible_sleep_on()函數嘗試等待其打開。在聲卡驅動中,讀取聲音數據時,如果沒有數據可讀,就會等待足夠常的時間直到可讀取。