0

From man select :

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

nfds should be set to the highest-numbered file descriptor in any of the three sets, plus 1.

I'm quite curious about :

  1. Why plus 1 is needed, not highest-numbered file descriptor itself?
  2. Why request plus 1 operation in user input, instead of handling it inside the system?

From sys_generic.c, it seems related __NFDBITS, but I'm not able to go further.

static int max_select_fd(unsigned long n, fd_set_bits *fds)
 339{
 340        unsigned long *open_fds;
 341        unsigned long set;
 342        int max;
 343        struct fdtable *fdt;
 344
 345        /* handle last in-complete long-word first */
 346        set = ~(~0UL << (n & (__NFDBITS-1)));
 347        n /= __NFDBITS;
 348        fdt = files_fdtable(current->files);
 349        open_fds = fdt->open_fds->fds_bits+n;
 350        max = 0;
 351        if (set) {
 352                set &= BITS(fds, n);
 353                if (set) {
 354                        if (!(set & ~*open_fds))
 355                                goto get_max;
 356                        return -EBADF;
 357                }
 358        }
 359        while (n) {
 360                open_fds--;
 361                n--;
 362                set = BITS(fds, n);

Similar topic but not the same:

What's the purpose of the first argument to select system call?

whisper
  • 11
  • 2
  • Non-answer, since I'm just speculating, but a lot of the system call interfaces is also about backward compatibility, which trumps making minor changes. (Also the customs used in history have not always been the best, witness the number of `something2()` system calls in Linux that have been created just to add a flags argument to some system call that didn't have it earlier.) All it takes is for the first implementer to have implemented it the way your code snippet shows, as `while(n--)` and that's what sticks. – ilkkachu May 05 '22 at 07:44

1 Answers1

2

The nfds argument to select is specified as follows:

The nfds argument specifies the range of descriptors to be tested. The first nfds descriptors shall be checked in each set; that is, the descriptors from zero through nfds-1 in the descriptor sets shall be examined.

In other words, nfds gives a count of file descriptors, it isn’t a file descriptor itself. Its smallest sensical value is 1, not 0 (technically, nfds can be 0; select will do nothing in that case, and might as well not be called at all, unless you’re interested in one of its side-effects such as waiting for the timeout).

The nfds value specifies how many file descriptors the caller wants to watch; since file descriptors start at 0, if nfds is n, file descriptors from 0 to n–1 will be considered.

Stephen Kitt
  • 411,918
  • 54
  • 1,065
  • 1,164
  • Actually, I have a C code that has served me well for about 30 years, which selects with zero descriptors. On several Unixes (we supported AIX, HP-UX, Dec Alpha, and SunOS 1) it was the only portable and reliable mechanism I could find to get a brief timer function, using a `struct timeval`, which was also interruptible by a signal. – Paul_Pedant May 05 '22 at 14:22
  • @Paul_Pedant that’s fantastic, for some value of fantastic! – Stephen Kitt May 05 '22 at 14:55