有没有在fd_set(select()或pselect())上使用结构拷贝导致问题的平台?

select()pselect()系统调用修改它们的参数(' fd_set * '参数),所以input值告诉系统要检查哪些文件描述符,返回值告诉程序员哪些文件描述符当前可用。

如果您打算为同一组文件描述符重复调用它们,则需要确保每次调用都有一个新的描述符副本。 显而易见的方法是使用结构副本:

 fd_set ref_set_rd; fd_set ref_set_wr; fd_set ref_set_er; ... ...code to set the reference fd_set_xx values... ... while (!done) { fd_set act_set_rd = ref_set_rd; fd_set act_set_wr = ref_set_wr; fd_set act_set_er = ref_set_er; int bits_set = select(max_fd, &act_set_rd, &act_set_wr, &act_set_er, &timeout); if (bits_set > 0) { ...process the output values of act_set_xx... } } 

编辑删除不正确的struct fd_set引用 – 由“R ..”指出 )。

我的问题:

  • 是否有任何平台不安全地执行fd_set值的结构副本(如图所示)?

我担心有没有隐藏的内存分配或任何意外的事情。 (有macros/函数FD_SET(),FD_CLR(),FD_ZERO()和FD_ISSET()来掩盖来自应用程序的内部。)

我可以看到,MacOS X(达尔文)是安全的; 因此,其他基于BSD的系统可能是安全的。 您可以通过logging您的答案中已知的其他系统来帮助您。

(我对fd_set和8192个以上的打开文件描述符的工作状态有一点关系 – 打开文件的缺省最大数目只有256个,但是最大数目是'unlimited'。另外,由于结构是1KB,复制代码不是非常高效,但是然后运行一个文件描述符列表在每个循环中重新创buildinput掩码也不一定有效。也许当打开多个文件描述符时,您不能执行select()那就是当你最需要这个function时。)


有一个相关的SO问题 – 询问有关“poll()vs select()”的问题,它解决了这个问题的一组不同的问题。


请注意,在MacOS X(大概是BSD)中,有一个FD_COPY()macros或函数,其中包含有效的原型:

  • extern void FD_COPY(const restrict fd_set *from, restrict fd_set *to);

在尚不可用的平台上进行仿真是值得的。

由于struct fd_set只是一个普通的C结构,应该总是很好。 我个人不喜欢通过=操作符进行结构复制,因为我曾经在很多平台上工作,这些平台不能访问普通的编译器内部函数。 在我的书中,显式使用memcpy()而不是让编译器插入一个函数调用是一个更好的方法。

从C规范,第6.5.16.1简单赋值 (为简洁起见,在这里编辑):

下列其中一项应为:

  • 左操作数具有与右键types兼容的结构或联合types的限定版本或非限定版本;

简单赋值 (=)中,右操作数的值将转换为赋值expression式的types,并replace左操作数指定的对象中存储的值。

如果存储在一个对象中的值是从另一个以任何方式重叠第一个对象的对象读取的,那么重叠应该是精确的,并且这两个对象应该具有兼容types的合格或不合格版本; 否则,行为是不确定的。

所以你去了,只要struct fd_set实际上是一个普通的C struct ,你保证成功。 但是,它依赖于编译器发出某种代码来执行它,或者依靠它用于结构分配的任何memcpy()内部函数。 如果您的平台出于某种原因无法与编译器的内部库链接,则可能无法正常工作。

如果你有更多的打开的文件描述符比适用于struct fd_set你将不得不玩一些技巧。 在Linux 手册页上说:

一个fd_set是一个固定大小的缓冲区。 执行FD_CLR()FD_SET()的值为负值或等于或大于FD_SETSIZE将导致未定义的行为。 而且,POSIX要求fd是一个有效的文件描述符。

如下所述,可能不值得努力certificate您的代码在所有系统上都是安全的。 FD_COPY()仅用于这样的用途,并且可以总是保证:

FD_COPY(&fdset_orig, &fdset_copy)用一个&fdset_copy副本replace一个已经分配的&fdset_copy &fdset_orig

首先,没有struct fd_set 。 这简直叫做fd_set 。 但是,POSIX确实要求它是一个结构types,所以复制是明确的。

其次,在标准C下, fd_set对象可以包含dynamic分配的内存,因为在返回之前不需要使用任何函数/macros来释放它。 即使编译器有alloca (基于堆栈分配的pre-vla扩展), fd_set也不能使用在堆栈上分配的内存,因为程序可能会将指向fd_set的指针传递给使用FD_SET等的另一个函数。分配的内存一旦返回给调用者就不再有效。 只有C编译器为析构函数提供了一些扩展才可以使用dynamic分配。

