fd_set以及select和poll的用法-驅動程序的阻塞與非阻塞--ZT

分析openmoko的源碼的時候遇到fd_set和select相關的問題。網上摘錄整理如下。以備以後查看。

 

fd_set以及select和poll的用法-驅動程序的阻塞與非阻塞

 

select()函數主要是建立在fd_set類型的基礎上的。
fd_set(它比較重要所以先介紹一下)是一組文件描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set類型通過下面四個宏來操作: 

    fd_set set;

    FD_ZERO(&set);       /* 將set清零使集合中不含任何fd*/
    FD_SET(fd, &set);    /* 將fd加入set集合 */
    FD_CLR(fd, &set);    /* 將fd從set集合中清除 */ WS
    FD_ISSET(fd, &set);  /* 測試fd是否在set集合中*/      

過去,一個fd_set通常只能包含<32的fd(文件描述字),因爲fd_set其實只用了一個32位矢量來表示fd;現在,UNIX系統通常會在頭文件<sys/select.h>中定義常量

FD_SETSIZE,它是數據類型fd_set的描述字數量,其值通常是1024,這樣就能表示<1024的fd。根據fd_set的位矢量實現,我們可以重新理解操作fd_set的四個宏

: 

fd_set set;

FD_ZERO(&set);      /*將set的所有位置0,如set在內存中佔8位則將set置爲 00000000*/
FD_SET(0, &set);    /* 將set的第0位置1,如set原來是00000000,則現在變爲10000000,這樣fd==1的文件描述字就被加進set中了 */
FD_CLR(4, &set);    /*將set的第4位置0,如set原來是10001000,則現在變爲10000000,這樣fd==4的文件描述字就被從set中清除了 */ 
FD_ISSET(5, &set);  /* 測試set的第5位是否爲1,如果set原來是10000100,則返回非零,表明fd==5的文件描述字在set中;否則返回0*/ 

―――――――――――――――――――――――――――――――――――――――
注意fd的最大值必須<FD_SETSIZE。
――――――――――――――――――――――――――――――――――――――― 

select函數的接口比較簡單:
    int select(int nfds, fd_set *readset, fd_set *writeset, fd_set* exceptset, struct timeval *timeout); 
功能:
測試指定的fd可讀?可寫?有異常條件待處理?     
參數:
nfds    
需要檢查的文件描述字個數(即檢查到fd_set的第幾位),數值應該比三組fd_set中所含的最大fd值更大,一般設爲三組fd_set中所含的最大fd值加1(如在

readset,writeset,exceptset中所含最大的fd爲5,則nfds=6,因爲fd是從0開始的)。設這個值是爲提高效率,使函數不必檢查fd_set的所有1024位。
readset   
     用來檢查可讀性的一組文件描述字。
writeset
     用來檢查可寫性的一組文件描述字。
exceptset
     用來檢查是否有異常條件出現的文件描述字。(注:錯誤不包括在異常條件之內)
timeout
有三種可能:
1.        timeout=NULL(阻塞:直到有一個fd位被置爲1函數才返回)
2.        timeout所指向的結構設爲非零時間(等待固定時間:有一個fd位被置爲1或者時間耗盡,函數均返回)
3.        timeout所指向的結構,時間設爲0(非阻塞:函數檢查完每個fd後立即返回) 

返回值:     
返回對應位仍然爲1的fd的總數。 
Remarks:
三組fd_set均將某些fd位置0,只有那些可讀,可寫以及有異常條件待處理的fd位仍然爲1。

使用select函數的過程一般是:
先調用宏FD_ZERO將指定的fd_set清零,然後調用宏FD_SET將需要測試的fd加入fd_set,接着調用函數select測試fd_set中的所有fd,最後用宏FD_ISSET檢查某個

fd在函數select調用後,相應位是否仍然爲1。 


