从man select
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds 是三个集合中编号最大的文件描述符,再加 1。
nfds
当我们已经有了可以确定文件描述符的readfds
、writefds
和时,的目的是什么?exceptfds
答案1
在《UNIX环境下的高级编程》,W. Richard Stevens 说这是性能优化:
通过指定我们感兴趣的最高描述符,内核可以避免遍历三个描述符集中的数百个未使用的位,寻找打开的位。
(第一版,第 399 页)
如果您正在进行任何类型的 UNIX 系统编程,强烈推荐 APUE 这本书。
更新
Anfd_set
通常能够跟踪最多 1024 个文件描述符。
跟踪哪些fds
设置为0
以及哪些设置为的最有效方法1
是位集,因此每个fd_set
设置都由 1024 位组成。
在 32 位系统上,一个 long int(或“字”)是 32 位,因此这意味着每个字fd_set
是
1024 / 32 = 32 个字。
如果nfds
是一些很小的东西,比如 8 或 16,这在许多应用程序中都是如此,它只需要查看第一个单词,这显然比查看所有 32 个单词要快。
(请参阅FD_SETSIZE
和__NFDBITS
来自/usr/include/sys/select.h
您平台上的值。)
更新2
至于为什么函数签名不是
int select(fd_set *readfds, int nreadfds,
fd_set *writefds, int nwritefds,
fd_set *exceptfds, int nexceptfds,
struct timeval *timeout);
我的猜测是因为代码试图将所有参数保留在寄存器,因此 CPU 可以更快地处理它们,如果必须跟踪额外的 2 个变量,CPU 可能没有足够的寄存器。
换句话说,select
就是公开实现细节,以便速度更快。
- BSD 4.4 Lite 选择源代码(选择和选择扫描功能)
- Linux 2.6.37 选择源代码(do_select 和 max_select_fd 函数)
答案2
我不确定,因为我不是 select() 的设计者之一,但我想说这是一种性能优化。调用函数知道它在读、写和除 FD 中放入了多少个文件描述符,那么内核为什么要再次计算出来呢?
请记住,在 80 年代初,当 select() 被引入时,他们没有可以使用的多千兆赫兹、多处理器。 25 MHz VAX 的速度相当快。另外,您希望 select() 能够快速工作(如果可以的话):如果某些 I/O 正在等待该进程,为什么要让该进程等待?