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