这几天在公司写了很多网络通信的代码,自然涉及到IO事件监听方法的问题。我很惊讶地发现选择轮换训练的方法在那里仍然很流行。我告诉他们不管在linux系统下还是windows系统下都要放弃select。原因是两个平台的select系统调用都有一个致命的陷阱。windows上单个fd_set容纳的socket句柄数不能超过FD_SETSIZE(在win32winsock2.h中定义为64个,以VS2010版本为准),fd_set结构使用数组来容纳这些socket句柄,每个FD_SET宏将一个套接字句柄放到这个数组中,在这个过程中,限制不能超过FD_SETSIZE。详情请查看winsock2.h中FD_SET宏的定义。这里的问题是,如果fd_set中的sockethandle个数已经达到FD_SETSIZE,那么后面的FD_SET操作实际上是没有效果的,sockethandle对应的IO事件会丢失!!!在linux系统下,问题其实出在fd_set的结构和FD_SET宏上。此时fd_set结构体使用一个位序列来记录每个要检测的IO事件的fd。记录方法略复杂,在/usr/include/sys/select.h中如下typedeflongint__fd_mask;#define__NFDBITS(8*sizeof(__fd_mask))#define__FDELT(d)((d)/__NFDBITS)#define__FDMASK(d)((__fd_mask)1<<((d)%__NFDBITS))typedefstruct{/*XPG4.2requiresthismembername.否则避免使用全局命名空间中的名称。*/#ifdef__USE_XOPEN__fd_maskfds_bits[__FD_SETSIZE/__NFDBITS];[__FD_SETSIZE/__NFDBITS];#define__FDS_BITS(set)((set)->__fds_bits)#endif}fd_set;#defineFD_SET(fd,fdsetp)__FD_SET(fd,fdsetp)/usr/include/bits/select.h中1#define__FD_SET(d,set)(__FDS_BITS(set)[__FDELT(d)]|=__FDMASK(d))可以看出,在上面的过程中,实际上fd_set的比特序列中的每一位的位置都对应着fd值的位置。fd_set结构中的位数由__FD_SETSIZE定义,__FD_SETSIZE在/usr/include/bits/typesize.h中(包含关系如下:sys/socket.h->bits/types.h->bits/typesizes.h)中定义为1024。现在的问题是当fd>=1024时,FD_SET宏实际上会导致内存写越界。其实在manselect中有明确的描述,如下:NOTESAnfd_setisafixedsizebuffer。使用负值或等于或大于FD_SETSIZE的fd值执行FD_CLR()或FD_SET()将导致未定义的行为。此外,POSIX要求fd是一个有效的文件描述符。这包括我之前,很多人都没有注意到,云峰大神有一篇博文《一起 select 引起的崩溃》也描述了这个问题。可见select在linux系统中并不安全。如果要使用,必须仔细确认fd是否达到1024,但这很难做到,否则还是老老实实用poll或者epoll吧。扯的有点远,不过也引出了本文的主题,就是linux系统下fd值是如何分配的。大家都知道fd是int类型,但是它的值是如何增长的。下面的内容我对此做了一点分析,以2.6.30版本的内核为例,欢迎拍砖。首先你要知道是哪个函数分配了fd。我将以管道为例。这是一个典型的fd分配系统调用。pipe和pipe2的syscall实现定义在fs/pipe.c中,如下SYSCALL_DEFINE2(pipe2,int__user*,fildes,int,flags){intfd[2];错误;错误=do_pipe_flags(fd,标志);if(!error){if(copy_to_user(fildes,fd,sizeof(fd))){sys_close(fd[0]);系统关闭(fd[1]);错误=-EFAULT;}}返回错误;}SYSCALL_DEFINE1(pipe,int__user*,fildes){返回sys_pipe2(fildes,0);get_unused_fd_flags(flags)分配fd,它是一个宏#defineget_unused_fd_flags(flags)alloc_fd(0,(flags)),位于include/linux/fs.h好了,我们找到主角了,它就是alloc_fd(),是kernelchapter实际进行fd分配的函数。其位于fs/file.c,实际也很简单,如下intalloc_fd(unsignedstart,unsignedflags){structfiles_struct*files=current->files;无符号整数;恐吓;structfdtable*fdt;自旋锁(&文件->文件锁);重复:fdt=files_fdtable(files);fd=开始;如果(fd<文件->next_fd)fd=文件->next_fd;如果(fd
