select是在指定时间内(最后一个参数指定的时间)轮询指定fd集合的接口
1. 需要包含的头文件
#include <sys/time.h> //select是在指定时间内轮询,所以有时间相关的参数
#include <sys/types.h>
#include <unistd.h>
2. 函数原型以及参数说明
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//参数1: int nfds: 最大的文件描述符 + 1
(1)由此可见,select轮询的是int的文件描述符,而非FILE*的
//参数2: 待检测 是否有数据可读取的 fd集合
fd_set实际上是long类型数组,每个数组元素都与一打开的文件句柄建立联系,相关的API如下
(1) FD_ZERO(fd_set*) //将fd_set清空不含任何描述符
(2)FD_SET(fd fd_set*) //将fd(int)加入 fd_set集合
(3)FD_CLR(fd, fd_set*) //将fd(int)从fd_set中删除
(4)FD_ISSET(fd,fd_set*)//select结束后调用,用于判断fd是否在set集合中,
fd在集合中则返回真,不在集合中返回假
//参数3:待检测 是否有数据可写的 fd集合 【目前我所见过的大部分填NULL】
//参数4: 待检测 是否有数据可执行的 fd集合【目前我所见过的大部分填NULL】
//参数5:timeval: 检测的时间
3. 函数返回值
>0: 就绪描述符的数量
=-1: 出错
0: 超时(无就绪的描述符)
使用注意:
1. 第二到四个参数分别是待检测可读fd集合, 待检测可写fd集合,待检测可执行fd集合,我们可以只填写部分集合,如果三个集合都不填写的话,就得到了一个比sleep更精准的定时器(timeval中精确到微秒,sleep精确到秒)
2. 第二到四个参数是类似的,只是监视文件的动作不同,以第二个readset为例,会在time参数指定的时间内监视该fd_set集合中是否有可读的文件,如果有,则返回可读的描述符的个数>0,如果在time指定的时间内没有可读的描述符,则返回0. 只有在select发生错误的时候才返回-1
3. timeval的三种使用方法:
1)timeval = NULL: 表示时间无限长,一直到描述符集合中某个描述符变化为止。如果描述符集合中没有发生变化的描述符,则一直阻塞。
2) timeval = 0: 只轮询描述符集合一次,如果没有发生变化的,返回0; 如果有,则返回变化的描述符的数量。
3) timeval > 0: 在该时间内阻塞轮询描述符集合,如果没有发生变化的,则返回0,如果有,则返回发生变化的描述符的数量,
void main()
{
int sock; int fd;
fd_set fds;
struct timeval timeout={0,3}; //select等待3微秒,3微秒轮询,要非阻塞就置0
while(1)
{
FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
FD_SET(sock,&fds); //添加描述符 sock
FD_SET(fd,&fds); //添加描述符fd
timeout.tv_sec=0;
timeout.tv_usec=3; //select函数会不断修改timeout的值,所以每次循环都应该重新赋值
maxfdp=sock>fd?sock+1:fd+1; //描述符最大值加1
switch(select(maxfdp,&fds,&fds,&fds,&timeout))
{
case -1:
exit (-1);
break; //select错误,直接exit退出
case 0:
break; //0代表超时,没有发生变化的fd
default:
if(FD_ISSET(sock,&fds)) //测试sock是否可读
{
if(FD_ISSET(fd,&fds)) //测试文件是否可写
}
}
}
}
使用注意:
1. select 多数情况下是连续调用的,要么是select被包在一个循环里面,要么是软件逻辑保证不停的select 。
2. select内部会对后面的四个参数进行修改,所以每一次select之前后面的四个参数需要重新设置。
3. 第二三四个参数(描述符集合)可以是重复的。
4. select轮询后,会把没有发生变化的描述符从集合中删除,集合中剩余的描述符即发生变化的描述符。所以只需要select后用FD_ISSET检测某个描述符是否还在集合中,即可判断该描述符是否发生了变化。
扩展:select与 驱动层的关系:
应用层的selcet调用后,会调用到驱动层的 file_operations->poll 接口,在这个函数里应该调用poll_wait(),将current加到某个等待队列(这里调用poll_wait()),并检查是否有效,如果无效就调用schedule_timeout();去睡眠。事件发生后,schedule_timeout()回来,调用fop->poll(),检查到可以运行,就调用poll_freewait(&table);从而完成select系统调用。重要的是fop->poll()里面要检查是否就绪,如果是,要返回相应标志。