是否存在在 fd_set 上使用结构复制(对于 select() 或 pselect())会导致问题?

2024-10-30 08:36:00
admin
原创
57
摘要:问题描述:select()和pselect()系统调用修改它们的参数(' fd_set *'参数),因此输入值告诉系统要检查哪些文件描述符,而返回值告诉程序员哪些文件描述符当前可用。如果要对同一组文件描述符重复调用它们,则需要确保每次调用都有描述符的新副本。显而易见的方法是使用结构副本:f...

问题描述:

select()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 (Darwin) 是安全的;因此,其他基于 BSD 的系统可能也是安全的。您可以通过在答案中记录您知道安全的其他系统来提供帮助。

(我确实有点担心它fd_set在打开超过 8192 个文件描述符的情况下能否正常工作 - 默认打开文件的最大数量只有 256 个,但最大数量是“无限制的”。此外,由于结构为 1 KB,复制代码的效率并不高,但随后在每个周期运行文件描述符列表以重新创建输入掩码也不一定高效。当select()您打开那么多文件描述符时,您可能无法做到这一点,尽管那时您最有可能需要该功能。)


有一个相关的 SO 问题 - 询问“poll() 与 select()”,它解决的问题是与该问题不同的一组问题。


请注意,在 MacOS X 上(可能更普遍的是 BSD 上),有一个FD_COPY()宏或函数,其有效原型如下:

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

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


解决方案 1:

由于struct fd_set这只是一个普通的 C 结构,所以这应该总是没问题的。我个人不喜欢通过=运算符进行结构复制,因为我曾在许多无法访问正常编译器内部函数集的平台上工作过。memcpy()在我看来,显式使用而不是让编译器插入函数调用是更好的方法。

来自 C 规范,第6.5.16.1 节简单赋值(为简洁起见,在此处编辑):

下列之一成立:

...

  • 左操作数具有与右操作数类型兼容的结构或联合类型的合格或不合格版本;

...

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

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

所以,只要struct fd_set实际上是常规 C struct,就一定能成功。但是,这确实取决于您的编译器发出某种代码来执行此操作,或者依赖于memcpy()它用于结构分配的任何内在函数。如果您的平台由于某种原因无法链接到编译器的内在库,则可能无法工作。

如果你打开的文件描述符多于 ,你就得耍些小花招了struct fd_set。Linux 手册页上说:

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

如下所述,可能不值得花费精力去证明您的代码在所有系统上都是安全的。 FD_COPY()就是为这种用途提供的,并且大概总是保证:

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

解决方案 2:

首先,没有struct fd_set。它只是被称为fd_set。但是,POSIX 确实要求它是结构类型,因此复制是明确定义的。

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

总之,仅分配/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_COPY宏(如果存在),并且只有当宏缺失时才会回退到理论上可能不安全的版本。

解决方案 3:

您说得对,POSIX 并不保证复制 afd_set一定“有效”。我个人不知道有哪个地方不保证,但我从未做过这个实验。

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

您还正确地推测select()(和poll()) 不能很好地扩展到大量文件描述符 - 这是因为每次函数返回时,您都必须循环遍历每个文件描述符以测试其上是否有活动。 解决方案是各种非标准接口(例如 Linux 的epoll()、FreeBSD 的kqueue),如果您发现存在延迟问题,您可能需要研究这些接口。

解决方案 4:

我对 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
", (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 参数可以调整大小

  • FD_COPY 是在未要求严格遵守 POSIX 或_DARWIN_C_SOURCE指定的情况下定义的

Solaris 10(SPARC)

  • 默认 FD_SETSIZE 在 32 位系统中为 1024,在 64 位系统中为 65536

  • FD_SETSIZE 参数可以调整大小

  • 没有 FD_COPY

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

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

找出如何确保它可用并不一定是一件简单的事;你最终需要进行手动扫描并找出如何触发它。

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

解决方案 5:

我没有足够的声望来将此作为评论添加到 caf 的答案中,但是有一些库可以通过非标准接口抽象出来,例如epoll()kqueue。libevent 是一个,libev 是另一个。我认为 GLib 也有一个与其主循环相关的库。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用