Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1319729
  • 博文数量: 482
  • 博客积分: 13297
  • 博客等级: 上将
  • 技术积分: 2890
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-12 16:25
文章分类

全部博文(482)

文章存档

2012年(9)

2011年(407)

2010年(66)

分类: LINUX

2011-05-06 19:19:12

分析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系统 通常会在头文件中定义常量

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的最大值必须――――――――――――――――――――――――――――――――――――――― 

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
#include
#include

int main(int argc, char ** argv)
{       
    char buf[10] = "";        
    read(0, buf, 9); /* 从标准输入 0 读入字符 */        
    fprintf(stdout, "%s\n", buf); /* 向标准输出 stdout 写字符 */       
    return 0;
}

/* **上面和下面的代码都可以用来从标准输入读用户输入的9个字符** */
#include
#include
#include

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  
#include
#include
#include
#include
#include
#include

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"在读终端被输出.

====

http://www.cnblogs.com/zhuxiongfeng/archive/2010/07/05/1771495.html

阅读(749) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~