select 是多路复用,或异步模型中经常用到的一个系统调用。
基本原型为:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
该函数的一个缺点就是nfds不能太大。上限为1024.为什么会有这样的限制,今天就来一起看看select的具体实现。
再/usr/include/sys/select.h中可以看到select使用的基本数据的定义:
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;
/* Some versions of define these macros. */
#undef __NFDBITS
#undef __FDELT
#undef __FDMASK
/* It's easier to assume 8-bit bytes than to get CHAR_BIT. */
#define __NFDBITS (8 * sizeof (__fd_mask))
#define __FDELT(d) ((d) / __NFDBITS)
#define __FDMASK(d) ((__fd_mask) 1 << ((d) % __NFDBITS))
/* fd_set for select and pselect. */
typedef struct
{
/* XPG4.2 requires this member name. Otherwise avoid the name
from the global namespace. */
#ifdef __USE_XOPEN
__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
} fd_set;
/* Maximum number of file descriptors in `fd_set'. */
#define FD_SETSIZE __FD_SETSIZE
#ifdef __USE_MISC
/* Sometimes the fd_set member is assumed to have this type. */
typedef __fd_mask fd_mask;
/* Number of bits per word of `fd_set' (some code assumes this is 32). */
# define NFDBITS __NFDBITS
#endif
/* Access macros for `fd_set'. */
#define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)
#define FD_CLR(fd, fdsetp) __FD_CLR (fd, fdsetp)
#define FD_ISSET(fd, fdsetp) __FD_ISSET (fd, fdsetp)
#define FD_ZERO(fdsetp) __FD_ZERO (fdsetp)
通过这段代码我们不难发现,select中关键数据结果fd_set.他的大小就决定了系统能承受的fd数量.
fe_set被定义为一个结构体,其唯一的一个成员变量是一个long int的数组。
剩下问题就计算数组大小了。__FD_SETSIZE / __NFDBITS.
查看include/linux/posix_types.h,如下:
#undef __NFDBITS
#define __NFDBITS (8 * sizeof(unsigned long))
#undef __FD_SETSIZE
#define __FD_SETSIZE 1024
#undef __FDSET_LONGS
#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS)
#undef __FDELT
#define __FDELT(d) ((d) / __NFDBITS)
#undef __FDMASK
#define __FDMASK(d) (1UL << ((d) % __NFDBITS))
typedef struct {
unsigned long fds_bits [__FDSET_LONGS];
} __kernel_fd_set;
系统自己也定义了一个__kernel_fd_set数据结构。这里我们不关心这个。
这里可以看到__FD_SETSIZE 为 1024.于是之前的数组大小就可以计算出来是32.
总共可以使用的bit是32*32=1024.也就是说,select的最大fd是1023.(select调用的时候要加1)
现在再看下与fd_set相关的几个宏又是如何实现的.
在arch/alpha/include/asm/posix_types.h给出了简单的c函数实现方式:
#ifdef __KERNEL__
#ifndef __GNUC__
#define __FD_SET(d, set) ((set)->fds_bits[__FDELT(d)] |= __FDMASK(d))
#define __FD_CLR(d, set) ((set)->fds_bits[__FDELT(d)] &= ~__FDMASK(d))
#define __FD_ISSET(d, set) (((set)->fds_bits[__FDELT(d)] & __FDMASK(d)) != 0)
#define __FD_ZERO(set)
((void) memset ((void *) (set), 0, sizeof (__kernel_fd_set)))
#else /* __GNUC__ */
/* With GNU C, use inline functions instead so args are evaluated only once: */
#undef __FD_SET
static __inline__ void __FD_SET(unsigned long fd, __kernel_fd_set *fdsetp)
{
unsigned long _tmp = fd / __NFDBITS;
unsigned long _rem = fd % __NFDBITS;
fdsetp->fds_bits[_tmp] |= (1UL<<_rem);
}
#undef __FD_CLR
static __inline__ void __FD_CLR(unsigned long fd, __kernel_fd_set *fdsetp)
{
unsigned long _tmp = fd / __NFDBITS;
unsigned long _rem = fd % __NFDBITS;
fdsetp->fds_bits[_tmp] &= ~(1UL<<_rem);
}
#undef __FD_ISSET
static __inline__ int __FD_ISSET(unsigned long fd, const __kernel_fd_set *p)
{
unsigned long _tmp = fd / __NFDBITS;
unsigned long _rem = fd % __NFDBITS;
return (p->fds_bits[_tmp] & (1UL<<_rem)) != 0;
}
/*
* This will unroll the loop for the normal constant case (8 ints,
* for a 256-bit fd_set)
*/
#undef __FD_ZERO
static __inline__ void __FD_ZERO(__kernel_fd_set *p)
{
unsigned long *tmp = p->fds_bits;
int i;
if (__builtin_constant_p(__FDSET_LONGS)) {
switch (__FDSET_LONGS) {
case 16:
tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0;
tmp[ 4] = 0; tmp[ 5] = 0; tmp[ 6] = 0; tmp[ 7] = 0;
tmp[ 8] = 0; tmp[ 9] = 0; tmp[10] = 0; tmp[11] = 0;
tmp[12] = 0; tmp[13] = 0; tmp[14] = 0; tmp[15] = 0;
return;
case 8:
tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0;
tmp[ 4] = 0; tmp[ 5] = 0; tmp[ 6] = 0; tmp[ 7] = 0;
return;
case 4:
tmp[ 0] = 0; tmp[ 1] = 0; tmp[ 2] = 0; tmp[ 3] = 0;
return;
}
}
i = __FDSET_LONGS;
while (i) {
i--;
*tmp = 0;
tmp++;
}
}
#endif /* __GNUC__ */
#endif /* __KERNEL__ */
再x86的系统架构中,是采用汇编语言实现的,效率更高。
include/asm-x86/posix_types_32.h
#undef __FD_SET
#define __FD_SET(fd,fdsetp)
asm volatile("btsl %1,%0":
"+m" (*(__kernel_fd_set *)(fdsetp))
: "r" ((int)(fd)))
#undef __FD_CLR
#define __FD_CLR(fd,fdsetp)
asm volatile("btrl %1,%0":
"+m" (*(__kernel_fd_set *)(fdsetp))
: "r" ((int) (fd)))
#undef __FD_ISSET
#define __FD_ISSET(fd,fdsetp)
(__extension__
({
unsigned char __result;
asm volatile("btl %1,%2 ; setb %0"
: "=q" (__result)
: "r" ((int)(fd)),
"m" (*(__kernel_fd_set *)(fdsetp)));
__result;
}))
#undef __FD_ZERO
#define __FD_ZERO(fdsetp)
do {
int __d0, __d1;
asm volatile("cld ; rep ; stosl"
: "=m" (*(__kernel_fd_set *)(fdsetp)),
"=&c" (__d0), "=&D" (__d1)
: "a" (0), "1" (__FDSET_LONGS),
"2" ((__kernel_fd_set *)(fdsetp))
: "memory");
} while (0)
最后一个问题,问什么当fd数量多的时候,select效率低。简单看一下select的实现就不难发现了.
fs/select.c
你可能发现了,select实现并不是放在net的目录下。select并未为网络通信而实现,而是对一般的fd都可用。也再次证明了一点,再linux/unix
下面一切都是文件。
int do_select(int n, fd_set_bits *fds, s64 *timeout)
{
.....
for (i = 0; i < n; ++rinp, ++routp, ++rexp)
{
.....
}
}
不用看具体的实现,也可以明白了,select是对从0开始的一直到n-1的每一个描述做检查。因此当n比较大的时候,这里的效率是比较低的。
可以重新编译linux内核来突破1024的问题,不过也只是治标不治本,能提高效率的函数有poll,epoll