Linux設備驅動中的阻塞和非阻塞IO

這篇文章我們來了解下Linux設備驅動中阻塞和非阻塞。

阻塞:阻塞是指執行設備操作時,如果不能獲得設備資源,則掛起進程,是進程進入休眠模式,直到設備資源可以獲取。

非阻塞:非阻塞是在不能獲取設備資源時,要麼放棄獲取,要麼一直不停的查詢,直到可以獲取資源。

這兩種操作能夠爲爲應用程序提供這樣的能力:

(1)當應用程序對設備資源進行read(), write()操作時,如果設備資源不能獲取,用戶以阻塞的方式進行訪問,則驅動程序在設備驅動的xxx_read(), xxx_write()等操作中將進程阻塞直到資源可以獲取,之後應用程序的read(), write()的調用才能返回。

(2)如果應用程序進行上述操作時,用戶以非阻塞的方式進行訪問時,設備資源不能獲取時,設備驅動的xxx_read(),xxx_write()等操作立即返回,應用程序的read(),write()等系統調用立即返回,應用程序受到-EAGAIN 返回值。

下面這幅圖是Linux開發詳解中的圖,可以加深理解。

 使用例子:

fd = open("/dev/ttyS1", O_RDWR);  阻塞的形式;

fd = open("/dev/ttyS1", O_RDWR| O_NONBLOCK);  非阻塞的形式;

那麼在Linux驅動程序中,是如何實現阻塞進程的喚醒呢?下面有幾種方法使用:

(1)等待隊列 ( wait queue),等待隊列是以隊列爲基礎數據結構實現的。

           1)使用方法:

首先定義等待隊列的頭部: wait_queue_head_t  my_queue;

初始化等待隊列的頭部:init_waitqueue_head(&my_queue); DECLARE_WAIT_QUEUE_HEAD() 宏是定義並初始化等待隊列頭部的快捷方式。

定義等待隊列元素:DECLARE_WAITQUEUE(name, tsk) ,該宏用於定義並初始化一個名爲name的等待隊列元素。

添加/移除等待隊列:void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
                                 void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

add_wait_queue()用於將等待隊列元素wait添加到等待隊列頭部q指向的雙向鏈表中。remove 函數執行移除操作。

等待事件:

     wait_event(queue, condition); 等待第一個參數queue作爲等待隊列頭部的隊列被喚醒,而且第二個參數condition必須滿足,否則繼續阻塞。
     wait_event_interruptible(queue, condition); 可被信號中斷
     wait_event_timeout(queue, condition, timeout)   加了等待超時時間,超時時間到不論condition是否滿足均返回。
    wait_event_interruptible_timeout(queue, condition, timeout)

 喚醒隊列:void wake_up(wait_queue_head_t *queue);
                   void wake_up_interruptible(wait_queue_head_t *queue);

喚醒以queue作爲等待隊列頭部的隊列中所有的進程。

在等待隊列上睡眠:

         sleep_on(wait_queue_head_t *q );  將目前進程的狀態置成TASK_UNINTERRUPTIBLE,並定義一個等待隊列元素,之後把它掛到等待隊列頭部q指向的雙向鏈表,直到資源可獲得,q隊列指向鏈接的進程被喚醒。
         interruptible_sleep_on(wait_queue_head_t *q );目前進程的狀態置成TASK_INTERRUPTIBLE,並定義一個等待隊列
元素,之後把它附屬到q指向的隊列,直到資源可獲得(q指引的等待隊列被喚醒)或者進程收到信號。

有了前面的基本概念知識後,我們通過這段書上的代碼來理解:

static ssize_t xxx_write(struct file *file, const char *buffer, size_t count,
 loff_t *ppos)
{

     DECLARE_WAITQUEUE(wait, current); /* 定義等待隊列元素 */
     add_wait_queue(&xxx_wait, &wait); /* 添加元素到等待隊列 */

 /* 等待設備緩衝區可寫 */
     do {
         avail = device_writable(...);
         if (avail < 0) {
             if (file->f_flags &O_NONBLOCK) { /* 非阻塞 */
                 ret = -EAGAIN;
                 goto out;
             }
         __set_current_state(TASK_INTERRUPTIBLE); /* 改變進程狀態 */
         schedule(); /* 調度其他進程執行 */
         if (signal_pending(current)) { /* 如果是因爲信號喚醒 */
             ret = -ERESTARTSYS;
             goto out;
         }
    } while (avail < 0);

/* 寫設備緩衝區 */
     device_write(...);
     out:
     remove_wait_queue(&xxx_wait, &wait); /* 將元素移出 xxx_wait 指引的隊列 */
     set_current_state(TASK_RUNNING); /* 設置進程狀態爲 TASK_RUNNING */
     return ret;
}

1)如果是非阻塞訪問(O_NONBLOCK被設置),設備忙時,直接返回“-EAGAIN”。
2)對於阻塞訪問,會調用__set_current_state(TASK_INTERRUPTIBLE)進行進程狀態切換並顯示通過“schedule()”調度其他進程執行。
3)醒來的時候要注意,由於調度出去的時候,進程狀態是TASK_INTERRUPTIBLE,即淺度睡眠,所以喚醒它的有可能是信號,因此,我們首先通過signal_pending(current)瞭解是不是信號喚醒的,如果是,立即返回“-ERESTARTSYS”。
非阻塞的實現有: select() 和poll() 。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章