非阻塞IO
在應(yīng)用程序中,使用open函數(shù)打開一個(gè)/dev
目錄下的一個(gè)設(shè)備文件時(shí),默認(rèn)是以阻塞的方式打開。
所謂阻塞,就是當(dāng)我們請(qǐng)求的資源不可用時(shí)(資源被占用,沒(méi)有數(shù)據(jù)到達(dá)等等),會(huì)使得進(jìn)程休眠,從現(xiàn)象看就是卡在那里。
應(yīng)用層
如果我們希望以非阻塞方式打開設(shè)備文件,則應(yīng)該在open設(shè)備文件時(shí),添加一個(gè)O_NONBLOCK
的flag參數(shù),例如:
fd = open("/dev/vser0", O_RDWR | O_NONBLOCK);
驅(qū)動(dòng)層
應(yīng)用層以非阻塞方式打開設(shè)備文件,則驅(qū)動(dòng)層也要有對(duì)應(yīng)的處理操作才行。
應(yīng)用層傳入的O_NONBLOCK
標(biāo)志,會(huì)保存在struct file
結(jié)構(gòu)體的f_flags
成員中。當(dāng)資源不可用時(shí),同時(shí)判斷f_flags變量是否為O_NONBLOCK
,有則代表以非阻塞方式打開,然后返回一個(gè)-EAGAIN
錯(cuò)誤,提示應(yīng)用層資源暫時(shí)不可用。
下面是驅(qū)動(dòng)中read
函數(shù)非阻塞處理的偽代碼,例如:
static ssize_t vser_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
......
if (資源不可用)
if (filp- >f_flags & O_NONBLOCK)
return -EAGAIN;
......
}
當(dāng)出現(xiàn)資源不可用時(shí),會(huì)出現(xiàn)以下提示:
read: Resource temporarily unavailable
阻塞IO
上述非阻塞方式打開設(shè)備文件,雖然可以防止進(jìn)程休眠,無(wú)論結(jié)果如何都會(huì)立即返回,但 缺點(diǎn)是必須要定期查詢資源是否可以獲得 ,例如上述代碼中,每次調(diào)用read函數(shù)都要去查詢一下資源是否可用。這種操作效率非常低。
但是用阻塞IO的話,進(jìn)程休眠期間就再也不能做其他的事情了 。
所以不論是非阻塞IO還是阻塞IO,都有缺點(diǎn),有沒(méi)有好的辦法呢?
正確做法應(yīng)該是: 使用阻塞IO,驅(qū)動(dòng)中添加喚醒操作 。
什么意思呢? 既然有休眠,就應(yīng)該有對(duì)應(yīng)的喚醒操作,否則進(jìn)程將會(huì)一直休眠下去 。驅(qū)動(dòng)程序應(yīng)該在資源可用時(shí)負(fù)責(zé)執(zhí)行喚醒操作。
要實(shí)現(xiàn)既有休眠,又有喚醒的阻塞IO模型,應(yīng)該使用 等待隊(duì)列 。
等待隊(duì)列
我們以一個(gè)虛擬串口設(shè)備為例:
如圖是一個(gè)虛擬串口設(shè)備示例圖,這是一個(gè)功能弱化之后的只具備內(nèi)回環(huán)作用的串口。
主要功能 :在驅(qū)動(dòng)中實(shí)現(xiàn)一個(gè)FIFO
,驅(qū)動(dòng)接收用戶層傳來(lái)的數(shù)據(jù),然后將之放入FIFO
,當(dāng)應(yīng)用層要獲取數(shù)據(jù)時(shí),驅(qū)動(dòng)將FIFO
中的數(shù)據(jù)讀出,然后復(fù)制給應(yīng)用層。
我們以這個(gè)虛擬串口設(shè)備為例,講解等待隊(duì)列的使用。
為了方便理解,簡(jiǎn)化了不必要的代碼,下面是驅(qū)動(dòng)代碼:
//定義內(nèi)核fifo
DEFINE_KFIFO(vsfifo, char ,32)
//定義兩個(gè)等待隊(duì)列頭
wait_queue_head_t rwqh;//讀等待隊(duì)列
wait_queue_head_t wwqh;//寫等待隊(duì)列
static ssize_t vser_read(struct file *flip, char __user *buf, size_t count, loff_t *pos)
{
......
/* fifo為空,沒(méi)有數(shù)據(jù)可讀,進(jìn)入休眠*/
if(kfifo_is_empty(&vsfifo)) {
if(flip- >f_flags & O_NONBLOCK)//非阻塞方式,直接返回
return -EAGAIN;
/* 阻塞方式,沒(méi)有數(shù)據(jù)可讀,將進(jìn)程放入讀等待隊(duì)列rwqh,進(jìn)程休眠;喚醒條件是fifo不為空 */
if (wait_event_interruptible(rwqh, !kfifo_is_empty(vsfifo)))
return -ERESTARTSYS;
}
//將fifo中的數(shù)據(jù)返回給應(yīng)用層
ret = kfifo_to_user(&vsfifo, buf, count, &copied);
/* fifo未滿,還有空間,代表可以往fifo寫數(shù)據(jù),喚醒寫等待隊(duì)列 */
if (!kfifo_is_full(&vsfifo))
wake_up_interruptible(&wwqh);
......
}
static ssize_t vser_write(struct file *flip, const char __user *buf, size_t count, loff_t *pos)
{
......
/* fifo已滿,不可寫 */
if (kfifo_is_full(&vsfifo)) {
if (flip- >f_flags & O_NONBLOCK)//非阻塞方式,直接返回
return -EAGAIN;
/* 阻塞方式,進(jìn)程休眠,放入寫等待隊(duì)列,喚醒條件是fifo未滿時(shí) */
if (wati_event_interruptible(wwqh, !kfifo_is_full(&vsfifo)))
return -ERESTARTSYS;
}
//從應(yīng)用層獲取數(shù)據(jù),寫入fifo
ret = kfifo_from_user(&vsfifo, buf, count, &copied);
/* fifo不為空,喚醒讀等待隊(duì)列rwqh */
if (!kfifo_is_empty(&vsfifo))
wake_up_interruptible(&rwqh);
......
}
/* 驅(qū)動(dòng)入口函數(shù) */
static int __init vser_init(void)
{
......
/* 初始化等待隊(duì)列頭 */
init_waitqueue_head(rwqh);
init_waitqueue_head(wwqh);
......
}