select 的作用是为了解决阻塞I/O的问题,这样说可能 有些抽象,简单的讲,在linux下,很多的操作都是基于文件操作方式,不管操作的对象是普通文件,还是各种设备(串口等实际设备),操作的函数为write和read,这两个 函数都是可能会出现“阻塞”的,比如说串口吧,write的功能,其实 是将数据先写到发送缓冲,而read函数则是 读取缓冲,write的阻塞发生在,当前发送缓冲里有数据,而read的阻塞则发生在当前 读缓冲中没有数据,我们在实际使用的过程中,往往read阻塞居多,毕竟串口不是一直都有数据接收的。那么在这种情况下,read是会阻塞的,阻塞的意思就是CPU会干等在那,很显然,我们肯定是不会允许cpu干等的,那么这个时候,我们可能会有一下两种方式:
(1)将1个进程编程2个进程(fork),主进程控制流程,子进程则作为串口接收,这时是允许阻塞的,反正CPU会调度,不过我们要考虑下,这么点功能,就使用多进程,是否合适,毕竟进程的开销蛮大的,而且接收进程必然要与主进行要进行通信,在Linux下进程的通信还是不太容易的。
(2)创建新的线程,线程中可以阻塞,不过这也要分情况,有些线程也是不允许阻塞的,而且线程之间也必然需要进行同步通信,所以也无疑会增加复杂性。
基于上述的缺点和需求,有了一种解决技术,官方的名称是I/O多路转接,其实就是select机制,select的功能概括起来就是:
select允许设定固定时间的监听文件描述符状态等待,一旦文件描述符有状态,立即返回,如果超过等待时间,也返回。
而返回后,我们 就可以使用read和write对 相应的文件进行操作了。linux下select的函数原型如下:
#include
int select( int maxfdp1,
fd_set *restrict readfds,
fd_set *restrict writefds,
fd_set *restrict exceptfds,
struct timeval *restrict tvptr);
* 参数 maxfdp1 :最大文件描述符加1,也就是被监听的描述符中的最大值+1,之所以加1,这是因为文件描
* 述符是从0开始的。
* ex: 我们要监听的 文件描述符为 iFd1,iFd2.., 则 maxfdp1的值是这些文件描述符
* 最大值+1,也就是select会同时监听所有的这些文件描述,哪一个有变化,则返回
*
* 参数 readfds :“读集”监听,如果对其中一个描述符进行read操作不阻塞(可以被read),则认为此描
* 述符准备就绪,select会返回。
*
* 参数 writefds :“写集”监听,如果对其中一个描述符进行write操作不阻塞(可以被write),则认为此
* 描述符准备就绪,select会返回。
*
* 参数 execptfds :“异常集”监听,如果对其中一个描述符有异常,则select会返回
*
* 参数 tvptr :这是一个结构体,可以实现“秒 + 微秒”组合延时。
*
* 返回: 准备就绪的描述符数目,若超时,返回0,若出错,返回-1
*
关于上述参数的 监听集类型 fd_set,可以参考另一篇文章
Linux下 fd_set 结构小结,fd_set定义后,必须要进行初始化, 然后使用FD_SET进行设置,从select返回后,可以 使用FD_ISSET来进一步确认给定位是否仍处于打开状态。
使用select进行编程的框架如下:
{
fd_set rd;
struct timeval tv;
int iFd,err;
iFd = open(xxxxxx);
FD_ZERO( &rd );
FD_SET(iFd, &rd);
err = select(iFd+1, &rd, NULL, NULL, &tv);
if( err ){
if(FD_ISSET(fd, &rd)){
/////应用功能
}
}
}
我们在实际使用的项目案例中,常用select功能的有串口编程、socket等,可能有些人会觉得 socket编程,用fork居多,其实也是可以通过select 实现的,因为本质上都是要涉及到非阻塞接收机制的。
我们拿串口来举例说明,代码如下:
int usart_recv(int iFd, char *rcv_buf, int len)
{
int err;
int res;
fd_set fs_read;
struct timeval tv_timeout;
FD_ZERO(&fs_read); //清空集合
FD_SET(iFd,&fs_read); // 将一个给定的文件描述符加入集合之中
tv_timeout.tv_sec = 5;
tv_timeout.tv_usec = 0;
err = select(iFd + 1, &fs_read, NULL, NULL, &tv_timeout);
//如果select返回值大于0,说明文件描述符发生了变化,串口可以进行read操作
if( err ){
if( FD_ISSET(iFd, &fs_read) ){
res = read(iFd, rcv_buf, len);
return res;
}
}
else return 0;
}
上述的串口接收函数,就可以实现阻塞5秒接收串口数据,我们可以在我们的程序中直接调用该函数。不会发生阻塞。