select仅检查fds到255,直到FD_SETSIZE

kug*_*uga 6 c linux select

选择fds高于255然后不检查fd是否打开.这是我的示例代码:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>

int main()
{
    fd_set set;
    for(int i = 5;i<FD_SETSIZE;i++)
    {
        printf("--> i is %d\n", i);
        FD_ZERO(&set);
        FD_SET(i, &set);
        close(i);

        int retval = select(FD_SETSIZE, &set, NULL, NULL, NULL);
        if(-1 == retval)
        {
            perror("select");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这导致:

--> i is 5
select: Bad file descriptor
...
--> i is 255
select: Bad file descriptor
--> i is 256
Run Code Online (Sandbox Code Playgroud)

然后应用程序阻止.为什么EBADF在FD_SETSIZE之前这不会创建256?

来自评论的请求信息:

结果prlimit是:

NOFILE     max number of open files                1024   1048576
Run Code Online (Sandbox Code Playgroud)

这是以下结果strace ./test_select:

select(1024, [127], NULL, NULL, NULL)   = -1 EBADF (Bad file descriptor)
dup(2)                                  = 3
fcntl(3, F_GETFL)                       = 0x8402 (flags O_RDWR|O_APPEND|O_LARGEFILE)
fstat(3, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 2), ...}) = 0
write(3, "select: Bad file descriptor\n", 28select: Bad file descriptor
) = 28
close(3)                                = 0
write(1, "--> i is 128\n", 13--> i is 128
)          = 13
close(128)                              = -1 EBADF (Bad file descriptor)
select(1024, [128], NULL, NULL, NULL
Run Code Online (Sandbox Code Playgroud)

揭穿评论中的想法:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>

int main()
{
    char filename[80];
    int fd;
    for(int i = 5;i<500;i++)
    {
        snprintf(filename, 80, "/tmp/file%d", i);
        fd = open(filename, O_RDWR | O_APPEND | O_CREAT);
    }
    printf("--> fd is %d, FD_SETSIZE is %d\n", fd, FD_SETSIZE);
    fd_set set;
    FD_ZERO(&set);
    FD_SET(fd, &set);
    int retval = select(FD_SETSIZE, NULL, &set, NULL, NULL);
    if(-1 == retval)
    {
        perror("select");
    }
}
Run Code Online (Sandbox Code Playgroud)

结果是:

$ ./test_select
--> fd is 523, FD_SETSIZE is 1024
Run Code Online (Sandbox Code Playgroud)

进程正常退出,无阻塞.

zwo*_*wol 4

这里发生了一些非常奇怪的事情。您可以在 Linux 内核中发现了一个错误。

我修改了你的测试程序,使其更加精确,并且在遇到问题时不会卡住:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <sys/time.h>

int main(void)
{
    fd_set set;
    struct timeval tv;
    int i;

    for(i = 5; i < FD_SETSIZE; i++)
    {
        FD_ZERO(&set);
        FD_SET(i, &set);

        tv.tv_sec = 0;
        tv.tv_usec = 1000;

        close(i);
        int retval = select(FD_SETSIZE, &set, 0, 0, &tv);
        if (retval == -1 && errno == EBADF)
          ;
        else
        {
            if (retval > 0)
                printf("fd %d: select returned success (%d)\n", i, retval);
            else if (retval == 0)
                printf("fd %d: select timed out\n", i);
            else
                printf("fd %d: select failed (%d; %s)\n", i, retval, strerror(errno));
            return 1;
        }
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

我对 POSIX 的理解是,无论如何FD_SETSIZE,这个程序应该不产生任何输出并成功退出。这就是它在 FreeBSD 11.1 和 NetBSD 7.1 上所做的事情(两者都运行在某种描述的 x86 处理器上)。但在 Linux(x86-64,内核 4.13)上,它打印

fd 256: select timed out
Run Code Online (Sandbox Code Playgroud)

并且退出失败。更奇怪的是,如果我在 下运行相同的二进制文件strace就会改变输出

$ strace -o /dev/null ./a.out
fd 64: select timed out
Run Code Online (Sandbox Code Playgroud)

如果我在 下运行它,即使我除了运行程序之外gdb不告诉gdb做任何事情,也会发生同样的事情。

Reading symbols from ./a.out...done.
(gdb) r
Starting program: /tmp/a.out 
fd 64: select timed out
[Inferior 1 (process 8209) exited with code 01]
Run Code Online (Sandbox Code Playgroud)

因此,某些事情正在发生变化,只是因为该过程受到ptrace监控。这只能是内核造成的。

我已经提交了一份关于 Linux 内核的错误报告,并将报告他们对此的看法。

  • `max_fds` 至少为 `NR_OPEN_DEFAULT` (== `BITS_PER_LONG`),通常为 64。但是,它会在分叉期间进行调整 (`dup_fds`)。我怀疑 shell 分叉时持有一个高编号的打开文件描述符(然后在“exec”之前关闭)。结果是 256。而“gdb”(或“ptrace”)分叉时没有如此大编号的打开文件描述符,因此您具有最小 (64) 大小。 (4认同)
  • 嗯...`select(2)`手册页在NOTES中包含这样的语句:“此外,POSIX要求`fd`是一个有效的文件描述符。” 由于这里的“fd”无效,这似乎是一个错误。我意识到这与 EBADF 错误返回有些冲突。(我相信@PSkocik是对的。问题的出现是因为“fd_set”中设置的位*超出了任务当前的“max_fds”设置——这些位不可能对应于有效的文件描述符,但它们*可以*设置。理想情况下,内核会检查超出“max_fds”的任何设置位并返回 EBADF。) (3认同)