是否存在在 fd_set 上使用结构复制(对于 select() 或 pselect())会导致问题?
- 2024-10-30 08:36:00
- admin 原创
- 57
问题描述:
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 也有一个与其主循环相关的库。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件