以下是一個測試單個文件描述字可讀性的例子:
     int isready(int fd)
     {
         int rc;
         fd_set fds;
         struct timeval tv;    
         FD_ZERO(&fds);
         FD_SET(fd,&fds);
         tv.tv_sec = tv.tv_usec = 0;    
      rc = select(fd+1, &fds, NULL, NULL, &tv);
         if (rc < 0)   //error
           return -1;    
         return FD_ISSET(fd,&fds) ? 1 : 0;
     }


下面還有一個複雜一些的應用:
//這段代碼將指定測試Socket的描述字的可讀可寫性,因爲Socket使用的也是fd
uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)    
{
     fd_set rfds,wfds;
#ifdef _WIN32
     TIMEVAL tv;
#else
     struct timeval tv;
#endif   /* _WIN32 */ 
     FD_ZERO(&rfds);
     FD_ZERO(&wfds); 
     if (rd)     //TRUE
          FD_SET(*s,&rfds);   //添加要測試的描述字 
     if (wr)     //FALSE
          FD_SET(*s,&wfds); 
     tv.tv_sec=timems/1000;     //second
     tv.tv_usec=timems%1000;     //ms 
     for (;;) //如果errno==EINTR,反覆測試緩衝區的可讀性
          switch(select((*s)+1,&rfds,&wfds,NULL,  (timems==TIME_INFINITE?NULL:&tv)))  //測試在規定的時間內套接口接收緩衝區中是否有數據可讀
         {
     //0--超時,-1--出錯
        case 0:     /* time out */
              return 0; 
       
         case (-1):    /* socket error */
              if (SocketError()==EINTR)
                   break;              
              return 0; //有錯但不是EINTR 
       
          default:
              if (FD_ISSET(*s,&rfds)) //如果s是fds中的一員返回非0,否則返回0
                   return 1;
              if (FD_ISSET(*s,&wfds))
                   return 2;
              return 0;
         };
}

 

 

+++++++++++++++
原創:http://jiaxingkui123.blog.163.com/blog/static/8756468420091534254862/

select系統調用是用來讓我們的程序監視多個文件句柄(file descriptor)的狀態變化的。程序會停在select這裏等待,直到被監視的文件句柄有某一個或多個發生了狀態改

變。 文件在句柄在Linux裏很多,如果你man某個函數,在函數返回值部分說到成功後有一個文件句柄被創建的都是的,如man socket可以看到“On success, a file

descriptor for the new socket is returned.”而man 2 open可以看到“open() and creat() return the new file descriptor”,其實文件句柄就是一個整數,看

socket函數的聲明就明白了:
int socket(int domain, int type, int protocol);

當然,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、

stderr,0就是stdin,1就是stdout,2就是stderr。比如下面這兩段代碼都是從標準輸入讀入9個字節字符:


#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char ** argv)
{      
    char buf[10] = "";       
    read(0, buf, 9); /* 從標準輸入 0 讀入字符 */       
    fprintf(stdout, "%s/n", buf); /* 向標準輸出 stdout 寫字符 */      
    return 0;
}

/* **上面和下面的代碼都可以用來從標準輸入讀用戶輸入的9個字符** */
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char ** argv)
{       
char buf[10] = "";       
fread(buf, 9, 1, stdin); /* 從標準輸入 stdin 讀入字符 */       
write(1, buf, strlen(buf));       
return 0;
}
 
繼續上面說的select,就是用來監視某個或某些句柄的狀態變化的。

select函數原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函 數的最後一個參數timeout顯然是一個超時時間值,其類型是struct timeval *,即一個struct timeval結構的變量的指針,所以我們在程序裏要申明一個struct

timeval tv;然後把變量tv的地址&tv傳遞給select函數。struct timeval結構如下:
struct timeval
{            
long    tv_sec;         /* seconds */            
long    tv_usec;        /* microseconds */        
};
 
第2、 3、4三個參數是一樣的類型: fd_set *,即我們在程序裏要申明幾個fd_set類型的變量,比如rdfds, wtfds, exfds,然後把這個變量的地址&rdfds, &wtfds,

&exfds 傳遞給select函數。

      這三個參數都是一個句柄的集合,第一個rdfds是用來保存這樣的句柄的:當句柄的狀態變成可讀的時系統就會告訴select函數返回,同理第二個wtfds是指有句柄狀

