【Linux 驅動】第六章 進階字元驅動程式操作 —-阻塞型I/O

來源:互聯網
上載者:User

 

     序言:試想如果在驅動方法中的read/write中,當資料不可用時,使用者可能調用read,當輸出緩衝區滿時,裝置並未準備好接受資料,這種情況下驅動程式可以阻塞該進程,並且置入休眠狀態直到滿足條件。


一, 休眠

   
          當一個進程休眠時,它會被標記為一種特殊狀態並從調度器的運行隊列中移走,直到某些情況修改了這個狀態,進程才會在任意cpu上調度,即運行該進程。
   在linux下,為了讓進程安全的進入休眠狀態,有兩條規則需要牢記:
 

         1)永遠不要在原子上下文中休眠。

               說明:原子上下文:在執行多個步驟時,不能有任何並發訪問。不能再擁有自旋鎖,seqlock,rcu鎖時休眠,進程1在擁有訊號量時休眠是合法的,但是要確保進程1擁有訊號量不會阻塞喚醒我們的那個進程2。

 
         2)對喚醒之後的狀態不能做任何假定,必須檢查以確保我們等待的條件為真。

       

      初始化一個等待隊列有兩種方法:

 
         1)靜態
                DECLARE_WAIT_QUEUE_HEAD(my_queue);
         2)動態
               wait_queue_head_t    my_queue;

               init_waitqueue_head(&my_queue);


 一個等待隊列由一個wait_queue_head_t 結構體來管理,其定義在<linux/wait.h>中

struct __wait_queue_head {    spinlock_t lock;    struct list_head task_list;};typedef struct __wait_queue_head wait_queue_head_t;

   
二, 簡單休眠
   
    等待隊列:一個進程鏈表,包含了等待某個特定事件的所有進程。
    在linux中,一個等待隊列通過一個"wait queue head"來管理,類型為 wait_queue_head_t,在<linux/wait.h>中定義。
   
 
   休眠宏:  wait_event(queue,condition);  //queue是等待隊列頭  condition是boolea類型
                      wait_event_interruptible(queue,condition); //可以中斷休眠
                      wait_event_timeout(queue,condition,timeout);//等待限定的時間
                      wait_event_interruptible_timeout(queue,condition,timeout);

     上述宏的調用如果condition條件不滿足將引起調用進程的阻塞,並且宏會在休眠前後對condition求值,從名字可以看出帶interruptible的宏使用者可以中斷休眠,返回非0值表示被中斷,此時驅動程式要返回      -ERESTARTSYS.而帶timeout的宏表示在給定的時間到期時,宏都會返回0.

   喚醒函數:

                   void   wake_up(wait_queue_head_t   *queue);
                   void   wake_up_interruptible(wait_queue_head_t    *queue);

wake_up會喚醒在queue上的所有進程,而wake_up_interruptible只喚醒那些可中斷的進程。

三,進階休眠

  進程如何休眠?

       step1:分配初始化一個wait_queue_t 結構,然後加入到對應的等待隊列。
 

       step2:通過函數set_current_state(int new_state)設定進程狀態。

                      驅動程式關心的進程狀態主要是TASK_RUNNING(可運行),TASK_INTERRUPTIBLE(可中斷休眠),TASK_UNINTERRPUTIBLE(不可中斷休眠)。


      step3:調用schedule(),記住在調用之前,必須先檢查進入休眠的條件。如果不做檢查會引入競態: 如果在忙於上面的這個過程時有其他的線程剛剛試圖喚醒你,你可能錯過

  喚醒且長時間休眠,經典的檢查代碼如下:


             if(!condition)

                        schedule();

四,手工休眠  <linux/sched.h>中包含必須的定義

        (1)建立和初始化一個等待隊列。常由宏定義完成:
                           DEFINE_WAIT(my_wait);   // name 是等待隊列入口項的名字.
                    

                    方法二:

                     wait_queue_t    my_wait;
                     init_wait(&my_wait);
       

                 常用的做法是放一個 DEFINE_WAIT 在迴圈的頂部,來實現休眠。

 (2)添加等待隊列入口到隊列,並設定進程狀態:
              void    prepare_to_wait(wait_queue_head_t     *queue, wait_queue_t    *wait, int    state); 
                   queue   等待隊列頭

                    wait   進程入口

                    state   進程的新狀態:TASK_INTERRUPTIBLE(可中斷休眠,推薦)或TASK_UNINTERRUPTIBLE(不可中斷休眠,不推薦)。

(3)在檢查確認仍然需要休眠之後調用 schedule
                      schedule();

(4)schedule 返回,就到了清理時間:
 

       void      finish_wait(wait_queue_head_t     *queue, wait_queue_t    *wait); 
  

      認真地看簡單休眠中的 wait_event(queue, condition) 和 wait_event_interruptible(queue, condition) 底層源碼會發現,其實他們只是手工休眠中的函數的組合。所以怕麻煩的話還是用wait_event比較好。