总而言之,分配/ memcpy fd_set对象似乎是安全的,但可以肯定的是,我会这样做:

 #ifndef FD_COPY #define FD_COPY(dest,src) memcpy((dest),(src),sizeof *(dest)) #endif 

或者只是:

 #ifndef FD_COPY #define FD_COPY(dest,src) (*(dest)=*(src)) #endif 

然后你会使用系统提供的FD_COPYmacros(如果它存在的话),只有在缺less理论上可能不安全的版本时才会回退。

你是正确的,POSIX不保证复制fd_set必须“工作”。 我没有亲自意识到它没有,但是我从来没有做过实验。

你可以使用poll()方法(也是POSIX)。 它的工作方式与select()非常相似,只是input/输出参数不是不透明的(并且不包含指针,所以一个简单的memcpy就可以工作),而且它的devise也完全不需要复制“请求的文件描述符”结构(因为“请求的事件”和“返回的事件”存储在不同的字段中)。

你也可以猜测select() (和poll() )不能很好地适应大量的文件描述符 – 这是因为每次函数返回时,都必须遍历每个文件描述符来testing是否存在活动。 对此的解决scheme是各种非标准接口(例如,Linux的epoll() ,FreeBSD的kqueue ),如果您发现延迟问题,您可能需要查看这些接口。

我没有足够的代表来添加这个作为评论咖啡的答案,但有库来抽象的非标准接口,如epoll()kqueue 。 libevent是一个,libev是另一个。 我认为GLib也有一个联系到它的主循环。

我在MacOS X,Linux,AIX,Solaris和HP-UX上做了一些研究,并且有一些有趣的结果。 我使用了以下程序:

 #if __STDC_VERSION__ >= 199901L #define _XOPEN_SOURCE 600 #else #define _XOPEN_SOURCE 500 #endif /* __STDC_VERSION__ */ #ifdef SET_FD_SETSIZE #define FD_SETSIZE SET_FD_SETSIZE #endif #ifdef USE_SYS_TIME_H #include <sys/time.h> #else #include <sys/select.h> #endif /* USE_SYS_TIME_H */ #include <stdio.h> int main(void) { printf("FD_SETSIZE = %d; sizeof(fd_set) = %d\n", (int)FD_SETSIZE, (int)sizeof(fd_set)); return 0; } 

它在每个平台上被编译了两次:

 cc -o select select.c cc -o select -DSET_FD_SETSIZE=16384 

(在一个平台上,HP-UX 11.11,我必须添加-DUSE_SYS_TIME_H才能编译所有东西。)我分别对FD_COPY进行了一次可视化检查 – 只有MacOS X似乎包含了它,而且必须由确保_POSIX_C_SOURCE未定义或通过定义_DARWIN_C_SOURCE

AIX 5.3

  • 默认的FD_SETSIZE是65536
  • FD_SETSIZE参数可以resize
  • 没有FD_COPY

HP-UX 11.11

  • 没有<sys/select.h>头文件 – 改用<sys/time.h>
  • 默认的FD_SETSIZE是2048
  • FD_SETSIZE参数可以resize
  • 没有FD_COPY

HP-UX 11.23

  • <sys/select.h>
  • 默认的FD_SETSIZE是2048
  • FD_SETSIZE参数可以resize
  • 没有FD_COPY

Linux(内核2.6.9,glibc 2.3.4)

  • 默认的FD_SETSIZE是1024
  • FD_SETSIZE参数不能resize
  • 没有FD_COPY

MacOS X 10.6.2

  • 默认的FD_SETSIZE是1024
  • FD_SETSIZE参数可以resize
  • 如果未请求严格的POSIX合规性或者指定了_DARWIN_C_SOURCE,则定义_DARWIN_C_SOURCE

Solaris 10(SPARC)

  • 默认FD_SETSIZE对于32位为1024,对于64位为65536
  • FD_SETSIZE参数可以resize
  • 没有FD_COPY

很显然,对程序的简单修改可以自动检查FD_COPY:

 #ifdef FD_COPY printf("FD_COPY is a macro\n"); #endif 

找出如何确保它是可用的,不一定是微不足道的; 你最终做手动扫描,并找出如何触发它。

在所有这些机器上,它看起来像一个fd_set可以通过结构副本复制,而不会冒未定义的行为的风险。