3 ms·
This might be a stupid question, but why couldn't select be extended to accept fd's >=1024? I don't see "not segfaulting" breaking existing behavior and it woul
by Sebb767 5y ago
This might be a stupid question, but why couldn't select be extended to accept fd's >=1024? I don't see "not segfaulting" breaking existing behavior and it would solve basically all issues with legacy programs. Yes, it might lead to select being used a bit longer than it otherwise would be, but that would probably be less of a pain than fighting segfaults caused by a `select()` somewhere in a linked library.
- wahern 5y agoselect(2) does support fds >= 1024. At least, it does on Linux and many other systems. The issue is that <sys/select.h> defines the type fd_set, a struct containing a fixed-size bit field as an array of integers. The maximum descriptor number (i.e. the bit field size) is reported by the constant FD_SETSIZE. Thus, conceptually fd_set is defined like typedef struct { int fds_bits[howmany(FD_SETSIZE, sizeof (int) * CHAR_BIT)] } fd_set; <sys/select.h> also provides a set of macros for initializing the structure (FD_ZERO), and testing (FD_ISSET) and setting (FD_SET, FD_CLR) descriptor numbers. But these macros have no way of reporting if a specified descriptor overflows the bit array. In all the implementations I'm familiar with, including Linux/glibc and Linux/musl, they don't even check, and will happily overwrite the array if given a descriptor greater than or equal to FD_SETSIZE. But the maximum descriptor number in the bit field is reported to the kernel as an argument to select. And most kernels, including Linux, obey this parameter[1], and thus impose no intrinsic limit. If userspace provides a validly allocated and initialized buffer all will work just fine. But how to allocate and use a properly sized buffer? Some systems, like OpenBSD, allow you to define FD_SETSIZE (or a similar constant) before including <sys/select.h>. On other systems, like modern Linux/glibc or Linux/musl, you have to get clever, like doing something like #define MYFD_SETSIZE 100000 #define MYFD_SETSIZE_OVERFLOW (MYFD_SETSIZE - FD_SETSIZE) union { fd_set fdset; char extra[sizeof (fd_set) + howmany(MYFD_SETSIZE_OVERFLOW, CHAR_BIT)]; } u; but then be careful not to rely on FD_ZERO, which won't know to zero out the high bits. [1] The same is true of (struct sockaddr_un).sun_path. On most systems you can pass Unix domain socket paths longer than 108 or whatever the local constant is, and the kernel will happily use the full path, though there may be another internal limit, like 256 or 1024. It can do this because syscalls like bind(2) and connect(2) take a separate addrlen parameter. One possibly shocking consequence to many people is that types like `struct sockaddr_storage` are not sufficient to represent every possible socket address. And unless you check the output of the addrlen pointer parameter to syscalls like accept(2), you may have received a truncated address without knowing it.
- toast0 5y agoYou certainly can, the kernel interface doesn't care how big the set is, glibc makes it difficult, but it's easy enough to do it on other OSes. The reason it's not generally done is because the select API has been effectively replaced with better things like kqueue and epoll and poll and ppoll. If you actually want to do something like select and you expect to have a lot of FDs, using something newer than select is a better choice. Edit: the other thing is everytime you call select, you've got to pass that N-bit set value, 1024 with the normal value of FD_SETSIZE. I've run on systems with 4M fds (limit set to 8M), that's a huge value to pass and iterate through for every select.
- cookguyruffles 5y ago> The reason it's not generally done is because the select API has been effectively replaced with better things like kqueue and epoll and poll and ppoll. If you actually want to do something like select and you expect to have a lot of FDs, using something newer than select is a better choice. The performance difference between select() and poll() isn't measurable for small sets of descriptors, and for epoll and friends, fixed setup costs are substantially higher. It's therefore not correct to suggest select() has been replaced with better interfaces, in the case of poll() it is a more complex interface and in the case of epoll (or io_uring or ..) it is a much more expensive interface.
- bonzini 5y agoIt's really debatable whether poll() is more complex than select(), for example select() requires you to rebuild from scratch the fdsets for every call.
- fluffycritter 5y agoMany years ago I wrote an article about this very issue (because I ran into it while debugging a service outage at 5 AM, which turned out to be something using select() when it shouldn't have): http://beesbuzz.biz/code/5739-The-problem-with-select-vs-poll http://beesbuzz.biz/code/5739-The-problem-with-select-vs-pol...