等待隊(duì)列是內(nèi)核中實(shí)現(xiàn)進(jìn)程調(diào)度的一個(gè)十分重要的數(shù)據(jù)結(jié)構(gòu),其任務(wù)是維護(hù)一個(gè)鏈表,鏈表中每一個(gè)節(jié)點(diǎn)都是一個(gè)PCB(進(jìn)程控制塊),內(nèi)核會(huì)將PCB掛在等待隊(duì)列中的所有進(jìn)程都調(diào)度為睡眠狀態(tài),直到某個(gè)喚醒的條件發(fā)生。應(yīng)用層的阻塞IO與非阻塞IO的使用我已經(jīng)在Linux I/O多路復(fù)用一文中討論過了,本文主要討論驅(qū)動(dòng)中怎么實(shí)現(xiàn)對(duì)設(shè)備IO的阻塞與非阻塞讀寫。顯然,實(shí)現(xiàn)這種與阻塞相關(guān)的機(jī)制要用到等待隊(duì)列機(jī)制。本文的內(nèi)核源碼使用的是3.14.0版本
設(shè)備阻塞IO的實(shí)現(xiàn)
當(dāng)我們讀寫設(shè)備文件的IO時(shí),最終會(huì)回調(diào)驅(qū)動(dòng)中相應(yīng)的接口,而這些接口也會(huì)出現(xiàn)在讀寫設(shè)備進(jìn)程的進(jìn)程(內(nèi)核)空間中,如果條件不滿足,接口函數(shù)使進(jìn)程進(jìn)入睡眠狀態(tài),即使讀寫設(shè)備的用戶進(jìn)程進(jìn)入了睡眠,也就是我們常說的發(fā)生了阻塞。In a word,讀寫設(shè)備文件阻塞的本質(zhì)是驅(qū)動(dòng)在驅(qū)動(dòng)中實(shí)現(xiàn)對(duì)設(shè)備文件的阻塞,其讀寫的流程可概括如下:
1. 定義-初始化等待隊(duì)列頭
//定義等待隊(duì)列頭wait_queue_head_t waitq_h;//初始化,等待隊(duì)列頭init_waitqueue_head(wait_queue_head_t *q); //或//定義并初始化等待隊(duì)列頭DECLARE_WAIT_QUEUE_HEAD(waitq_name);
上面的幾條選擇中,最后一種會(huì)直接定義并初始化一個(gè)等待頭,但是如果在模塊內(nèi)使用全局變量傳參,用著并不方便,具體用哪種看需求。
我們可以追一下源碼,看一下上面這幾行都干了什么:
//include/linux/wait.h 35 struct __wait_queue_head { 36 spinlock_t lock; 37 struct list_head task_list; 38 }; 39 typedef struct __wait_queue_head wait_queue_head_t;
wait_queue_head_t
--36-->這個(gè)隊(duì)列用的自旋鎖
--27-->將整個(gè)隊(duì)列"串"在一起的紐帶
然后我們看一下初始化的宏:
55 #define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \ 56 .lock = __SPIN_LOCK_UNLOCKED(name.lock), \ 57 .task_list = { &(name).task_list, &(name).task_list } } 58 59 #define DECLARE_WAIT_QUEUE_HEAD(name) \ 60 wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
DECLARE_WAIT_QUEUE_HEAD()
--60-->根據(jù)傳入的字符串name,創(chuàng)建一個(gè)名為name的等待隊(duì)列頭
--57-->初始化上述task_list域,竟然沒有用內(nèi)核標(biāo)準(zhǔn)的初始化宏,無語。。。
2. 將本進(jìn)程添加到等待隊(duì)列
為等待隊(duì)列添加事件,即進(jìn)程進(jìn)入睡眠狀態(tài)直到condition為真才返回。**_interruptible的版本版本表示睡眠可中斷,_timeout**版本表示超時(shí)版本,超時(shí)就會(huì)返回,這種命名規(guī)范在內(nèi)核API中隨處可見。
void wait_event(wait_queue_head_t *waitq_h,int condition);void wait_event_interruptible(wait_queue_head_t *waitq_h,int condition);void wait_event_timeout(wait_queue_head_t *waitq_h,int condition);void wait_event_interruptible_timeout(wait_queue_head_t *waitq_h,int condition);
這可是等待隊(duì)列的核心,我們來看一下
wait_event
?? └──?wait_event
?? ?? ?? ?? └──?_wait_event
?? ?? ?? ?? ├── abort_exclusive_wait
?? ?? ?? ?? ├── finish_wait
?? ?? ?? ?? ├── prepare_to_wait_event
?? ?? ?? ?? └── ___wait_is_interruptible
244 #define wait_event(wq, condition) \245 do { \246 if (condition) \247 break; \248 __wait_event(wq, condition); \ 249 } while (0)
wait_event
--246-->如果condition為真,立即返回
--248-->否則調(diào)用__wait_event
194 #define ___wait_event(wq, condition, state, exclusive, ret, cmd) \ 195 ({ \206 for (;;) { \207 long __int = prepare_to_wait_event(&wq, &__wait, state);\208 \ 209 if (condition) \ 210 break; \212 if (___wait_is_interruptible(state) && __int) { \213 __ret = __int; \214 if (exclusive) { \215 abort_exclusive_wait(&wq, &__wait, \216 state, NULL); \217 goto __out; \218 } \219 break; \220 } \222 cmd; \223 } \224 finish_wait(&wq, &__wait); \225 __out: __ret; \226 })
___wait_event
--206-->死循環(huán)的輪詢
--209-->如果條件為真,跳出循環(huán),執(zhí)行finish_wait();進(jìn)程被喚醒
--212-->如果進(jìn)程睡眠的方式是interruptible的,那么當(dāng)中斷來的時(shí)候也會(huì)abort_exclusive_wait被喚醒
--222-->如果上面兩條都不滿足,就會(huì)回調(diào)傳入的schedule(),即繼續(xù)睡眠
模板
struct wait_queue_head_t xj_waitq_h;static ssize_t demo_read(struct file *filp, char __user *buf, size_t size, loff_t *offset){ if(!condition) //條件可以在中斷處理函數(shù)中置位 wait_event_interruptible(&xj_waitq_h,condition);}static file_operations fops = { .read = demo_read,};static __init demo_init(void){ init_waitqueue_head(&xj_waitq_h);}
IO多路復(fù)用的實(shí)現(xiàn)
對(duì)于普通的非阻塞IO,我們只需要在驅(qū)動(dòng)中注冊(cè)的read/write接口時(shí)不使用阻塞機(jī)制即可,這里我要討論的是IO多路復(fù)用,即當(dāng)驅(qū)動(dòng)中的read/write并沒有實(shí)現(xiàn)阻塞機(jī)制的時(shí)候,我們?nèi)绾卫脙?nèi)核機(jī)制來在驅(qū)動(dòng)中實(shí)現(xiàn)對(duì)IO多路復(fù)用的支持。下面這個(gè)就是我們要用的API
int poll(struct file *filep, poll_table *wait);void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
當(dāng)應(yīng)用層調(diào)用select/poll/epoll機(jī)制的時(shí)候,內(nèi)核其實(shí)會(huì)遍歷回調(diào)相關(guān)文件的驅(qū)動(dòng)中的poll接口,通過每一個(gè)驅(qū)動(dòng)的poll接口的返回值,來判斷該文件IO是否有相應(yīng)的事件發(fā)生,我們知道,這三種IO多路復(fù)用的機(jī)制的核心區(qū)別在于內(nèi)核中管理監(jiān)視文件的方式,分別是位,數(shù)組,鏈表,但對(duì)于每一個(gè)驅(qū)動(dòng),回調(diào)的接口都是poll。
模板
struct wait_queue_head_t waitq_h;static unsigned int demo_poll(struct file *filp, struct poll_table_struct *pts){ unsigned int mask = 0; poll_wait(filp, &wwaitq_h, pts); if(counter){ mask = (POLLIN | POLLRDNORM); } return mask;}static struct file_operations fops = { .owner = THIS_MODULE, .poll = demo_poll,};static __init demo_init(void){ init_waitqueue_head(&xj_waitq_h);}
其他API
剛才我們討論了如何使用等待隊(duì)列實(shí)現(xiàn)阻塞IO,非阻塞IO,其實(shí)關(guān)于等待隊(duì)列,內(nèi)核還提供了很多其他API用以完成相關(guān)的操作,這里我們來認(rèn)識(shí)一下
//在等待隊(duì)列上睡眠sleep_on(wait_queue_head_t *wqueue_h);sleep_on_interruptible(wait_queue_head_t *wqueue_h);//喚醒等待的進(jìn)程void wake_up(wait_queue_t *wqueue);void wake_up_interruptible(wait_queue_t *wqueue);
評(píng)論
查看更多