只选择检查文件描述符(fds)255以下,而不是FD_SETSIZE。

6

选择高于255的文件描述符不会检查该文件描述符是否已打开。这是我的示例代码:

#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");
        }
    }
}

这将导致:
--> i is 5
select: Bad file descriptor
...
--> i is 255
select: Bad file descriptor
--> i is 256

然后应用程序阻塞了。 为什么这不会在256到FD_SETSIZE之间创建EBADF

来自评论的请求信息:

prlimit的结果为:

NOFILE     max number of open files                1024   1048576

这是运行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

评论中的疑惑解答:

#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");
    }
}

结果是:

$ ./test_select
--> fd is 523, FD_SETSIZE is 1024

进程正常退出,不会阻塞。


1
FD_SETSIZE 是 1023。否则我的 for 循环将无法达到 256。 - kuga
你正在关闭放入循环中的每个文件描述符,这将创建EBADF。此外,您正在使用未初始化的内容进行选择,这非常容易生成EBADF - Antti Haapala -- Слава Україні
1
我知道这个。为什么在256上不会创建EBADF?此外,set在每次循环迭代中都会初始化。 - kuga
2
尽管这很奇怪,也不应该发生,但我确实可以在我的电脑上重现这种效果(它实际上会在 fd 64 处卡住)。 - zwol
1
很可能是libc实现将fd_sets限制为256位。更高的数字会被忽略,因此设置fd=256的调用就像一个空集合一样处理。 - joop
显示剩余16条评论
1个回答

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;
}

我对POSIX的理解是,无论FD_SETSIZE是什么,这个程序都不应该产生任何输出并成功退出。在FreeBSD 11.1和NetBSD 7.1上(都运行在某些x86处理器上),它确实做到了这一点。但是在Linux(x86-64,内核版本为4.13)上,它会打印出如下信息:

fd 256: select timed out

并且未能成功退出。更奇怪的是,如果我在strace下运行同一个二进制文件,输出会发生改变

$ strace -o /dev/null ./a.out
fd 64: select timed out

如果我在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]

由于进程受到ptrace监控,因此某些内容正在发生变化。这只能是由内核引起的。

我已在Linux内核上提交了错误报告,并将汇报他们对此的看法。


1
这就是罪魁祸首:https://github.com/torvalds/linux/blob/master/fs/select.c#L620 - Petr Skocik
@PSkocik 我对内核的细节了解不够,无法理解为什么它是罪魁祸首 - 你是说 fdt->max_fds 在某些情况下可能会低于 FD_SETSIZE(而且显然也低于当前的 RLIMIT_NOFILE 设置)吗?我应该在哪里查找控制该值的代码? - zwol
3
在 "select(2)" 的手册页的注释中包含了这条声明: "此外,POSIX 要求 fd 必须是一个有效的文件描述符。"由于你这里的 fd 不是有效的,这似乎会导致一个 bug。我知道这与 EBADF 错误返回有些冲突。(我相信 @PSkocik 是正确的。问题出现在 fd_set 中设置了位数 超过 任务当前的 max_fds 设置 - 这些位数不可能对应有效的文件描述符,但它们可以被设置。理想情况下,内核应该检查任何在 max_fds 之外设置的位数并返回 EBADF。) - Gil Hamilton
4
max_fds 至少为 NR_OPEN_DEFAULT(等于 BITS_PER_LONG),通常为 64。但是,在 fork(dup_fds)期间会进行调整。我怀疑 shell 在保持高编号的打开文件描述符时进行了 fork(然后在 exec 之前关闭)。这就导致了 256。而 gdb(或 ptrace)进行 fork 时没有这样的高编号打开文件描述符,因此最小尺寸为 64。 - Gil Hamilton
1
“Ad argumentum”,在宏描述下确实有这样的内容:“如果fd参数小于0或大于等于FD_SETSIZE,或者fd不是有效的文件描述符,则这些宏的行为是未定义的...”你当然可以认为这不适用于select/pselect的行为,但另一方面,生成fd_set的唯一指定方法是使用这些宏。因此,可以通过推论认为这是未定义的行为。(话虽如此,我个人认为这是一个错误。;) 它应该按照您的预期工作。) - Gil Hamilton
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接