Linux裝置驅動編程之阻塞與非阻塞

來源:互聯網
上載者:User

阻塞操作是指,在執行裝置操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。非阻塞操作的進程在不能進行裝置操作時,並不掛起。被掛起的進程進入sleep狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。

  在Linux

  關於上述常式,我們補充說一點,如果將驅動程式中的read函數改為:

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
 //擷取訊號量:可能阻塞
 if (down_interruptible(&sem))
 {
  return - ERESTARTSYS;
 }

 //等待資料可獲得:可能阻塞
 if (wait_event_interruptible(outq, flag != 0))
 {
  return - ERESTARTSYS;
 }
 flag = 0;

 //臨界資源訪問
 if (copy_to_user(buf, &global_var, sizeof(int)))
 {
  up(&sem);
  return - EFAULT;
 }

 //釋放訊號量
 up(&sem);

 return sizeof(int);
}

  即交換wait_event_interruptible(outq, flag != 0)和down_interruptible(&sem)的順序,這個驅動程式將變得不可運行。實際上,當兩個可能要阻塞的事件同時出現時,即兩個wait_event或down擺在一起的時候,將變得非常危險,死結的可能性很大,這個時候我們要特別留意它們的出現順序。當然,我們應該儘可能地避免這種情況的發生!

  +還有一個與裝置阻塞與非阻塞訪問息息相關的論題,即select和poll,select和poll的本質一樣,前者在BSD Unix中引入,後者在System V中引入。poll和select用於查詢裝置的狀態,以便使用者程式獲知是否能對裝置進行非阻塞的訪問,它們都需要裝置驅動程式中的poll函數支援。

  驅動程式中poll函數中最主要用到的一個API是poll_wait,其原型如下:

void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);

  poll_wait函數所做的工作是把當前進程添加到wait參數指定的等待列表(poll_table)中。下面我們給globalvar的驅動添加一個poll函數:

static unsigned int globalvar_poll(struct file *filp, poll_table *wait)
{
 unsigned int mask = 0;

 poll_wait(filp, &outq, wait);

 //資料是否可獲得?
 if (flag != 0)
 {
  mask |= POLLIN | POLLRDNORM; //標示資料可獲得
 }
 return mask;
}

  需要說明的是,poll_wait函數並不阻塞,程式中poll_wait(filp, &outq, wait)這句話的意思並不是說一直等待outq訊號量可獲得,真正的阻塞動作是上層的select/poll函數中完成的。select/poll會在一個迴圈中對每個需要監聽的裝置調用它們自己的poll支援函數以使得當前進程被加入各個裝置的等待列表。若當前沒有任何被監聽的裝置就緒,則核心進行調度(調用schedule)讓出cpu進入阻塞狀態,schedule返回時將再次迴圈檢測是否有操作可以進行,如此反覆;否則,若有任意一個裝置就緒,select/poll都立即返回。

  我們編寫一個使用者態應用程式來測試改寫後的驅動。程式中要用到BSD Unix中引入的select函數,其原型為:

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

  其中readfds、writefds、exceptfds分別是被select()監視的讀、寫和異常處理的檔案描述符集合,numfds的值是需要檢查的號碼最高的檔案描述符加1。timeout參數是一個指向struct timeval類型的指標,它可以使select()在等待timeout時間後若沒有檔案描述符準備好則返回。struct timeval資料結構為:

struct timeval
{
  int tv_sec; /* seconds */
  int tv_usec; /* microseconds */
};

  除此之外,我們還將使用下列API:

  FD_ZERO(fd_set *set)――清除一個檔案描述符集;
  FD_SET(int fd,fd_set *set)――將一個檔案描述符加入檔案描述符集中;
  FD_CLR(int fd,fd_set *set)――將一個檔案描述符從檔案描述符集中清除;
  FD_ISSET(int fd,fd_set *set)――判斷檔案描述符是否被置位。

  下面的使用者態測試程式等待/dev/globalvar可讀,但是設定了5秒的等待逾時,若超過5秒仍然沒有資料可讀,則輸出"No data within 5 seconds":

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

main()
{
 int fd, num;
 fd_set rfds;
 struct timeval tv;

 fd = open("/dev/globalvar", O_RDWR, S_IRUSR | S_IWUSR);
 if (fd != - 1)
 {
  while (1)
  {
   //查看globalvar是否有輸入
   FD_ZERO(&rfds);
   FD_SET(fd, &rfds);
   //設定逾時時間為5s
   tv.tv_sec = 5;
   tv.tv_usec = 0;
   select(fd + 1, &rfds, NULL, NULL, &tv);

   //資料是否可獲得?
   if (FD_ISSET(fd, &rfds))
   {
    read(fd, &num, sizeof(int));
    printf("The globalvar is %d/n", num);

    //輸入為0,退出
    if (num == 0)
    {
     close(fd);
     break;
    }
   }
   else
    printf("No data within 5 seconds./n");
  }
 }
 else
 {
  printf("device open failure/n");
 }
}

  開兩個終端,分別運行程式:一個對globalvar進行寫,一個用上述程式對globalvar進行讀。當我們在寫終端給globalvar輸入一個值後,讀終端立即就能輸出該值,當我們連續5秒沒有輸入時,"No data within 5 seconds"在讀終端被輸出,如:

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.