select系统调用

    
          就最近解决业务socket通信的问题时,发现业务端出现收包卡死的问题;经过业务调试打印,内核打点,查看套接字匹配情况,业务模型场景模拟等一系列操作,最后整理下select的流程和使用;
        在网络程序中,一个进程同时处理多个文件描述符是很常见的情况。select()系统调用可以使进程检测同时等待的多个I/O设备,当没有设备准备好时,select()阻塞,其中任一设备准备好时,select()就返回。
select()的调用形式为:
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readfds, fd_set *writefds, fe_set *exceptfds, const struct timeval *timeout);
         参数含义:
maxfd:使用时记住这个值为待检测的最大文件描述符加1即可;--------》但是有坑啊!
readfds:指定了被读监控的文件描述符集;Those listed in readfds will be watched to see if  characters  become  available  for  reading;
writefds:指定了被写监控的文件描述符集;those in writefds will be watched to see if a write will not block
exceptfds指定了被例外条件监控的文件描述符集;
timeout:很常用的一个timeeval结构,起了定时器的作用:到了指定的时间,无论是否有设备准备好,都返回调用。timeval的结构定义如下:
struct timeval{
        long tv_sec; //表示几秒
        long tv_usec; //表示几微妙
}    
       对以上的调用的函数各参数介绍后,说哈他的大致用法:简单粗暴,就是把需要读,写或者其他监控条件的fd加入相应的fd集用于select进行监控管理,并对这些fd进行轮循监控,当有相应的监控条件被触发后,select就会返回,接下来就是进行相应的操作;用法中说到加入,监控的字眼,于是就得说说select必须了解的几个宏:
void FD_SET(int fd, fd_set *fdset);//宏FD_SET设置文件描述符集fdset中对应于文件描述符fd的位(设置为1),即将fd加入fdset集的操作;
void FD_CLR(int fd, fd_set *fdset);//宏FD_CLR清除文件描述符集fdset中对应于文件描述符fd的位(设置为0),与上相反操作;
void FD_ISSET(int fd, fd_set *fdset);//在调用select后使用FD_ISSET来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置,如果置位,就进行相应操作;
void FD_ZERO(fd_set *fdset);//宏FD_ZERO清除文件描述符集fdset中的所有位(既把所有位都设置为0)。内核代码的实现如下:
 其中:
  typedef long fd_mask;
 #define NBBY    8
 #define NFDBITS (sizeof(fd_mask) * NBBY)

      以下博文有介绍上述宏的实现和理解,可以参考博文:https://www.cnblogs.com/lp1129/articles/2705556.html和https://www.cnblogs.com/scope-beyound/p/3628217.html
         好了,接下来说说使用的注意点:
      操作系统通过宏FD_SETSIZE来声明在一个进程中select所能操作的文件描述符的最大数目。通常是1024,既定义FD_SETSIZE为1024,一个整数占4个字节,既32位,那么就是用包含32个元素的整数数组来表示文件描述符集(这是内核代码实现的思路,对理解内核select底层实现很有帮助);
敲黑板!!!
      上面提到,监控的最大描述符的值为1024,主要依赖FD_SETSIZE宏的限制,通常的理解是加入到fdset中的fd数目总和是1024,正如上面说的由于宏定义和内核实现的结果所致;但是有个很难注意的地方就是select系统调用坑的地方,他的maxfd参数,这个参数上面说了,为待检测的最大文件描述符加1,很多资料都并没有明确说明他有最大值的限制,但是在实际中,根据自己复现和业务问题发现,他也是有FD_SETSIZE的限制!!!
接下来看看内核底层的实现,一探究竟:
基本的调用层次关系:sys_select() -------》core_sys_select() -----------》do_select() ---------》 fop->poll()
对于核心函数core_sys_select()主要实现逻辑关系如下:
1.获取当前进程的描述符表,并获得最大描述符值:

2.拷贝用户态的描述符集到内核fds结构体(其中fd.res_*,用于存放内核处理后的fd集,使用前先全置0)

3.调用do_select,内核完成fd集的监控处理;
4.将fd.res_*内核处理的结果返回(拷贝)到用户空间;

对于do_select的实现逻辑关系如下:
1.根据core_sys_select()拷贝到内核的fd集相关数据,获得最大的fd,赋值给n用于后续循环遍历控制

2.内核通过for循环遍历所有的fd,并进行相应的底层监控处理,最后返回给上级函数;

扫描二维码关注公众号,回复: 2746446 查看本文章

        
从上面的max_select_fd的实现来看,他是取加到fds集中最大的fd作为循环结束点,所以select调用中的maxfd也是有FD_SETSIZE限制的哈!
从代码实现,总结下select的特点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
基于以上不足,epoll就显得性能更好;
最后,从网上找到一个关于select应用的流程图:

此外,如何突破FD_SETSIZE的限制呢?可以修改宏定义,重新编译内核;其实内核之所以受限于这个宏,在于fd_set结构的定义:

        
这个宏限制了,fd_set结构体中可存放fd的个数(数组1024/8/8=16个元素,每个元素(long型,64位)每一个位代表一个fd);


参考博文:https://blog.csdn.net/shltsh/article/details/39349433
 https://blog.csdn.net/martin_liang/article/details/9124911
 

猜你喜欢

转载自blog.csdn.net/hzj_001/article/details/81542812
今日推荐