嵌入式Linux编程之select使用总结

2019-07-13 02:26发布

   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秒接收串口数据,我们可以在我们的程序中直接调用该函数。不会发生阻塞。