五, 獨佔等待

            當一個進程調用 wake_up 在等待隊列上,所有的在這個隊列上等待的進程被置為可啟動並執行。 這在許多情況下是正確的做法。但有時,可能只有一個被喚醒的進程將成功獲得需要的資源,而其餘的將再次休眠。這時如果等待隊列中的進程數目大,這可能嚴重降低系統效能。為此,核心開發人員增加了一個“獨佔等待”選項

   獨佔等待進程與普通休眠進程的不同:

          1)等待隊列入口設定了WQ_FLAG_EXCLUSIVE標誌,並且添加到等待隊列的尾部,沒有這個標誌的進程被添加到等待隊列的頭部。

           2)在某個等待隊列上調用wake_up時,它會喚醒第一個具有WQ_FLAG_EXCLUSIVE標誌的進程之後再喚醒其他進程。

   什麼時候採用獨佔等待進程呢?滿足以下兩個條件可以考慮:

          1)對某個資源存在嚴重競爭。

          2)喚醒單個進程就能完整消耗該資源

六,喚醒的相關函數
       很少會需要調用wake_up_interruptible 之外的喚醒函數,但為完整起見,這裡是整個集合:
              wake_up(wait_queue_head_t *queue); //喚醒隊列中的每個非獨佔等待進程和一個獨佔等待進程
              wake_up_interruptible(wait_queue_head_t *queue); //過處於不可中斷休眠的進程。它們在返回之前, 使一個或多個進程被喚醒、被調度(如果它們被從一個原子上下文調用, 這就不會發生)

              wake_up_nr(wait_queue_head_t *queue, int nr); 
              wake_up_interruptible_nr(wait_queue_head_t  *queue, int nr); 

              這些函數類似 wake_up, 除了它們能夠喚醒多達 nr 個獨佔等待者, 而不只是一個. 注意傳遞 0 被解釋為請求所有的互斥等待者都被喚醒 

             wake_up_all(wait_queue_head_t *queue); 
             wake_up_interruptible_all(wait_queue_head_t  *queue);      

            這種 wake_up 喚醒所有的進程, 不管它們是否進行獨佔等待(可中斷的類型仍然跳過在做不可中斷等待的進程)

             wake_up_interruptible_sync(wait_queue_head_t *queue); 
             一個被喚醒的進程可能搶佔當前進程, 並且在 wake_up 返回之前被調度到處理器。 但是, 如果你需要不要被調度出處理器時,可以使用            wake_up_interruptible 的"同步"變體. 這個函數最常用在調用者首先要完成剩下的少量工作,且不希望被調度出處理器時。

七,阻塞和非阻塞操作

 設定非阻塞I/O

   顯式的非阻塞I/O由filp->f_flags中的O_NONBLOCK標誌決定,為了保持和System V代碼相容,標誌O_NDELAY和O_NONBLOCK是一個意思,非阻塞io的read和write會返回-EAGAIN.

   應用程式有兩種方式制定非阻塞IO: 

   1)在open的時候指定,用於在open調用可能會阻塞很長的時間。

   2)調用fcntl函數。具體使用方法可在網上查哦這裡就不列出了。

阻塞操作的標準語義

    如果一個進程調用了read但是沒有資料可讀,進程阻塞,資料到達時進程被喚醒,並把資料返回給調用者,即使資料少於count;如果一個進程調用了write但是緩衝區滿,該進程必須阻塞,休眠在與read進程不同的等待隊列上,當緩衝區有空閑時,進程被喚醒,寫入資料成功,即使寫入了小於count的位元組數。

一個阻塞IO的例子scullp

static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos){        struct scull_pipe *dev = filp->private_data;        if (down_interruptible(&dev->sem))                return -ERESTARTSYS;        while (dev->rp == dev->wp) { /* nothing to read */                up(&dev->sem); /* release the lock */                if (filp->f_flags & O_NONBLOCK)                        return -EAGAIN;                PDEBUG("\"%s\" reading: going to sleep\n", current->comm);                if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))                        return -ERESTARTSYS; /* signal: tell the fs layer to handle it */                if (down_interruptible(&dev->sem))                        return -ERESTARTSYS;        }        /* ok, data is there, return something */        if (dev->wp > dev->rp)                count = min(count, (size_t)(dev->wp - dev->rp));        else /* the write pointer has wrapped, return data up to dev->end */                count = min(count, (size_t)(dev->end - dev->rp));        if (copy_to_user(buf, dev->rp, count)) {                up (&dev->sem);                return -EFAULT;        }        dev->rp += count;        if (dev->rp == dev->end)                dev->rp = dev->buffer; /* wrapped */        up (&dev->sem);        /* finally, awake any writers and return */       wake_up_interruptible(&dev->outq);        PDEBUG("\"%s\" did read %li bytes\n",current->comm, (long)count);        return count;}

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.