態變成可寫的時系統就會告訴select函數返回,同理第三個參數exfds是特殊情況,即句柄上有特殊情況發生時系統會告訴select函數返回。特殊情況比如對方通過一個

socket句柄發來了緊急數據。如果我們程序裏只想檢測某個socket是否有數據可讀,我們可以這樣:


fd_set rdfds; /* 先申明一個 fd_set 集合來保存我們要檢測的 socket句柄 */

struct timeval tv; /* 申明一個時間變量來保存時間 */

int ret; /* 保存返回值 */

FD_ZERO(&rdfds); /* 用select函數之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要檢測的句柄socket加入到集合裏 */
 
tv.tv_sec = 1;
tv.tv_usec = 500; /* 設置select等待的最大時間爲1秒加500微秒 */

ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 檢測我們上面設置到集合rdfds裏的句柄是否有可讀信息 */

if(ret < 0)
    perror("select");/* 這說明select函數出錯 */
else if(ret == 0)
   printf("超時/n"); /* 說明在我們設定的時間值1秒加500毫秒的時間內,socket的狀態沒有發生變化 */
else
{ /* 說明等待時間還未到1秒加500毫秒,socket的狀態發生了變化 */   
    printf("ret=%d/n", ret); /* ret這個返回值記錄了發生狀態變化的句柄的數目,由於我們只監視了socket這一個句柄,所以這裏一定ret=1,如果同時有多個句柄發

生變化返回的就是句柄的總和了 */
    /* 這裏我們就應該從socket這個句柄裏讀取數據了,因爲select函數已經告訴我們這個句柄裏有數據可讀 */  

   if(FD_ISSET(socket, &rdfds)) { /* 先判斷一下socket這外被監視的句柄是否真的變成可讀的了 */       
   /* 讀取socket句柄裏的數據 */       
    recv(...);   
   }
}
 


注意select函數的第一個參數,是所有加入集合的句柄值的最大那個值還要加1。比如我們創建了3個句柄:

int sa, sb, sc;

sa = socket(...); /* 分別創建3個句柄並連接到服務器上 */

connect(sa,...);

sb = socket(...);

connect(sb,...);

sc = socket(...);

connect(sc,...);

FD_SET(sa, &rdfds);/* 分別把3個句柄加入讀監視集合裏去 */

FD_SET(sb, &rdfds);

FD_SET(sc, &rdfds);
 


在使用select函數之前,一定要找到3個句柄中的最大值是哪個,我們一般定義一個變量來保存最大值,取得最大socket值如下:


int maxfd = 0;

if(sa > maxfd)

     maxfd = sa;

if(sb > maxfd)

    maxfd = sb;

if(sc > maxfd)

    maxfd = sc;
 


然後調用select函數:

ret = select(maxfd + 1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */


同樣的道理,如果我們要檢測用戶是否按了鍵盤進行輸入,我們就應該把標準輸入0這個句柄放到select裏來檢測,如下:

FD_ZERO(&rdfds);

FD_SET(0, &rdfds);

tv.tv_sec = 1;

tv.tv_usec = 0;

ret = select(1, &rdfds, NULL, NULL, &tv); /* 注意是最大值還要加1 */

if(ret < 0)

     perror("select");/* 出錯 */

else if(ret == 0)

     printf("超時/n"); /* 在我們設定的時間tv內,用戶沒有按鍵盤 */

else { /* 用戶有按鍵盤,要讀取用戶的輸入 */  

scanf("%s", buf); }


+++++++++++++++++++++++++++++++++++++++++++++

Linux設備驅動編程之阻塞與非阻塞

 
時間:2006-10-23 11:04:02  來源:天極軟件  作者:宋寶華

阻塞操作是指,在執行設備操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。非阻塞操作的進程在不能進行設備操作時,並不掛起。被掛起的進程

進入sleep狀態,被從調度器的運行隊列移走,直到等待的條件被滿足。

  關於上述例程,我們補充說一點,如果將驅動程序中的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"在讀終端被輸出.

 

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