是否有任何平台在fd_set上使用结构副本(对于select()或pselect())会导致问题?

时间:2010-03-11 00:13:38

标签: c linux unix posix

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

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

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值的结构副本不安全?

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

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

(我对fd_set与超过8192个打开文件描述符的效果有一点关注 - 默认的最大打开文件数仅为256,但最大数量为“无限制”。此外,由于结构是1 KB,复制代码不是非常有效,但是然后运行文件描述符列表以在每个循环上重新创建输入掩码也不一定有效。也许你不能做select()时你打开了那么多文件描述符,不过那时候你最有可能需要这些功能。)


有一个相关的SO问题 - 询问'poll() vs select()'从这个问题中解决了一系列不同的问题。


请注意,在MacOS X上 - 可能更常见的是BSD - 有一个FD_COPY()宏或函数,带有效原型:

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

可能值得在尚未提供的平台上进行模拟。

5 个答案:

答案 0 :(得分:9)

由于struct fd_set只是一个常规的C结构,所以应该总是很好。我个人不喜欢通过=运算符进行结构复制,因为我已经在很多平台上工作,这些平台无法访问正常的编译器内在函数集。在我的书中,明确地使用memcpy()而不是让编译器插入函数调用是一种更好的方法。

从C规范, 6.5.16.1简单作业部分(为简洁起见,此处编辑):

  

以下其中一项应成立:

     

...

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

...

     

简单赋值(=)中,右操作数的值将转换为赋值表达式的类型,并替换存储在左操作数指定的对象中的值。

     

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

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

如果你有更多的开放文件描述符而不是struct fd_set,你将不得不玩一些技巧。 linux man page说:

  

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

如下所述,可能不值得努力证明您的代码在所有系统上都是安全的。 FD_COPY()仅用于此类用途,并且可能始终保证:

  

FD_COPY(&fdset_orig, &fdset_copy)&fdset_copy的副本替换已分配的&fdset_orig文件描述符集。

答案 1 :(得分:7)

答案 2 :(得分:3)

你是正确的,POSIX不保证复制fd_set必须“工作”。我个人并不知道它没有,但我从未做过实验。

您可以使用poll()替代方案(也是POSIX)。它以与select()非常相似的方式工作,除了输入/输出参数不是不透明的(并且不包含指针,因此裸memcpy将起作用),其设计也完全消除了需要制作“请求的文件描述符”结构的副本(因为“请求的事件”和“返回的事件”存储在不同的字段中)。

你猜测select()(和poll())不能很好地扩展到大量的文件描述符也是正确的 - 这是因为每次函数返回时,你必须遍历每一个文件描述符,用于测试是否有活动。对此的解决方案是各种非标准接口(例如,Linux的epoll(),FreeBSD的kqueue),如果您发现存在延迟问题,可能需要查看这些接口。

答案 3 :(得分:2)

我对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参数的大小
  • 没有FD_COPY

HP-UX 11.11

  • 没有<sys/select.h>标题 - 请改为使用<sys/time.h>
  • 默认FD_SETSIZE为2048
  • 可以调整FD_SETSIZE参数的大小
  • 没有FD_COPY

HP-UX 11.23

  • <sys/select.h>
  • 默认FD_SETSIZE为2048
  • 可以调整FD_SETSIZE参数的大小
  • 没有FD_COPY

Linux(内核2.6.9,glibc 2.3.4)

  • 默认FD_SETSIZE为1024
  • FD_SETSIZE参数无法调整大小
  • 没有FD_COPY

MacOS X 10.6.2

  • 默认FD_SETSIZE为1024
  • 可以调整FD_SETSIZE参数的大小
  • 如果未请求严格的POSIX合规性或指定_DARWIN_C_SOURCE,则定义FD_COPY

Solaris 10(SPARC)

  • 默认FD_SETSIZE对于32位为1024,对于64位
  • 为65536
  • 可以调整FD_SETSIZE参数的大小
  • 没有FD_COPY

显然,对程序的一个微不足道的修改允许自动检查FD_COPY:

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

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

在所有这些机器上,看起来fd_set可以被结构副本复制而不会遇到未定义行为的风险。

答案 4 :(得分:1)

我没有足够的代表将此添加为对caf的答案的评论,但是有些库可以通过epoll()kqueue之类的非标准接口进行抽象。 libevent是一个,而libev是另一个。我认为GLib也有一个与其主循环相关的。