最近看epoll 和 select 都涉及到一個東西叫作設備等待隊列,等待隊列是如何工做的,內核是怎麼管理的?看這篇文章html
- 問題:進程是如何組織起來的?
咱們知道,進程是有不少種狀態的:include/linux/sched.h
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
等等。
那麼,對於不一樣狀態的進程,內核是如何來管理的呢?
- 就緒隊列:狀態爲TASK_RUNNING的進程組成的列表;
- 處於TASK_STOPPED、EXIT_ZOMBIE或者EXIT_DEAD狀態的進程是不須要鏈接進特定鏈表的。由於對於這些狀態的進程而言,父進程只會經過 PID或者子進程鏈表來進行訪問。
- 而處於TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE狀態的進程分爲不少種類型,其每一個進程對應一 種特定事件。在這種狀況下,進程的狀態信息是不能提供足夠的信息去快速的檢索所需進程,所以有必要介紹一些其餘的鏈表組織結構。好比等待隊列。
- 等待隊列:
在內核裏面,等待隊列是有不少用處的,尤爲是在中斷處理、進程同步、定時等場合。咱們主要討論其在進程同步中的應用。
有時候,一個進程可能要等待一些事件的發生,如磁盤操做結束、一些系統資源的釋放等等。我的理理解:等待隊列就是暫時存放等待某些事件發生的進程的集合。若是一個進程要等待一個事件發生,那麼該進程便將自身放入相應的等待隊列中進入睡眠,而放棄控制權,直到等待事件發生後纔會被內核喚醒。
- 等待隊列的結構:
![](http://static.javashuo.com/static/loading.gif)
- 等待隊列是以雙循環鏈表的形式實現的,並且隊列中的成分(等待隊列項)包含了指向進程描述符task的指針。
- 等待隊列頭:include/linux/wait.h
每個等待隊列是有一個等待隊列頭,等待隊列頭是一個wait_queue_head_t的數據結構:
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;
成員說明:
lock:由於等待隊列是不容許多個進程同時進行訪問的,以防產生不可預料的結果,所以在此結構中定義了"自旋鎖"以實現訪問間的同步。
task_list:用於實現雙向鏈表形式。
struct list_head {
struct list_head *next, *prev;
};
- 等待隊列項:include/linux/wait.h
struct __wait_queue {
unsigned int flags;
struct task_struct *task;(2.6.25.5中是void *private;)
wait_queue_func_t func;
struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;
等待隊列中的每個成分:等待隊列項,表明着一個正在等待特定事件發生的睡眠進程。
成員解釋:
task:存放着睡眠進程狀態描述符的地址;
task_list:用於將進程連接進等待相同事件發生的進程鏈表中(等待隊列)。
flag:
互斥進程(exclusive processes)和非互斥進程:
咱們來考慮一下,若是等待的事件發生了、變爲真的了,那麼是否是要喚醒等待該事件的全部進程(某個等待隊列中)呢?
老是喚醒全部等待該事件的進程並不必定是合適的。好比考慮這樣一種狀況:若是隊列中的多個進程等待的資源是要互斥訪問的,必定時間內只容許一個進程去訪問的話,這時候,只須要喚醒一個進程就能夠了,其餘進程繼續睡眠。若是喚醒全部的進程,最終也只有一個進程得到該資源,其餘進程讓需返回睡眠。
所以,等待隊列中的睡眠進程可被劃分爲互斥、非互斥進程。
互斥進程:等待的資源是互斥訪問的;互斥進程由內核有選擇的喚醒,等待隊列項的flag字段爲1;
非互斥進程:等待的資源是可多進程同時訪問的。非互斥進程在事件發生時,老是被內核喚醒,等待隊列元素的flag字段爲0。
func:
指定等待隊列中的睡眠進程如何被喚醒。
- 等待隊列的建立:include/linux/wait.h
DECLARE_WAITQUEUE()
init_waitqueue_head()
能夠用DECLARE_WAIT_QUEUE_HEAD(name)宏定義一個新的等待隊列,該宏靜態地聲明和初始化名爲name的等待隊列頭變量。 init_waitqueue_head()函數用於初始化已動態分配的wait queue head變量。
等待隊列能夠經過 DECLARE_WAITQUEUE()靜態建立,也能夠用 init_waitqueue_head()動態建立。進程把本身放入等待隊列中並設置成不可執行狀態。 例:
The init_waitqueue_entry(q, p) function initializes a wait_queue_t structure q as follows:
q->flags = 0;
q->task = p;
q->func = default_wake_function;
非互斥進程p(flags = 0)將被default_wake_function函數喚醒,而default_wake_function喚醒函數是try_to_wake_up( )的包裝而已。
DEFINE_WAIT:
能夠用宏DEFINE_WAIT聲明一個新的wait_queue_t變量(等待隊列項),而且對其進行初始化:
#define DEFINE_WAIT(name) \
wait_queue_t name = { \
.private = current, \
.func = autoremove_wake_function, \
.task_list = LIST_HEAD_INIT((name).task_list), \
}
- 等待隊列的添加和刪除:
- add_wait_queue( ):kernel/wait.c
add_wait_queue_exclusive( )
add_wait_queue()函數把一個非互斥進程插入等待隊列鏈表的第一個位置;
在wait.c中:
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(),而且使用了所機制對該操做進行互斥保護。
add_wait_queue_exclusive( )函數把一個互斥進程插入等待隊列鏈表的最後一個位置;
- remove_wait_queue( ):
remove_wait_queue( )函數從等待隊列鏈表中刪除一個進程;
- waitqueue_active( ):
waitqueue_active( )函數檢查一個給定的等待隊列是否爲空。
- 等待隊列的使用:睡眠和喚醒:/kernel/sched.c
該組函數使用任務管理中公用形式的等待隊列。
但願等待一個特定事件的進程能調用下列函數中的任一個:
- 睡眠操做:思想是更改當前進程(CURRENT)的任務狀態,並要求從新調度,由於這時這個進程的狀態已經改變,再也不在調度表的就緒隊列中,所以沒法再得到執行機會,進入"睡眠"狀態,直至被"喚醒"(wake_up()),即其任務狀態從新被修改回就緒態。
經常使用的睡眠操做有interruptible_sleep_on和sleep_on,兩個函數相似,是把調用進程加入到特定的等待隊列中,只不過前者將進程的狀態從就緒態 (TASK_RUNNING)設置爲TASK_INTERRUPTIBLE,容許經過發送signal喚醒它(便可中斷的睡眠狀態);然後者將進程的狀態 設置爲TASK_UNINTERRUPTIBLE,在這種狀態下,不接收任何singal。
在當前進程上操做的sleep_on()函數:
void sleep_on(wait_queue_head_t *wq)
{
wait_queue_t wait;
/* 構造當前進程對應的等待隊列項 */
init_waitqueue_entry(&wait, current); //wait.h中
/* 將當前進程的狀態從TASK_RUNNING改成TASK_UNINTERRUPTIBLE */
current->state = TASK_UNINTERRUPTIBLE;
/* 將等待隊列項添加到指定鏈表中 */
wq_write_lock_irqsave(&q->lock,flags);
__add_wait_queue(q, &wait);
wq_write_unlock(&q->lock);
/* 進程從新調度,放棄執行權 */
schedule( );
/* 本進程被喚醒,從新得到執行權,首要之事是將等待隊列項從鏈表中刪除 */
wq_write_lock_irq(&q->lock);
__remove_wait_queue(q, &wait);
wq_write_unlock_irqrestore(&q->lock,flags);
/* 至此,等待過程結束,本進程能夠正常執行下面的邏輯 */
}
該函數把當前進程的狀態設置爲TASK_UNINTERRUPTIBLE,並把它插入到特定的等待隊列。而後,它調用調度程序,而調度程序從新開始另外一個進程的執行。當睡眠進程被喚醒時,調度程序從新開始執行sleep_on()函數,把該進程隊列中刪除。
- interruptible_sleep_on():
interruptible_sleep_on()與sleep_on()函數基本上是同樣的,可是interruptible_sleep_on()把 當前進程的狀態設置爲TASK_INTERRUPTIBLE而不是TASK_UNINTERRUPTIBLE,所以,接受一個信號就能夠喚醒當前進程;
- sleep_on_timeout()interruptible_sleep_on_timeout()於上述兩個函數相似,只是他們還容許調用者定義一個時間間隔使得進程被內核喚醒;可是,在這兩個函數中調用的是schedule_timeout()來代替schedule()。
- prepare_to_wait()、prepare_to_wait_exclusive()、finish_wait():在wait.c中:
是在linux2.6中介紹的,將當前進程放入等待隊列的另外一種方式。做用同sleep_on()。
- 對應的喚醒操做包括wake_up_interruptible和wake_up。wake_up函數不只能夠喚醒狀態爲 TASK_UNINTERRUPTIBLE的進程,並且能夠喚醒狀態爲TASK_INTERRUPTIBLE的進程。 wake_up_interruptible只負責喚醒狀態爲TASK_INTERRUPTIBLE的進程。這兩個宏的定義以下:
#define wake_up(x) __wake_up((x),TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE, 1)
#define wake_up_interruptible(x) __wake_up((x),TASK_INTERRUPTIBLE, 1)
__wake_up函數主要是獲取隊列操做的鎖,具體工做是調用__wake_up_common完成的。
void __wake_up(wait_queue_head_t *q, unsigned int mode, int nr)
{
if (q) {
unsigned long flags;
wq_read_lock_irqsave(&q->lock, flags);
__wake_up_common(q, mode, nr, 0);
wq_read_unlock_irqrestore(&q->lock, flags);
}
}
參數q表示要操做的等待隊列,mode表示要喚醒任務的狀態,如TASK_UNINTERRUPTIBLE或TASK_INTERRUPTIBLE等。nr_exclusive是要喚醒的互斥進程數目,在這以前遇到的非互斥進程將被無條件喚醒。sync表示???
{
struct list_head *tmp;
struct task_struct *p;
CHECK_MAGIC_WQHEAD(q);
WQ_CHECK_LIST_HEAD(&q->task_list);
/* 遍歷等待隊列 */
list_for_each(tmp,&q->task_list) {
unsigned int state;
/* 得到當前等待隊列項 */
wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
CHECK_MAGIC(curr->__magic);
/* 得到對應的進程 */
p = curr->task;
state = p->state;
/* 若是咱們須要處理這種狀態的進程 */
if (state & mode) {
WQ_NOTE_WAKER(curr);
if (try_to_wake_up(p, sync) && (curr->flags&WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}
}
其餘的還有wake_up_nr, wake_up_all, wake_up_interruptible_nr, wake_up_interruptible_all, wake_up_interruptible_sync, wake_up_locked.
參考文章:
http://hi.baidu.com/abigbigman/blog/item/a0a1fb54f2faa85cd009065f.html
http://linux.chinaunix.net/techdoc/system/2008/03/08/982296.shtmllinux