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

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

你可能感兴趣的:(Linux)