在Linux內核中等待隊列有很多用途,可用於中斷處理、進程同步及定時。我們在這裡只說,進程經常必須等待某些事件的發生。等待隊列實現了在事件上的條件等待: 希望等待特定事件的進程把自己放進合適的等待隊列,並放棄控制全。因此,等待隊列表示一組睡眠的進程,當某一條件為真時,由內核喚醒它們。
等待隊列由循環鏈表實現,由等待隊列頭(wait_queue_head_t)和等待隊列項(wait_queue)組成,其元素(等待隊列項)包含指向進程描述符的指針。每個等待隊列都有一個等待隊列頭(wait queue head),等待隊列頭是一個類型為wait_queue_head_t的數據結構
定義等待隊列頭(相關內容可以在linux/include/wait.h中找到)
等待隊列頭結構體的定義:
struct wait_queue_head {
spinlock_t lock; //自旋鎖變量,用於在對等待隊列頭
struct list_head task_list; // 指向等待隊列的list_head
};
typedef struct __wait_queue_head wait_queue_head_t;
使用等待隊列時首先需要定義一個wait_queue_head,這可以通過DECLARE_WAIT_QUEUE_HEAD宏來完成,這是靜態定義的方法。該宏會定義一個wait_queue_head,並且初始化結構中的鎖以及等待隊列。
Linux中等待隊列的實現思想如下圖所示,當一個任務需要在某個wait_queue_head上睡眠時,將自己的進程控制塊信息封裝到wait_queue中,然後掛載到wait_queue的鏈表中,執行調度睡眠。當某些事件發生後,另一個任務(進程)會喚醒wait_queue_head上的某個或者所有任務,喚醒工作也就是將等待隊列中的任務設置為可調度的狀態,並且從隊列中刪除。
(2)等待隊列中存放的是在執行設備操作時不能獲得資源而掛起的進程
定義等待對列:
struct wait_queue {
unsigned int flags; //prepare_to_wait()裡有對flags的操作,查看以得出其含義
#define WQ_FLAG_EXCLUSIVE 0x01 //一個常數,在prepare_to_wait()用於修改flags的值
void * private //通常指向當前任務控制塊
wait_queue_func_t func; //喚醒阻塞任務的函數 ,決定了喚醒的方式
struct list_head task_list; // 阻塞任務鏈表
};
typedef struct __wait_queue wait_queue_t;
poll實現分析
select/poll的缺點在於:
1.每次調用時要重復地從用戶態讀入參數。
2.每次調用時要重復地掃描文件描述符。
3.每次在調用開始時,要把當前進程放入各個文件描述符的等待隊列。在調用結束後,又把進程從各個等待隊列中刪除。
(1) struct poll_table_entry {
struct file filp;
wait_queue_t wait;//內部有一個指針指向一個進程
wait_queue_head_t wait_address;//等待隊列頭部(等待隊列有多個wait_queue_t組成,通過雙鏈表連接)
};
(2) struct poll_table_page {
struct poll_table_page next;
struct poll_table_entry entry;
struct poll_table_entry entries[0];
};
(3) struct poll_wqueues {
poll_table pt;//一個函數指針,通常指向__pollwait或null
struct poll_table_page * table;
int error;
};
(4) struct poll_list {
struct poll_list *next;//按內存頁連接,因為kmalloc有申請數據限制
int len;//用戶空間傳入fd的數量
struct pollfd entries[0];//存放用戶空間存入的數據
};
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
typedef struct poll_table struct {
poll_queue_proc qproc;
} poll_table;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);